[wound_bonus]
+ BARE WOUND: [bare_wound_bonus]
+
+ "}
diff --git a/code/game/objects/items/cardboard_cutouts.dm b/code/game/objects/items/cardboard_cutouts.dm
index 960363685b1e8..e46bb676a8caf 100644
--- a/code/game/objects/items/cardboard_cutouts.dm
+++ b/code/game/objects/items/cardboard_cutouts.dm
@@ -360,3 +360,34 @@
applied_name = "Private Security Officer"
applied_desc = "A cardboard cutout of a private security officer."
mob_spawner = /obj/effect/mob_spawn/corpse/human/nanotrasensoldier
+
+/datum/cardboard_cutout/heretic
+ name = "Heretic"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of a Heretic."
+ outfit = /datum/outfit/heretic_hallucination
+
+/datum/cardboard_cutout/changeling
+ name = "Changeling"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of a Changeling."
+ outfit = /datum/outfit/changeling
+
+/datum/cardboard_cutout/pirate
+ name = "Pirate"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of a space pirate."
+ outfit = /datum/outfit/pirate/space/captain/cardboard
+
+/datum/cardboard_cutout/ninja
+ name = "Space Ninja"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of a space ninja."
+ outfit = /datum/outfit/ninja
+
+/datum/cardboard_cutout/abductor
+ name = "Abductor Agent"
+ applied_name = "Unknown"
+ applied_desc = "A cardboard cutout of an abductor agent."
+ species = /datum/species/abductor
+ outfit = /datum/outfit/abductor/agent/cardboard
diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm
index 00ca25985bfee..dad44d69e7ea7 100644
--- a/code/game/objects/items/chainsaw.dm
+++ b/code/game/objects/items/chainsaw.dm
@@ -26,6 +26,8 @@
var/on = FALSE
///The looping sound for our chainsaw when running
var/datum/looping_sound/chainsaw/chainsaw_loop
+ ///how long it takes to behead someone with this chainsaw.
+ var/behead_time = 15 SECONDS
/obj/item/chainsaw/apply_fantasy_bonuses(bonus)
. = ..()
@@ -98,8 +100,9 @@
desc = span_warning("VRRRRRRR!!!")
armour_penetration = 100
force_on = 30
+ behead_time = 2 SECONDS
-/obj/item/chainsaw/doomslayer/attack(mob/living/target_mob, mob/living/user, params)
+/obj/item/chainsaw/attack(mob/living/target_mob, mob/living/user, params)
if (target_mob.stat != DEAD)
return ..()
@@ -113,7 +116,7 @@
playsound(user, 'sound/items/weapons/slice.ogg', vol = 80, vary = TRUE)
target_mob.balloon_alert(user, "cutting off head...")
- if (!do_after(user, 2 SECONDS, target_mob, extra_checks = CALLBACK(src, PROC_REF(has_same_head), target_mob, head)))
+ if (!do_after(user, behead_time, target_mob, extra_checks = CALLBACK(src, PROC_REF(has_same_head), target_mob, head)))
return TRUE
head.dismember(silent = FALSE)
@@ -128,7 +131,7 @@
return TRUE
return FALSE
-/obj/item/chainsaw/doomslayer/proc/has_same_head(mob/living/target_mob, obj/item/bodypart/head)
+/obj/item/chainsaw/proc/has_same_head(mob/living/target_mob, obj/item/bodypart/head)
return target_mob.get_bodypart(BODY_ZONE_HEAD) == head
/obj/item/chainsaw/mounted_chainsaw
diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
index 36ab3de00c60d..b47d97bcf6607 100644
--- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
@@ -344,6 +344,23 @@
/datum/stock_part/capacitor = 1)
def_components = list(/obj/item/stock_parts/power_store/battery = /obj/item/stock_parts/power_store/battery/high/empty)
+/obj/item/circuitboard/machine/smes/connector
+ name = "power connector"
+ build_path = /obj/machinery/power/smes/connector
+ req_components = list(
+ /obj/item/stack/cable_coil = 5,
+ /datum/stock_part/capacitor = 1,)
+
+/obj/item/circuitboard/machine/smesbank
+ name = "portable SMES"
+ greyscale_colors = CIRCUIT_COLOR_ENGINEERING
+ needs_anchored = FALSE
+ build_path = /obj/machinery/power/smesbank
+ req_components = list(
+ /obj/item/stack/cable_coil = 5,
+ /obj/item/stock_parts/power_store/battery = 5,)
+ def_components = list(/obj/item/stock_parts/power_store/battery = /obj/item/stock_parts/power_store/battery/high/empty)
+
/obj/item/circuitboard/machine/techfab/department/engineering
name = "\improper Departmental Techfab - Engineering"
greyscale_colors = CIRCUIT_COLOR_ENGINEERING
@@ -352,6 +369,9 @@
/obj/item/circuitboard/machine/smes/super
def_components = list(/obj/item/stock_parts/power_store/battery = /obj/item/stock_parts/power_store/battery/super/empty)
+/obj/item/circuitboard/machine/smesbank/super
+ def_components = list(/obj/item/stock_parts/power_store/battery = /obj/item/stock_parts/power_store/battery/super/empty)
+
/obj/item/circuitboard/machine/thermomachine
name = "Thermomachine"
greyscale_colors = CIRCUIT_COLOR_ENGINEERING
@@ -1392,7 +1412,7 @@
/obj/item/circuitboard/machine/fishing_portal_generator/emagged
name = "Emagged Fishing Portal Generator"
- build_path = /obj/machinery/fishing_portal_generator
+ build_path = /obj/machinery/fishing_portal_generator/emagged
//Supply
/obj/item/circuitboard/machine/ore_redemption
@@ -1720,3 +1740,65 @@
req_components = list(
/datum/stock_part/servo = 1,
)
+
+/obj/item/circuitboard/machine/manucrafter
+ name = /obj/machinery/power/manufacturing/crafter::name
+ greyscale_colors = CIRCUIT_COLOR_ENGINEERING
+ build_path = /obj/machinery/power/manufacturing/crafter
+ req_components = list(
+ /obj/item/stack/sheet/iron = 5,
+ /datum/stock_part/servo = 1,
+ )
+
+/obj/item/circuitboard/machine/manulathe
+ name = /obj/machinery/power/manufacturing/lathe::name
+ greyscale_colors = CIRCUIT_COLOR_ENGINEERING
+ build_path = /obj/machinery/power/manufacturing/lathe
+ req_components = list(
+ /obj/item/stack/sheet/iron = 5,
+ /datum/stock_part/servo = 1,
+ )
+
+/obj/item/circuitboard/machine/manucrusher
+ name = /obj/machinery/power/manufacturing/crusher::name
+ greyscale_colors = CIRCUIT_COLOR_ENGINEERING
+ build_path = /obj/machinery/power/manufacturing/crusher
+ req_components = list(
+ /obj/item/stack/sheet/iron = 5,
+ /datum/stock_part/servo = 1,
+ )
+
+/obj/item/circuitboard/machine/manuunloader
+ name = /obj/machinery/power/manufacturing/unloader::name
+ greyscale_colors = CIRCUIT_COLOR_ENGINEERING
+ build_path = /obj/machinery/power/manufacturing/unloader
+ req_components = list(
+ /obj/item/stack/sheet/iron = 5,
+ /datum/stock_part/servo = 1,
+ )
+
+/obj/item/circuitboard/machine/manusorter
+ name = /obj/machinery/power/manufacturing/sorter::name
+ greyscale_colors = CIRCUIT_COLOR_ENGINEERING
+ build_path = /obj/machinery/power/manufacturing/sorter
+ req_components = list(
+ /obj/item/stack/sheet/iron = 5,
+ /datum/stock_part/scanning_module = 1,
+ )
+
+/obj/item/circuitboard/machine/manusmelter
+ name = /obj/machinery/power/manufacturing/smelter::name
+ greyscale_colors = CIRCUIT_COLOR_ENGINEERING
+ build_path = /obj/machinery/power/manufacturing/smelter
+ req_components = list(
+ /obj/item/stack/sheet/iron = 5,
+ /datum/stock_part/micro_laser = 1,
+ )
+
+/obj/item/circuitboard/machine/manurouter
+ name = /obj/machinery/power/manufacturing/router::name
+ greyscale_colors = CIRCUIT_COLOR_ENGINEERING
+ build_path = /obj/machinery/power/manufacturing/router
+ req_components = list(
+ /obj/item/stack/sheet/iron = 5,
+ )
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 808b37c54722e..78b150ed89c62 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -504,8 +504,8 @@
var/clicky
if(LAZYACCESS(modifiers, ICON_X) && LAZYACCESS(modifiers, ICON_Y))
- clickx = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(world.icon_size/2), world.icon_size/2)
- clicky = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(world.icon_size/2), world.icon_size/2)
+ clickx = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2)
+ clicky = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2)
if(!instant)
to_chat(user, span_notice("You start drawing a [temp] on the [target.name]..."))
diff --git a/code/game/objects/items/dehy_carp.dm b/code/game/objects/items/dehy_carp.dm
index 88a3a98a5bb26..e863f09ecde5b 100644
--- a/code/game/objects/items/dehy_carp.dm
+++ b/code/game/objects/items/dehy_carp.dm
@@ -71,3 +71,6 @@
UnregisterSignal(owner, COMSIG_QDELETING)
owner = null
+
+/obj/item/toy/plush/carpplushie/dehy_carp/peaceful
+ mobtype = /mob/living/basic/carp/passive
diff --git a/code/game/objects/items/devices/aicard_evil.dm b/code/game/objects/items/devices/aicard_evil.dm
index 852a105de350f..bb23779fafec6 100644
--- a/code/game/objects/items/devices/aicard_evil.dm
+++ b/code/game/objects/items/devices/aicard_evil.dm
@@ -35,7 +35,7 @@
balloon_alert(user, "invalid access!")
return
var/mob/chosen_one = SSpolling.poll_ghosts_for_target(
- check_jobban = ROLE_OPERATIVE,
+ check_jobban = list(ROLE_OPERATIVE, JOB_AI),
poll_time = 20 SECONDS,
checked_target = src,
ignore_category = POLL_IGNORE_SYNDICATE,
@@ -47,12 +47,12 @@
/// Poll has concluded with a ghost, create the AI
/obj/item/aicard/syndie/loaded/proc/on_poll_concluded(mob/user, datum/antagonist/nukeop/op_datum, mob/dead/observer/ghost)
- if(isnull(ghost))
+ if(!ismob(ghost))
to_chat(user, span_warning("Unable to connect to S.E.L.F. dispatch. Please wait and try again later or use the intelliCard on your uplink to get your points refunded."))
return
// pick ghost, create AI and transfer
- var/mob/living/silicon/ai/weak_syndie/new_ai = new /mob/living/silicon/ai/weak_syndie(get_turf(src), new /datum/ai_laws/syndicate_override, ghost)
+ var/mob/living/silicon/ai/weak_syndie/new_ai = new /mob/living/silicon/ai/weak_syndie(null, new /datum/ai_laws/syndicate_override, ghost)
// create and apply syndie datum
var/datum/antagonist/nukeop/nuke_datum = new()
nuke_datum.send_to_spawnpoint = FALSE
diff --git a/code/game/objects/items/devices/pressureplates.dm b/code/game/objects/items/devices/pressureplates.dm
index 18bb026745ac0..17f324d109f99 100644
--- a/code/game/objects/items/devices/pressureplates.dm
+++ b/code/game/objects/items/devices/pressureplates.dm
@@ -7,7 +7,8 @@
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
icon_state = "pressureplate"
- layer = LOW_OBJ_LAYER
+ plane = FLOOR_PLANE
+ layer = HIGH_TURF_LAYER
var/trigger_mob = TRUE
var/trigger_item = FALSE
var/specific_item = null
diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm
index 270c070d6dc5e..5b7ee4f7026b8 100644
--- a/code/game/objects/items/devices/scanners/health_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/health_analyzer.dm
@@ -280,7 +280,7 @@
var/list/missing_organs = list()
if(!humantarget.get_organ_slot(ORGAN_SLOT_BRAIN))
missing_organs[ORGAN_SLOT_BRAIN] = "Brain"
- if(!humantarget.needs_heart() && !humantarget.get_organ_slot(ORGAN_SLOT_HEART))
+ if(humantarget.needs_heart() && !humantarget.get_organ_slot(ORGAN_SLOT_HEART))
missing_organs[ORGAN_SLOT_HEART] = "Heart"
if(!HAS_TRAIT_FROM(humantarget, TRAIT_NOBREATH, SPECIES_TRAIT) && !isnull(humantarget.dna.species.mutantlungs) && !humantarget.get_organ_slot(ORGAN_SLOT_LUNGS))
missing_organs[ORGAN_SLOT_LUNGS] = "Lungs"
diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm
index 9c4192116a003..b4150ecb72fea 100644
--- a/code/game/objects/items/extinguisher.dm
+++ b/code/game/objects/items/extinguisher.dm
@@ -6,6 +6,8 @@
worn_icon_state = "fire_extinguisher"
inhand_icon_state = "fire_extinguisher"
hitsound = 'sound/items/weapons/smash.ogg'
+ pickup_sound = 'sound/items/handling/gas_tank/gas_tank_pick_up.ogg'
+ drop_sound = 'sound/items/handling/gas_tank/gas_tank_drop.ogg'
obj_flags = CONDUCTS_ELECTRICITY
throwforce = 10
w_class = WEIGHT_CLASS_NORMAL
@@ -47,6 +49,9 @@
var/cooling_power = 2
/// Icon state when inside a tank holder.
var/tank_holder_icon_state = "holder_extinguisher"
+ ///The sound a fire extinguisher makes when picked up, dropped if there is liquid inside.
+ var/fire_extinguisher_reagent_sloshing_sound = SFX_DEFAULT_LIQUID_SLOSH
+
/obj/item/extinguisher/Initialize(mapload)
. = ..()
@@ -66,6 +71,17 @@
context[SCREENTIP_CONTEXT_ALT_LMB] = "Empty"
return CONTEXTUAL_SCREENTIP_SET
+/obj/item/extinguisher/dropped(mob/user, silent)
+ . = ..()
+ if(fire_extinguisher_reagent_sloshing_sound && reagents.total_volume > 0)
+ playsound(src, fire_extinguisher_reagent_sloshing_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
+
+/obj/item/extinguisher/equipped(mob/user, slot, initial = FALSE)
+ . = ..()
+ if((slot & ITEM_SLOT_HANDS) && fire_extinguisher_reagent_sloshing_sound && reagents.total_volume > 0)
+ playsound(src, fire_extinguisher_reagent_sloshing_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
+
+
/obj/item/extinguisher/empty
starting_water = FALSE
diff --git a/code/game/objects/items/food/egg.dm b/code/game/objects/items/food/egg.dm
index d731d8c4978dc..bbb7d6784e2e0 100644
--- a/code/game/objects/items/food/egg.dm
+++ b/code/game/objects/items/food/egg.dm
@@ -130,9 +130,9 @@ GLOBAL_VAR_INIT(chicks_from_eggs, 0)
return ITEM_INTERACT_BLOCKING
var/atom/broken_egg = new /obj/item/food/rawegg(interacting_with.loc)
if(LAZYACCESS(modifiers, ICON_X))
- broken_egg.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(world.icon_size/2), world.icon_size/2)
+ broken_egg.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2)
if(LAZYACCESS(modifiers, ICON_Y))
- broken_egg.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(world.icon_size/2), world.icon_size/2)
+ broken_egg.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2)
playsound(user, 'sound/items/sheath.ogg', 40, TRUE)
reagents.copy_to(broken_egg, reagents.total_volume)
diff --git a/code/game/objects/items/food/meatdish.dm b/code/game/objects/items/food/meatdish.dm
index 9bda586b2693f..770b6f8bf3124 100644
--- a/code/game/objects/items/food/meatdish.dm
+++ b/code/game/objects/items/food/meatdish.dm
@@ -88,6 +88,13 @@
cell_line = null
starting_reagent_purity = 0.3
+///carp fillet, but without the toxin. Used by baby carps (fish item), which have a trait that handles the toxin already.
+/obj/item/food/fishmeat/carp/no_tox
+
+/obj/item/food/fishmeat/carp/no_tox/Initialize(mapload)
+ food_reagents -= /datum/reagent/toxin/carpotoxin
+ return ..()
+
/obj/item/food/fishmeat/moonfish
name = "moonfish fillet"
desc = "A fillet of moonfish."
diff --git a/code/game/objects/items/inducer.dm b/code/game/objects/items/inducer.dm
index 2404974d69970..0f66cd4b6d108 100644
--- a/code/game/objects/items/inducer.dm
+++ b/code/game/objects/items/inducer.dm
@@ -7,212 +7,219 @@
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
force = 7
+
/// Multiplier that determines the speed at which this inducer works at.
var/power_transfer_multiplier = 1
+ /// Is the battery hatch opened
var/opened = FALSE
- var/cell_type = /obj/item/stock_parts/power_store/battery/high
- var/obj/item/stock_parts/power_store/powerdevice
+ /// The cell for used in recharging cycles
+ var/obj/item/stock_parts/power_store/powerdevice = /obj/item/stock_parts/power_store/battery/high
+ /// Are we in the process of recharging something
var/recharging = FALSE
/obj/item/inducer/Initialize(mapload)
. = ..()
- if(!powerdevice && cell_type)
- powerdevice = new cell_type
-/obj/item/inducer/proc/induce(obj/item/stock_parts/power_store/target, coefficient)
- var/obj/item/stock_parts/power_store/our_cell = get_cell()
- var/rating_base = target.rating_base
- var/totransfer = min(our_cell.charge, (rating_base * coefficient * power_transfer_multiplier))
- var/transferred = target.give(totransfer)
+ if(ispath(powerdevice))
+ powerdevice = new powerdevice(src)
- our_cell.use(transferred)
- our_cell.update_appearance()
- target.update_appearance()
+ register_context()
-/obj/item/inducer/get_cell()
- return powerdevice
+ update_appearance(UPDATE_OVERLAYS)
-/obj/item/inducer/emp_act(severity)
+/obj/item/inducer/Destroy(force)
+ QDEL_NULL(powerdevice)
. = ..()
- var/obj/item/stock_parts/power_store/our_cell = get_cell()
- if(!isnull(our_cell) && !(. & EMP_PROTECT_CONTENTS))
- our_cell.emp_act(severity)
-/obj/item/inducer/attack_atom(obj/target, mob/living/carbon/user, params)
- if(user.combat_mode)
- return ..()
+/obj/item/inducer/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == powerdevice)
+ powerdevice = null
- if(cantbeused(user))
- return
+/obj/item/inducer/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = NONE
- if(recharge(target, user))
+ if(isnull(held_item))
+ if(opened && !QDELETED(powerdevice))
+ context[SCREENTIP_CONTEXT_LMB] = "Remove Cell"
+ . = CONTEXTUAL_SCREENTIP_SET
return
- return ..()
+ if(opened)
+ if(istype(held_item, /obj/item/stock_parts/power_store) && QDELETED(powerdevice))
+ context[SCREENTIP_CONTEXT_LMB] = "Insert cell"
+ return CONTEXTUAL_SCREENTIP_SET
-/obj/item/inducer/proc/cantbeused(mob/user)
- if(!ISADVANCEDTOOLUSER(user))
- to_chat(user, span_warning("You don't have the dexterity to use [src]!"))
- return TRUE
+ if(istype(held_item, /obj/item/stack/sheet/mineral/plasma) && !QDELETED(powerdevice))
+ context[SCREENTIP_CONTEXT_LMB] = "Charge cell"
+ return CONTEXTUAL_SCREENTIP_SET
- var/obj/item/stock_parts/power_store/our_cell = get_cell()
+ if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
+ context[SCREENTIP_CONTEXT_LMB] = "[opened ? "Close" : "Open"] Panel"
+ return CONTEXTUAL_SCREENTIP_SET
- if(isnull(our_cell))
- balloon_alert(user, "no cell installed!")
- return TRUE
+/obj/item/inducer/examine(mob/living/user)
+ . = ..()
- if(!our_cell.charge)
- balloon_alert(user, "no charge!")
- return TRUE
- return FALSE
+ if(!QDELETED(powerdevice))
+ . += span_notice("Its display shows: [display_energy(powerdevice.charge)].")
+ if(opened)
+ . += span_notice("The cell can be removed with an empty hand.")
+ . += span_notice("Plasma sheets can be used to recharge the cell.")
+ else
+ . += span_warning("It's missing a power cell.")
-/obj/item/inducer/screwdriver_act(mob/living/user, obj/item/tool)
- . = TRUE
- tool.play_tool_sound(src)
+ . += span_notice("Its battery compartment can be [EXAMINE_HINT("screwed")] [opened ? "shut" : "open"].")
+
+/obj/item/inducer/update_overlays()
+ . = ..()
if(!opened)
- to_chat(user, span_notice("You unscrew the battery compartment."))
- opened = TRUE
- update_appearance()
return
- else
- to_chat(user, span_notice("You close the battery compartment."))
- opened = FALSE
- update_appearance()
+ . += "inducer-[!QDELETED(powerdevice) ? "bat" : "nobat"]"
+
+/obj/item/inducer/get_cell()
+ return powerdevice
+
+/obj/item/inducer/emp_act(severity)
+ . = ..()
+ if(!QDELETED(powerdevice) && !(. & EMP_PROTECT_CONTENTS))
+ powerdevice.emp_act(severity)
+
+/obj/item/inducer/screwdriver_act(mob/living/user, obj/item/tool)
+ . = NONE
+
+ if(!tool.use_tool(src, user, delay = 0))
return
-/obj/item/inducer/attackby(obj/item/used_item, mob/user)
- var/obj/item/stock_parts/power_store/our_cell = get_cell()
- if(istype(used_item, /obj/item/stock_parts/power_store))
- if(opened)
- if(isnull(our_cell))
- if(!user.transferItemToLoc(used_item, src))
- return
- to_chat(user, span_notice("You insert [used_item] into [src]."))
- powerdevice = used_item
- update_appearance()
- return
- else
- to_chat(user, span_warning("[src] already has \a [our_cell] installed!"))
- return
-
- if (istype(used_item, /obj/item/stack/sheet/mineral/plasma) && !isnull(our_cell))
- if(our_cell.charge == our_cell.maxcharge)
- balloon_alert(user, "already fully charged!")
- return
- used_item.use(1)
- our_cell.give(1.5 * STANDARD_CELL_CHARGE)
+ opened = !opened
+ to_chat(user, span_notice("You [opened ? "open" : "close"] the battery compartment."))
+ update_appearance(UPDATE_OVERLAYS)
+
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/inducer/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ . = NONE
+ if(user.combat_mode || !istype(tool) || tool.flags_1 & HOLOGRAM_1 || tool.item_flags & ABSTRACT)
+ return ITEM_INTERACT_SKIP_TO_ATTACK
+
+ if(!opened)
+ balloon_alert(user, "open first!")
+ return ITEM_INTERACT_FAILURE
+
+ if(istype(tool, /obj/item/stock_parts/power_store))
+ if(!QDELETED(powerdevice))
+ balloon_alert(user, "cell already installed!")
+ return ITEM_INTERACT_FAILURE
+
+ if(!user.transferItemToLoc(tool, src))
+ balloon_alert(user, "stuck in hand!")
+ return ITEM_INTERACT_FAILURE
+
+ powerdevice = tool
+ return ITEM_INTERACT_SUCCESS
+
+ if(istype(tool, /obj/item/stack/sheet/mineral/plasma) && !QDELETED(powerdevice))
+ if(!powerdevice.used_charge())
+ balloon_alert(user, "fully charged!")
+ return ITEM_INTERACT_FAILURE
+
+ tool.use(1)
+ powerdevice.give(1.5 * STANDARD_CELL_CHARGE)
balloon_alert(user, "cell recharged")
- return
- if(cantbeused(user))
- return
+ return ITEM_INTERACT_SUCCESS
- if(recharge(used_item, user))
- return
+/obj/item/inducer/interact_with_atom(atom/movable/interacting_with, mob/living/user, list/modifiers)
+ . = NONE
+ if(user.combat_mode || !istype(interacting_with) || interacting_with.flags_1 & HOLOGRAM_1)
+ return ITEM_INTERACT_SKIP_TO_ATTACK
+
+ //basic checks
+ if(opened)
+ balloon_alert(user, "close first!")
+ return ITEM_INTERACT_FAILURE
- return ..()
+ if(recharging || (!isturf(interacting_with) && user.loc == interacting_with))
+ return ITEM_INTERACT_FAILURE
-/obj/item/inducer/proc/recharge(atom/movable/target, mob/user)
- if(!isturf(target) && user.loc == target)
- return FALSE
- if(recharging)
- return TRUE
+ if(!ISADVANCEDTOOLUSER(user))
+ to_chat(user, span_warning("You don't have the dexterity to use [src]!"))
+ return ITEM_INTERACT_FAILURE
+ if(QDELETED(powerdevice))
+ balloon_alert(user, "no cell installed!")
+ return ITEM_INTERACT_FAILURE
+
+ if(!powerdevice.charge)
+ balloon_alert(user, "no charge!")
+ return ITEM_INTERACT_FAILURE
+
+ var/obj/item/stock_parts/power_store/target_cell = interacting_with.get_cell(src, user)
+
+ if(QDELETED(target_cell))
+ return ITEM_INTERACT_FAILURE
+
+ if(!target_cell.used_charge())
+ balloon_alert(user, "fully charged!")
+ return ITEM_INTERACT_FAILURE
+
+ //begin recharging
recharging = TRUE
- var/obj/item/stock_parts/power_store/our_cell = get_cell()
- var/obj/item/stock_parts/power_store/target_cell = target.get_cell()
- var/obj/target_as_object = target
- var/coefficient = 1
-
- if(istype(target, /obj/item/gun/energy) || istype(target, /obj/item/clothing/suit/space))
- to_chat(user, span_alert("Error: unable to interface with device."))
- return FALSE
-
- if(target_cell)
- var/done_any = FALSE
- if(target_cell.charge >= target_cell.maxcharge)
- balloon_alert(user, "it's fully charged!")
- recharging = FALSE
- return TRUE
-
- user.visible_message(span_notice("[user] starts recharging [target] with [src]."), span_notice("You start recharging [target] with [src]."))
-
- while(target_cell.charge < target_cell.maxcharge)
- if(do_after(user, 1 SECONDS, target = user) && our_cell.charge)
- done_any = TRUE
- induce(target_cell, coefficient)
- do_sparks(1, FALSE, target)
- if(istype(target_as_object))
- target_as_object.update_appearance()
- else
- break
- if(done_any) // Only show a message if we succeeded at least once
- user.visible_message(span_notice("[user] recharged [target]!"), span_notice("You recharged [target]!"))
- recharging = FALSE
- return TRUE
- recharging = FALSE
+ user.visible_message(span_notice("[user] starts recharging [interacting_with] with [src]."), span_notice("You start recharging [interacting_with] with [src]."))
+ var/done_any = FALSE
+ while(target_cell.used_charge())
+ if(!do_after(user, 1 SECONDS, target = user))
+ break
-/obj/item/inducer/attack(mob/target, mob/living/user)
- if(user.combat_mode)
- return ..()
+ //transfer of charge
+ var/transferred = min(powerdevice.charge, target_cell.used_charge(), (target_cell.rating_base * target_cell.rating * power_transfer_multiplier))
+ if(!transferred)
+ break
+ powerdevice.use(target_cell.give(transferred))
- if(cantbeused(user))
- return
+ //update all appearances
+ powerdevice.update_appearance()
+ target_cell.update_appearance()
+ interacting_with.update_appearance()
- if(recharge(target, user))
- return
+ //sparks & update
+ do_sparks(1, FALSE, interacting_with)
+ done_any = TRUE
- return ..()
+ recharging = FALSE
+ // Only show a message if we succeeded at least once
+ if(done_any)
+ user.visible_message(span_notice("[user] recharges [interacting_with]!"), span_notice("You recharge [interacting_with]!"))
+
+ return ITEM_INTERACT_SUCCESS
/obj/item/inducer/attack_self(mob/user)
- if(opened && powerdevice)
+ if(opened && !QDELETED(powerdevice))
user.visible_message(span_notice("[user] removes [powerdevice] from [src]!"), span_notice("You remove [powerdevice]."))
powerdevice.update_appearance()
user.put_in_hands(powerdevice)
- powerdevice = null
- update_appearance()
-
-
-/obj/item/inducer/examine(mob/living/user)
- . = ..()
- var/obj/item/stock_parts/power_store/our_cell = get_cell()
- if(!isnull(our_cell))
- . += span_notice("Its display shows: [display_energy(our_cell.charge)].")
- else
- . += span_notice("Its display is dark.")
- if(opened)
- . += span_notice("Its battery compartment is open.")
-
-/obj/item/inducer/update_overlays()
- . = ..()
- if(!opened)
- return
- . += "inducer-[!isnull(get_cell()) ? "bat" : "nobat"]"
+ update_appearance(UPDATE_OVERLAYS)
/obj/item/inducer/empty
- cell_type = null
+ powerdevice = null
opened = TRUE
/obj/item/inducer/orderable
- cell_type = /obj/item/stock_parts/power_store/battery/upgraded
+ powerdevice = /obj/item/stock_parts/power_store/battery/upgraded
opened = FALSE
/obj/item/inducer/sci
icon_state = "inducer-sci"
inhand_icon_state = "inducer-sci"
desc = "A tool for inductively charging internal power cells. This one has a science color scheme, and is less potent than its engineering counterpart."
- cell_type = null
+ powerdevice = null
opened = TRUE
-/obj/item/inducer/sci/Initialize(mapload)
- . = ..()
- update_appearance()
-
/obj/item/inducer/syndicate
icon_state = "inducer-syndi"
inhand_icon_state = "inducer-syndi"
desc = "A tool for inductively charging internal power cells. This one has a suspicious colour scheme, and seems to be rigged to transfer charge at a much faster rate."
power_transfer_multiplier = 2 // 2x the base speed
- cell_type = /obj/item/stock_parts/power_store/cell/super
+ powerdevice = /obj/item/stock_parts/power_store/battery/super
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index 813ace6ced89e..9a1b8bed101f0 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -515,10 +515,10 @@
greyscale_config_inhand_left = /datum/greyscale_config/cleric_mace_lefthand
greyscale_config_inhand_right = /datum/greyscale_config/cleric_mace_righthand
greyscale_config_worn = /datum/greyscale_config/cleric_mace
- greyscale_colors = COLOR_WHITE
+ greyscale_colors = COLOR_WHITE + COLOR_BROWN
material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_AFFECT_STATISTICS //Material type changes the prefix as well as the color.
- custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*6) //Defaults to an Iron Mace.
+ custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 4.5, /datum/material/wood = SHEET_MATERIAL_AMOUNT * 1.5) //Defaults to an Iron Mace.
slot_flags = ITEM_SLOT_BELT
force = 14
w_class = WEIGHT_CLASS_BULKY
@@ -529,6 +529,26 @@
attack_verb_continuous = list("smacks", "strikes", "cracks", "beats")
attack_verb_simple = list("smack", "strike", "crack", "beat")
+///Cleric maces are made of two custom materials: one is handle, and the other is the mace itself.
+/obj/item/melee/cleric_mace/get_material_multiplier(datum/material/custom_material, list/materials, index)
+ if(length(materials) <= 1)
+ return 1.2
+ if(index == 1)
+ return 1
+ else
+ return 0.3
+
+/obj/item/melee/cleric_mace/get_material_prefixes(list/materials)
+ var/datum/material/material = materials[1]
+ return material.name //It only inherits the name of the main material it's made of. The secondary is in the description.
+
+/obj/item/melee/cleric_mace/finalize_material_effects(list/materials)
+ . = ..()
+ if(length(materials) == 1)
+ return
+ var/datum/material/material = materials[2]
+ desc = "[initial(desc)] Its handle is made of [material.name]."
+
/obj/item/melee/cleric_mace/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
if(attack_type == PROJECTILE_ATTACK || attack_type == LEAP_ATTACK)
final_block_chance = 0 //Don't bring a...mace to a gunfight, and also you aren't going to really block someone full body tackling you with a mace
diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm
index 0148076f529ec..2d700cf3ff0c0 100644
--- a/code/game/objects/items/pet_carrier.dm
+++ b/code/game/objects/items/pet_carrier.dm
@@ -11,6 +11,10 @@
inhand_icon_state = "pet_carrier"
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ greyscale_config = /datum/greyscale_config/pet_carrier
+ greyscale_config_inhand_left = /datum/greyscale_config/pet_carrier_inhands_left
+ greyscale_config_inhand_right = /datum/greyscale_config/pet_carrier_inhands_right
+ greyscale_colors = COLOR_BLUE
force = 5
attack_verb_continuous = list("bashes", "carries")
attack_verb_simple = list("bash", "carry")
@@ -19,7 +23,6 @@
throw_range = 3
custom_materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 7.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT)
interaction_flags_mouse_drop = NEED_DEXTERITY
-
var/open = TRUE
var/locked = FALSE
var/list/occupants = list()
@@ -147,14 +150,9 @@
if(open)
icon_state = initial(icon_state)
return ..()
- icon_state = "[base_icon_state]_[!occupants.len ? "closed" : "occupied"]"
+ icon_state = "[base_icon_state]_[!occupants.len ? "closed" : "occupied"]_[locked ? "locked" : "unlocked"]"
return ..()
-/obj/item/pet_carrier/update_overlays()
- . = ..()
- if(!open)
- . += "[base_icon_state]_[locked ? "" : "un"]locked"
-
/obj/item/pet_carrier/mouse_drop_dragged(atom/over_atom, mob/user, src_location, over_location, params)
if(isopenturf(over_atom) && open && occupants.len)
user.visible_message(span_notice("[user] unloads [src]."), \
@@ -202,5 +200,9 @@
base_icon_state = "biopod"
icon_state = "biopod_open"
inhand_icon_state = "biopod"
+ greyscale_config = null
+ greyscale_config_inhand_left = null
+ greyscale_config_inhand_right = null
+ greyscale_colors = null
#undef pet_carrier_full
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 4b210c47e9fca..2e7682eda1fd1 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -648,7 +648,7 @@
name = "Internal inducer"
icon = 'icons/obj/tools.dmi'
icon_state = "inducer-engi"
- cell_type = null
+ powerdevice = null
/obj/item/inducer/cyborg/get_cell()
var/obj/item/robot_model/possible_model = loc
@@ -657,7 +657,7 @@
. = silicon_friend.cell
/obj/item/inducer/cyborg/screwdriver_act(mob/living/user, obj/item/tool)
- return FALSE
+ return NONE
/obj/item/borg/upgrade/pinpointer
name = "medical cyborg crew pinpointer"
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index e2a8e10c4df49..18891ebdd9306 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -77,18 +77,19 @@
amount = new_amount
while(amount > max_amount)
amount -= max_amount
- new type(loc, max_amount, FALSE)
+ new type(loc, max_amount, FALSE, mat_override, mat_amt)
if(!merge_type)
merge_type = type
+ . = ..()
+
+ var/materials_mult = amount
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)
+ materials_mult *= mat_amt
+ mats_per_unit = mat_override
+ if(LAZYLEN(mats_per_unit))
+ initialize_materials(mats_per_unit, materials_mult)
- . = ..()
if(merge)
for(var/obj/item/stack/item_stack in loc)
if(item_stack == src)
@@ -118,26 +119,15 @@
if(is_path_in_list(merge_type, GLOB.golem_stack_food_directory))
AddComponent(/datum/component/golem_food, golem_food_key = merge_type)
-/** 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.
- */
+///Called to lazily update the materials of the item whenever the used or if more is added
/obj/item/stack/proc/update_custom_materials()
- set_custom_materials(mats_per_unit, amount, is_update=TRUE)
+ if(length(mats_per_unit))
+ set_custom_materials(mats_per_unit, amount)
-/**
- * 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/apply_material_effects(list/materials)
+ . = ..()
+ if(amount)
+ mats_per_unit = SSmaterials.FindOrCreateMaterialCombo(materials, 1/amount)
/obj/item/stack/blend_requirements()
if(is_cyborg)
@@ -437,7 +427,7 @@
if((recipe.crafting_flags & CRAFT_APPLIES_MATS) && LAZYLEN(mats_per_unit))
if(isstack(created))
var/obj/item/stack/crafted_stack = created
- crafted_stack.set_mats_per_unit(mats_per_unit, recipe.req_amount / recipe.res_amount)
+ crafted_stack.set_custom_materials(mats_per_unit, (recipe.req_amount / recipe.res_amount) * crafted_stack.amount)
else
created.set_custom_materials(mats_per_unit, recipe.req_amount / recipe.res_amount)
@@ -540,8 +530,7 @@
amount -= used
if(check && is_zero_amount(delete_if_zero = TRUE))
return TRUE
- if(length(mats_per_unit))
- update_custom_materials()
+ update_custom_materials()
update_appearance()
update_weight()
return TRUE
@@ -588,8 +577,7 @@
source.add_charge(_amount * cost)
else
amount += _amount
- if(length(mats_per_unit))
- update_custom_materials()
+ update_custom_materials()
update_appearance()
update_weight()
diff --git a/code/game/objects/items/storage/boxes/clothes_boxes.dm b/code/game/objects/items/storage/boxes/clothes_boxes.dm
index 582b611186c56..86cd3931f1e20 100644
--- a/code/game/objects/items/storage/boxes/clothes_boxes.dm
+++ b/code/game/objects/items/storage/boxes/clothes_boxes.dm
@@ -38,8 +38,8 @@
new /obj/item/stack/sticky_tape(src)
/obj/item/storage/box/fakesyndiesuit
- name = "boxed space suit and helmet"
- desc = "A sleek, sturdy box used to hold replica spacesuits."
+ name = "boxed replica space suit and helmet"
+ desc = "A sleek, sturdy box used to hold toy spacesuits."
icon_state = "syndiebox"
illustration = "syndiesuit"
diff --git a/code/game/objects/items/storage/briefcase.dm b/code/game/objects/items/storage/briefcase.dm
index bd474808446af..a0b6e892e0591 100644
--- a/code/game/objects/items/storage/briefcase.dm
+++ b/code/game/objects/items/storage/briefcase.dm
@@ -110,3 +110,12 @@
new /obj/item/clothing/mask/balaclava(src)
new /obj/item/bodybag(src)
new /obj/item/soap/nanotrasen(src)
+
+/obj/item/storage/briefcase/hitchiker/PopulateContents()
+ new /obj/item/food/sandwich/peanut_butter_jelly(src)
+ new /obj/item/food/sandwich/peanut_butter_jelly(src)
+ new /obj/item/reagent_containers/cup/glass/waterbottle/large(src)
+ new /obj/item/soap(src)
+ new /obj/item/pillow/random(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/tank/internals/emergency_oxygen(src)
diff --git a/code/game/objects/items/storage/holsters.dm b/code/game/objects/items/storage/holsters.dm
index 010cc7ffd7f5a..b1bdc86b39db8 100644
--- a/code/game/objects/items/storage/holsters.dm
+++ b/code/game/objects/items/storage/holsters.dm
@@ -198,7 +198,7 @@
/obj/item/storage/belt/holster/nukie/cowboy/full/PopulateContents()
generate_items_inside(list(
/obj/item/ammo_box/a357 = 2,
- /obj/item/gun/ballistic/revolver/syndicate/cowboy/nuclear = 1,
+ /obj/item/gun/ballistic/revolver/cowboy/nuclear = 1,
), src)
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index c939e3d0dd9f4..5f08747e60c92 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -19,7 +19,7 @@
hitsound = 'sound/items/weapons/smash.ogg'
drop_sound = 'sound/items/handling/toolbox/toolbox_drop.ogg'
pickup_sound = 'sound/items/handling/toolbox/toolbox_pickup.ogg'
- material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR
+ material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
var/latches = "single_latch"
var/has_latches = TRUE
wound_bonus = 5
@@ -381,7 +381,7 @@
/obj/item/storage/toolbox/guncase/revolver
name = "revolver gun case"
- weapon_to_spawn = /obj/item/gun/ballistic/revolver/syndicate/nuclear
+ weapon_to_spawn = /obj/item/gun/ballistic/revolver/badass/nuclear
extra_to_spawn = /obj/item/ammo_box/a357
/obj/item/storage/toolbox/guncase/sword_and_board
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 05b8a5a0e8ab0..9e5ae3ba74b1c 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -78,7 +78,7 @@
new /obj/item/jammer(src) // 5 tc
if(KIT_GUN)
- new /obj/item/gun/ballistic/revolver/syndicate(src) // 13 tc
+ new /obj/item/gun/ballistic/revolver(src) // 13 tc
new /obj/item/ammo_box/a357(src) // 4tc
new /obj/item/ammo_box/a357(src)
new /obj/item/storage/belt/holster/chameleon(src) // 1 tc
diff --git a/code/game/objects/items/syndie_spraycan.dm b/code/game/objects/items/syndie_spraycan.dm
index fb6192c6e3990..5690ecb7a28cc 100644
--- a/code/game/objects/items/syndie_spraycan.dm
+++ b/code/game/objects/items/syndie_spraycan.dm
@@ -157,7 +157,8 @@
mergeable_decal = FALSE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
clean_type = CLEAN_TYPE_HARD_DECAL
- layer = SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
var/slip_time = 6 SECONDS
var/slip_flags = NO_SLIP_WHEN_WALKING
diff --git a/code/game/objects/items/tail_pin.dm b/code/game/objects/items/tail_pin.dm
index df191da845aa4..dc2ffaefea0a9 100644
--- a/code/game/objects/items/tail_pin.dm
+++ b/code/game/objects/items/tail_pin.dm
@@ -44,6 +44,6 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/party_game, 32)
var/list/modifiers = params2list(params)
if(!LAZYACCESS(modifiers, ICON_X) || !LAZYACCESS(modifiers, ICON_Y))
return
- I.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(world.icon_size/2), world.icon_size/2)
- I.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(world.icon_size/2), world.icon_size/2)
+ I.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2)
+ I.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2)
return TRUE
diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm
index 8634336504d54..6fca04c7a3f40 100644
--- a/code/game/objects/items/teleportation.dm
+++ b/code/game/objects/items/teleportation.dm
@@ -110,10 +110,12 @@
w_class = WEIGHT_CLASS_SMALL
throw_speed = 3
throw_range = 5
- custom_materials = list(/datum/material/iron= SHEET_MATERIAL_AMOUNT * 5)
+ custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5)
armor_type = /datum/armor/item_hand_tele
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
- var/list/active_portal_pairs
+ ///List of portal pairs created by this hand tele
+ var/list/active_portal_pairs = list()
+ ///Maximum concurrent active portal pairs allowed
var/max_portal_pairs = 3
/**
@@ -130,10 +132,7 @@
fire = 100
acid = 100
-/obj/item/hand_tele/Initialize(mapload)
- . = ..()
- active_portal_pairs = list()
-
+///Checks if the targeted portal was created by us, then causes it to expire, removing it
/obj/item/hand_tele/proc/try_dispel_portal(atom/target, mob/user)
if(is_parent_of_portal(target))
to_chat(user, span_notice("You dispel [target] with [src]!"))
@@ -262,6 +261,9 @@
RegisterSignal(portal2, COMSIG_QDELETING, PROC_REF(on_portal_destroy))
try_move_adjacent(portal1, user.dir)
+ if(QDELETED(portal1) || QDELETED(portal2)) //in the event that something managed to delete the portal objects, i.e. something teleported them
+ to_chat(user, span_notice("[src] vibrates, but no portal seems to appear. Maybe you should try something else."))
+ return
active_portal_pairs[portal1] = portal2
investigate_log("was used by [key_name(user)] at [AREACOORD(user)] to create a portal pair with destinations [AREACOORD(portal1)] and [AREACOORD(portal2)].", INVESTIGATE_PORTAL)
@@ -271,6 +273,9 @@
return TRUE
+///Checks for whether creating a portal in our area is allowed or not,
+///returning FALSE when in a NOTELEPORT area, an away mission or when the user is not on a turf.
+///Is, for some reason, separate from the teleport target's check in try_create_portal_to()
/obj/item/hand_tele/proc/can_teleport_notifies(mob/user)
var/turf/current_location = get_turf(user)
var/area/current_area = current_location.loc
@@ -280,6 +285,7 @@
return TRUE
+///Clears last teleport location when the teleporter providing our target location changes its target
/obj/item/hand_tele/proc/on_teleporter_new_target(datum/source)
SIGNAL_HANDLER
@@ -287,6 +293,7 @@
last_portal_location = null
UnregisterSignal(source, COMSIG_TELEPORTER_NEW_TARGET)
+///Removes a destroyed portal from active_portal_pairs list
/obj/item/hand_tele/proc/on_portal_destroy(obj/effect/portal/P)
SIGNAL_HANDLER
@@ -339,6 +346,8 @@
var/maximum_teleport_distance = 8
//How far the emergency teleport checks for a safe position
var/parallel_teleport_distance = 3
+ // How much blood lost per teleport (out of base 560 blood)
+ var/bleed_amount = 20
/obj/item/syndicate_teleporter/Initialize(mapload)
. = ..()
@@ -427,7 +436,10 @@
charges = max(charges - 1, 0)
new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(current_location)
new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(destination)
- make_bloods(current_location, destination, user)
+ if(make_bloods(current_location, destination, user))
+ new /obj/effect/temp_visual/circle_wave/syndi_teleporter/bloody(destination)
+ else
+ new /obj/effect/temp_visual/circle_wave/syndi_teleporter(destination)
playsound(current_location, SFX_PORTAL_ENTER, 50, 1, SHORT_RANGE_SOUND_EXTRARANGE)
playsound(destination, 'sound/effects/phasein.ogg', 25, 1, SHORT_RANGE_SOUND_EXTRARANGE)
playsound(destination, SFX_PORTAL_ENTER, 50, 1, SHORT_RANGE_SOUND_EXTRARANGE)
@@ -463,11 +475,14 @@
new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(mobloc)
new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(emergency_destination)
balloon_alert(user, "emergency teleport triggered!")
- if (!HAS_TRAIT(user, TRAIT_NOBLOOD))
- make_bloods(mobloc, emergency_destination, user)
+ if(make_bloods(destination, emergency_destination, user))
+ new /obj/effect/temp_visual/circle_wave/syndi_teleporter/bloody(destination)
+ else
+ new /obj/effect/temp_visual/circle_wave/syndi_teleporter(destination)
playsound(mobloc, SFX_PORTAL_ENTER, 50, 1, SHORT_RANGE_SOUND_EXTRARANGE)
playsound(emergency_destination, 'sound/effects/phasein.ogg', 25, 1, SHORT_RANGE_SOUND_EXTRARANGE)
playsound(emergency_destination, SFX_PORTAL_ENTER, 50, 1, SHORT_RANGE_SOUND_EXTRARANGE)
+ playsound(src, 'sound/machines/warning-buzzer.ogg', 25, TRUE)
else //We tried to save. We failed. Death time.
get_fragged(user, destination)
@@ -495,16 +510,45 @@
victim.apply_damage(20, BRUTE)
victim.Paralyze(6 SECONDS)
to_chat(victim, span_warning("[user] teleports into you, knocking you to the floor with the bluespace wave!"))
+ victim.throw_at(get_step_rand(victim), 1, 1, user, spin = TRUE)
///Bleed and make blood splatters at tele start and end points
/obj/item/syndicate_teleporter/proc/make_bloods(turf/old_location, turf/new_location, mob/living/user)
+ if(HAS_TRAIT(user, TRAIT_NOBLOOD))
+ return FALSE
user.add_splatter_floor(old_location)
user.add_splatter_floor(new_location)
if(!iscarbon(user))
- return
+ return FALSE
var/mob/living/carbon/carbon_user = user
- carbon_user.bleed(10)
+ // always lose a bit
+ carbon_user.bleed(bleed_amount * 0.25)
+ // sometimes lose a lot
+ // average evens out to 10 per teleport, but the randomness spices things up
+ if(prob(25) && bleed_amount)
+ playsound(src, 'sound/effects/wounds/pierce1.ogg', 40, vary = TRUE)
+ visible_message(span_warning("Blood visibly spurts out of [user] as [src] fails to teleport [user.p_their()] body properly!"), \
+ span_boldwarning("Blood visibly spurts out of you as [src] fails to teleport your body properly!"))
+ carbon_user.bleed(bleed_amount * 0.75)
+ carbon_user.spray_blood(pick(GLOB.alldirs), rand(1, 3))
+ return TRUE
+
+ return FALSE
+ // retval used for picking wave type
+
+/// Visual effect spawned when teleporting
+/obj/effect/temp_visual/circle_wave/syndi_teleporter
+ duration = 0.25 SECONDS
+ color = COLOR_SYNDIE_RED
+ max_alpha = 100
+ amount_to_scale = 0.8
+
+/obj/effect/temp_visual/circle_wave/syndi_teleporter/bloody
+ duration = 0.25 SECONDS
+ color = COLOR_VIVID_RED
+ max_alpha = 160
+ amount_to_scale = 1
/obj/item/paper/syndicate_teleporter
name = "Teleporter Guide"
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 065f4929e4089..c9270b1f67ef6 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -1360,6 +1360,11 @@
toysay = "EI NATH!"
toysound = 'sound/effects/magic/disintegrate.ogg'
+/obj/item/toy/figure/wizard/special
+ name = "\improper Wizard action figure special edition"
+ toysay = "CLANG!";
+ toysound = 'sound/effects/clang.ogg'
+
/obj/item/toy/figure/rd
name = "\improper Research Director action figure"
icon_state = "rd"
@@ -1562,7 +1567,7 @@ GLOBAL_LIST_EMPTY(intento_players)
#define DISARM "disarm"
#define GRAB "grab"
#define HARM "harm"
-#define ICON_SPLIT world.icon_size/2
+#define ICON_SPLIT ICON_SIZE_ALL/2
// These states do not have any associated processing.
#define STATE_AWAITING_PLAYER_INPUT "awaiting_player_input"
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 7015e403141c4..bddc056b99ef8 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -1200,8 +1200,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
user.do_attack_animation(target, "nothing")
var/list/modifiers = params2list(params)
var/damage_mod = 1
- var/x_slashed = text2num(modifiers[ICON_X]) || world.icon_size/2 //in case we arent called by a client
- var/y_slashed = text2num(modifiers[ICON_Y]) || world.icon_size/2 //in case we arent called by a client
+ var/x_slashed = text2num(modifiers[ICON_X]) || ICON_SIZE_X/2 //in case we arent called by a client
+ var/y_slashed = text2num(modifiers[ICON_Y]) || ICON_SIZE_Y/2 //in case we arent called by a client
new /obj/effect/temp_visual/slash(get_turf(target), target, x_slashed, y_slashed, slash_color)
if(target == previous_target?.resolve()) //if the same target, we calculate a damage multiplier if you swing your mouse around
var/x_mod = previous_x - x_slashed
@@ -1249,7 +1249,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
var/matrix/new_transform = matrix()
new_transform.Turn(rand(1, 360)) // Random slash angle
var/datum/decompose_matrix/decomp = target.transform.decompose()
- new_transform.Translate((x_slashed - world.icon_size/2) * decomp.scale_x, (y_slashed - world.icon_size/2) * decomp.scale_y) // Move to where we clicked
+ new_transform.Translate((x_slashed - ICON_SIZE_X/2) * decomp.scale_x, (y_slashed - ICON_SIZE_Y/2) * decomp.scale_y) // Move to where we clicked
//Follow target's transform while ignoring scaling
new_transform.Turn(decomp.rotation)
new_transform.Translate(decomp.shift_x, decomp.shift_y)
diff --git a/code/game/objects/items/wiki_manuals.dm b/code/game/objects/items/wiki_manuals.dm
index 7209d76cc5735..d64c565f04adf 100644
--- a/code/game/objects/items/wiki_manuals.dm
+++ b/code/game/objects/items/wiki_manuals.dm
@@ -1,31 +1,5 @@
// Wiki books that are linked to the configured wiki link.
-/// The size of the window that the wiki books open in.
-#define BOOK_WINDOW_BROWSE_SIZE "970x710"
-/// This macro will resolve to code that will open up the associated wiki page in the window.
-#define WIKI_PAGE_IFRAME(wikiurl, link_identifier) {"
-
-
-
-
-
-
-
- You start skimming through the manual...
-
-
-
- "}
-
// A book that links to the wiki
/obj/item/book/manual/wiki
starting_content = "Nanotrasen presently does not have any resources on this topic. If you would like to know more, contact your local Central Command representative." // safety
@@ -37,9 +11,10 @@
if(!wiki_url)
user.balloon_alert(user, "this book is empty!")
return
-
credit_book_to_reader(user)
- DIRECT_OUTPUT(user, browse(WIKI_PAGE_IFRAME(wiki_url, page_link), "window=manual;size=[BOOK_WINDOW_BROWSE_SIZE]")) // if you change this GUARANTEE that it works.
+ if(tgui_alert(user, "This book's page will open in your browser. Are you sure?", "Open The Wiki", list("Yes", "No")) != "Yes")
+ return
+ usr << link("[wiki_url]/[page_link]")
/obj/item/book/manual/wiki/chemistry
name = "Chemistry Textbook"
@@ -223,6 +198,3 @@
starting_author = "Nanotrasen Edu-tainment Division"
starting_title = "Tactical Game Cards - Player's Handbook"
page_link = "Tactical_Game_Cards"
-
-#undef BOOK_WINDOW_BROWSE_SIZE
-#undef WIKI_PAGE_IFRAME
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 0a88aade6978f..c51adf91b3b10 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -291,3 +291,28 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag)
pixel_z = anchored_tabletop_offset
else
pixel_z = initial(pixel_z)
+
+/obj/apply_single_mat_effect(datum/material/material, mat_amount, multiplier)
+ . = ..()
+ if(!(material_flags & MATERIAL_AFFECT_STATISTICS))
+ return
+ var/integrity_mod = GET_MATERIAL_MODIFIER(material.integrity_modifier, multiplier)
+ modify_max_integrity(ceil(max_integrity * integrity_mod))
+ var/strength_mod = GET_MATERIAL_MODIFIER(material.strength_modifier, multiplier)
+ force *= strength_mod
+ throwforce *= strength_mod
+ var/list/armor_mods = material.get_armor_modifiers(multiplier)
+ set_armor(get_armor().generate_new_with_multipliers(armor_mods))
+
+///This proc is called when the material is removed from an object specifically.
+/obj/remove_single_mat_effect(datum/material/material, mat_amount, multiplier)
+ . = ..()
+ if(!(material_flags & MATERIAL_AFFECT_STATISTICS))
+ return
+ var/integrity_mod = GET_MATERIAL_MODIFIER(material.integrity_modifier, multiplier)
+ modify_max_integrity(floor(max_integrity / integrity_mod))
+ var/strength_mod = GET_MATERIAL_MODIFIER(material.strength_modifier, multiplier)
+ force /= strength_mod
+ throwforce /= strength_mod
+ var/list/armor_mods = material.get_armor_modifiers(1 / multiplier)
+ set_armor(get_armor().generate_new_with_multipliers(armor_mods))
diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm
index a26a2f9278437..e6f7edd939628 100644
--- a/code/game/objects/structures/bonfire.dm
+++ b/code/game/objects/structures/bonfire.dm
@@ -73,8 +73,8 @@
if(!LAZYACCESS(modifiers, ICON_X) || !LAZYACCESS(modifiers, ICON_Y))
return
//Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf)
- used_item.pixel_x = used_item.base_pixel_x + clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(world.icon_size/2), world.icon_size/2)
- used_item.pixel_y = used_item.base_pixel_y + clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(world.icon_size/2), world.icon_size/2)
+ used_item.pixel_x = used_item.base_pixel_x + clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2)
+ used_item.pixel_y = used_item.base_pixel_y + clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2)
else
return ..()
@@ -185,6 +185,13 @@
/obj/structure/bonfire/dense
density = TRUE
+/obj/structure/bonfire/dense/prelit/Initialize(mapload)
+ . = ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/structure/bonfire/dense/prelit/LateInitialize()
+ start_burning()
+
/obj/structure/bonfire/prelit/Initialize(mapload)
. = ..()
return INITIALIZE_HINT_LATELOAD
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 58e99dc8839aa..7a3fcef368ada 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -134,7 +134,7 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets)
if(isnull(card_reader_choices))
card_reader_choices = list(
"Personal",
- "Departmental",
+ "Job",
"None"
)
if(access_choices)
@@ -800,11 +800,11 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets)
switch(choice)
if("Personal") //only the player who swiped their id has access.
id_card = WEAKREF(id)
- name = "[id.registered_name] locker"
- desc = "now owned by [id.registered_name]. [initial(desc)]"
- if("Departmental") //anyone who has the same access permissions as this id has access
- name = "[id.assignment] closet"
- desc = "Its a [id.assignment] closet. [initial(desc)]"
+ name = "[id.registered_name]'s locker"
+ desc += " It has been ID locked to [id.registered_name]."
+ if("Job") //anyone who has the same access permissions as this id has access. Does NOT apply to the whole department.
+ name = "[id.assignment]'s locker"
+ desc += " It has been access locked to [id.assignment]s."
set_access(id.GetAccess())
if("None") //free for all
name = initial(name)
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index f01fe56431d94..f7a0ccd66dfdd 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -305,22 +305,16 @@
base_icon_state = "food"
/obj/structure/closet/crate/freezer/donk
- name = "donk co. fridge"
- desc = "A Donk Co. brand fridge, keeps your donkpcokets and foam ammunition fresh!"
+ name = "\improper Donk Co. fridge"
+ desc = "A Donk Co. brand fridge, keeps your donkpockets and foam ammunition fresh!"
icon_state = "donkcocrate"
base_icon_state = "donkcocrate"
-/obj/structure/closet/crate/freezer/interdyne
- name = "interdyne freezer"
- desc = "Interdyne Pharmauceutics branded freezer. Might or might not contain cold steel, or fresh organs."
- icon_state = "interdynefreezer"
- base_icon_state = "interdynefreezer"
-
-/obj/structure/closet/crate/freezer/blood/interdyne
- name = "interdyne blood freezer"
- desc = "Interdyne Pharmauceutics branded freezer. Only freshly harvested- I mean, freshly kept blood inside!"
- icon_state = "interdynefreezer"
- base_icon_state = "interdynefreezer"
+/obj/structure/closet/crate/self
+ name = "\improper S.E.L.F. crate"
+ desc = "A robust-looking crate with a seemingly decorative holographic display. The front of the crate proudly declares its allegiance to the notorious terrorist group 'S.E.L.F'."
+ icon_state = "selfcrate"
+ base_icon_state = "selfcrate"
/obj/structure/closet/crate/radiation
desc = "A crate with a radiation sign on it."
diff --git a/code/game/objects/structures/crates_lockers/crates/secure.dm b/code/game/objects/structures/crates_lockers/crates/secure.dm
index e93591f1d596c..b0b6cfaae0016 100644
--- a/code/game/objects/structures/crates_lockers/crates/secure.dm
+++ b/code/game/objects/structures/crates_lockers/crates/secure.dm
@@ -44,17 +44,6 @@
icon_state = "weaponcrate"
base_icon_state = "weaponcrate"
-/obj/structure/closet/crate/secure/gorlex_weapons
- desc = "A secure weapons crate of Gorlex Marauders."
- name = "weapons crate"
- icon_state = "gorlex_weaponcrate"
- base_icon_state = "gorlex_weaponcrate"
-
-/obj/structure/closet/crate/secure/gorlex_weapons/jammed
- desc = "A beaten up, jammed open weapon crate of Gorlex Marauders."
- name = "jammed weapons crate"
- locked = FALSE
-
/obj/structure/closet/crate/secure/plasma
desc = "A secure plasma crate."
name = "plasma crate"
@@ -204,64 +193,110 @@
to_chat(user, span_warning("[src] is broken!"))
else ..()
-/obj/structure/closet/crate/secure/interdyne
- name = "interdyne crate"
+/obj/structure/closet/crate/secure/freezer/interdyne
+ name = "\improper Interdyne freezer"
+ desc = "This is an Interdyne Pharmauceutics branded freezer. May or may not contain fresh organs."
+ icon_state = "interdynefreezer"
+ base_icon_state = "interdynefreezer"
+ req_access = list(ACCESS_SYNDICATE)
+
+/obj/structure/closet/crate/secure/freezer/interdyne/blood
+ name = "\improper Interdyne blood freezer"
+ desc = "This is an Interdyne Pharmauceutics branded freezer. It's made to contain fresh, high-quality blood."
+
+/obj/structure/closet/crate/secure/freezer/interdyne/blood/PopulateContents()
+ . = ..()
+ for(var/i in 1 to 13)
+ new /obj/item/reagent_containers/blood/random(src)
+
+/obj/structure/closet/crate/secure/freezer/donk
+ name = "\improper Donk Co. fridge"
+ desc = "A Donk Co. brand fridge, keeps your donkpockets and foam ammunition fresh!"
+ icon_state = "donkcocrate_secure"
+ base_icon_state = "donkcocrate_secure"
+ req_access = list(ACCESS_SYNDICATE)
+
+/obj/structure/closet/crate/secure/syndicate
+ name = "\improper Syndicate crate"
+ desc = "A secure crate with the Syndicate's branding on it."
+ icon_state = "syndicrate"
+ base_icon_state = "syndicrate"
+ req_access = list(ACCESS_SYNDICATE)
+
+/obj/structure/closet/crate/secure/syndicate/interdyne
+ name = "\improper Interdyne crate"
desc = "Crate belonging to Interdyne Pharmaceutics. Hopefully doesn't have bioweapons inside..."
icon_state = "interdynecrate"
base_icon_state = "interdynecrate"
-/obj/structure/closet/crate/secure/tiger
- name = "tiger co-op crate"
+/obj/structure/closet/crate/secure/syndicate/tiger
+ name = "\improper Tiger Co-Op crate"
icon_state = "tigercrate"
base_icon_state = "tigercrate"
-/obj/structure/closet/crate/secure/self
- name = "s.e.l.f. crate"
+/obj/structure/closet/crate/secure/syndicate/self
+ name = "\improper S.E.L.F. crate"
desc = "A secure crate locked from the inside with a scanning panel above it and holographic display of lock's status. Sentient Engine Liberation Front engineers are quite the show-offs."
- icon_state = "selfcrate"
- base_icon_state = "selfcrate"
+ icon_state = "selfcrate_secure"
+ base_icon_state = "selfcrate_secure"
-/obj/structure/closet/crate/secure/m13
+/obj/structure/closet/crate/secure/syndicate/mi13
name = "mysterious secure crate"
desc = "A secure crate. Lacks any obvious logos or even codes for where it arrived from, but looks like taken straight from a spy movie."
icon_state = "mithirteencrate"
base_icon_state = "mithirteencrate"
+ open_sound_volume = 15
+ close_sound_volume = 20
-/obj/structure/closet/crate/secure/arc
- name = "animal rights consortium crate"
+/obj/structure/closet/crate/secure/syndicate/arc
+ name = "\improper Animal Rights Consortium crate"
icon_state = "arccrate"
base_icon_state = "arccrate"
-/obj/structure/closet/crate/secure/cybersun
- name = "cybersun crate"
+/obj/structure/closet/crate/secure/syndicate/cybersun
+ name = "\improper Cybersun crate"
-/obj/structure/closet/crate/secure/cybersun/dawn
+/obj/structure/closet/crate/secure/syndicate/cybersun/dawn
desc = "A secure crate from Cybersun Industries. It has distinct orange-green colouring, probably of some departament or division, but you cannot tell what is it."
icon_state = "cyber_dawncrate"
base_icon_state = "cyber_dawncrate"
-/obj/structure/closet/crate/secure/cybersun/noon
+/obj/structure/closet/crate/secure/syndicate/cybersun/noon
desc = "A secure crate from Cybersun Industries. It has distinct yellow-orange colouring, probably of some departament or division, but you cannot tell what is it."
icon_state = "cyber_nooncrate"
base_icon_state = "cyber_nooncrate"
-/obj/structure/closet/crate/secure/cybersun/dusk
+/obj/structure/closet/crate/secure/syndicate/cybersun/dusk
desc = "A secure crate from Cybersun Industries. It has distinct purple-green colouring, probably of some departament or division, but you cannot tell what is it."
icon_state = "cyber_duskcrate"
base_icon_state = "cyber_duskcrate"
-/obj/structure/closet/crate/secure/cybersun/night
+/obj/structure/closet/crate/secure/syndicate/cybersun/night
desc = "A secure crate from Cybersun Industries. This one blatantly adorns syndicate colours. You can only guess it contains equipement for syndicate operatives."
icon_state = "cyber_nightcrate"
base_icon_state = "cyber_nightcrate"
-/obj/structure/closet/crate/secure/wafflecorp
- name = "wafflecorp crate"
+/obj/structure/closet/crate/secure/syndicate/wafflecorp
+ name = "\improper Waffle corp. crate"
desc = "A very outdated model and design of shipment crate with a modern lock strapped on it, how befitting of its brand owner, Waffle Corporation. Golden lettering written in cursive by the logo reads 'bringing you consecutively top five world-wide rated* breakfast since 2055. A much smaller fineprint, also in cursive, clarifies: '*in years 2099-2126'... It's year 2563 now, however."
icon_state = "wafflecrate"
base_icon_state = "wafflecrate"
-/obj/structure/closet/crate/secure/gorlex
- name = "gorlex marauders crate"
+/obj/structure/closet/crate/secure/syndicate/gorlex
+ name = "\improper Gorlex Marauders crate"
icon_state = "gorlexcrate"
base_icon_state = "gorlexcrate"
+
+/obj/structure/closet/crate/secure/syndicate/gorlex/weapons
+ desc = "A secure weapons crate of Gorlex Marauders."
+ name = "weapons crate"
+ icon_state = "gorlex_weaponcrate"
+ base_icon_state = "gorlex_weaponcrate"
+
+/obj/structure/closet/crate/secure/syndicate/gorlex/weapons/bustedlock
+ desc = "A beaten up weapon crate with Gorlex Marauders branding. Its lock looks broken."
+ name = "damaged weapons crate"
+ secure = FALSE
+ locked = FALSE
+ max_integrity = 400
+ damage_deflection = 15
diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm
index a4d35f02e0c09..5c2f1972c33ef 100644
--- a/code/game/objects/structures/false_walls.dm
+++ b/code/game/objects/structures/false_walls.dm
@@ -392,8 +392,9 @@
var/datum/material/material_datum = material
new material_datum.sheet_type(loc, FLOOR(custom_materials[material_datum] / SHEET_MATERIAL_AMOUNT, 1))
-/obj/structure/falsewall/material/mat_update_desc(mat)
- desc = "A huge chunk of [mat] used to separate rooms."
+/obj/structure/falsewall/material/finalize_material_effects(list/materials)
+ . = ..()
+ desc = "A huge chunk of [get_material_english_list(materials)] used to separate rooms."
/obj/structure/falsewall/material/toggle_open()
if(!QDELETED(src))
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index a464f41827678..f5e63bcf23bba 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -452,6 +452,10 @@
desc = "A wondrous decorated Christmas tree."
icon_state = "pine_c"
+/obj/structure/flora/tree/pine/xmas/presentless
+ icon_state = "pinepresents"
+ desc = "A wondrous decorated Christmas tree. It has presents, though none of them seem to have your name on them."
+
/obj/structure/flora/tree/pine/xmas/presents
icon_state = "pinepresents"
desc = "A wondrous decorated Christmas tree. It has presents!"
diff --git a/code/game/objects/structures/plaques/static_plaques.dm b/code/game/objects/structures/plaques/static_plaques.dm
index 3ae3b66b71b66..4b53ae0437301 100644
--- a/code/game/objects/structures/plaques/static_plaques.dm
+++ b/code/game/objects/structures/plaques/static_plaques.dm
@@ -3,6 +3,12 @@
/obj/structure/plaque/static_plaque
engraved = TRUE
+/obj/structure/plaque/static_plaque/Initialize(mapload)
+ . = ..()
+ if(isopenturf(loc) && !isProbablyWallMounted(src))
+ SET_PLANE_IMPLICIT(src, FLOOR_PLANE)
+ layer = HIGH_TURF_LAYER
+
/obj/structure/plaque/static_plaque/atmos
name = "\improper FEA Atmospherics Division plaque"
desc = "This plaque commemorates the fall of the Atmos FEA division. For all the charred, dizzy, and brittle men who have died in its hands."
diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm
index fe2e4b06b4f10..af213603f6353 100644
--- a/code/game/objects/structures/railings.dm
+++ b/code/game/objects/structures/railings.dm
@@ -101,6 +101,10 @@
/obj/structure/railing/wirecutter_act(mob/living/user, obj/item/I)
. = ..()
+ if(resistance_flags & INDESTRUCTIBLE)
+ to_chat(user, span_warning("You try to cut apart the railing, but it's too hard!"))
+ I.play_tool_sound(src, 100)
+ return TRUE
to_chat(user, span_warning("You cut apart the railing."))
I.play_tool_sound(src, 100)
deconstruct()
diff --git a/code/game/objects/structures/shower.dm b/code/game/objects/structures/shower.dm
index 1ae66f2f82f19..6c5435c1bfad8 100644
--- a/code/game/objects/structures/shower.dm
+++ b/code/game/objects/structures/shower.dm
@@ -90,6 +90,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/shower, (-16))
AddComponent(/datum/component/plumbing/simple_demand, extend_pipe_to_edge = TRUE)
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)
@@ -233,18 +234,33 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/shower, (-16))
qdel(mist)
-/obj/machinery/shower/proc/on_entered(datum/source, atom/movable/AM)
+/obj/machinery/shower/proc/on_entered(datum/source, atom/movable/enterer)
SIGNAL_HANDLER
+
if(actually_on && reagents.total_volume)
- wash_atom(AM)
+ wash_atom(enterer)
+
+/obj/machinery/shower/proc/on_exited(datum/source, atom/movable/exiter)
+ SIGNAL_HANDLER
+
+ if(!isliving(exiter))
+ return
+
+ var/obj/machinery/shower/locate_new_shower = locate() in get_turf(exiter)
+ if(locate_new_shower && isturf(exiter.loc))
+ return
+ var/mob/living/take_his_status_effect = exiter
+ take_his_status_effect.remove_status_effect(/datum/status_effect/shower_regen)
/obj/machinery/shower/proc/wash_atom(atom/target)
target.wash(CLEAN_RAD | CLEAN_WASH)
reagents.expose(target, (TOUCH), SHOWER_EXPOSURE_MULTIPLIER * SHOWER_SPRAY_VOLUME / max(reagents.total_volume, SHOWER_SPRAY_VOLUME))
- if(isliving(target))
- var/mob/living/living_target = target
- check_heat(living_target)
- living_target.add_mood_event("shower", /datum/mood_event/nice_shower)
+ if(!isliving(target))
+ return
+ var/mob/living/living_target = target
+ check_heat(living_target)
+ living_target.add_mood_event("shower", /datum/mood_event/nice_shower)
+ living_target.apply_status_effect(/datum/status_effect/shower_regen)
/**
* Toggle whether shower is actually on and outputting water.
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 834347cc84f4f..ac7282f9386b6 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -309,8 +309,8 @@
// Items are centered by default, but we move them if click ICON_X and ICON_Y are available
if(LAZYACCESS(modifiers, ICON_X) && LAZYACCESS(modifiers, ICON_Y))
// Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf)
- tool.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(world.icon_size*0.5), world.icon_size*0.5)
- tool.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(world.icon_size*0.5), world.icon_size*0.5)
+ tool.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X*0.5), ICON_SIZE_X*0.5)
+ tool.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y*0.5), ICON_SIZE_Y*0.5)
AfterPutItemOnTable(tool, user)
return ITEM_INTERACT_SUCCESS
@@ -361,13 +361,10 @@
material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
buildstack = null //No buildstack, so generate from mat datums
-/obj/structure/table/greyscale/set_custom_materials(list/materials, multiplier)
+/obj/structure/table/greyscale/finalize_material_effects(list/materials)
. = ..()
- var/list/materials_list = list()
- for(var/custom_material in custom_materials)
- var/datum/material/current_material = GET_MATERIAL_REF(custom_material)
- materials_list += "[current_material.name]"
- desc = "A square [(materials_list.len > 1) ? "amalgamation" : "piece"] of [english_list(materials_list)] on four legs. It can not move."
+ var/english_list = get_material_english_list(materials)
+ desc = "A square [(length(materials) > 1) ? "amalgamation" : "piece"] of [english_list] on four legs. It can not move."
///Table on wheels
/obj/structure/table/rolling
@@ -664,6 +661,9 @@
/obj/structure/table/reinforced/welder_act_secondary(mob/living/user, obj/item/tool)
if(tool.tool_start_check(user, amount = 0))
+ if(attempt_electrocution(user))
+ return ITEM_INTERACT_BLOCKING
+
if(deconstruction_ready)
to_chat(user, span_notice("You start strengthening the reinforced table..."))
if (tool.use_tool(src, user, 50, volume = 50))
@@ -684,6 +684,40 @@
return ..()
+/obj/structure/table/reinforced/screwdriver_act_secondary(mob/living/user, obj/item/tool)
+ if(deconstruction_ready && attempt_electrocution(user))
+ return ITEM_INTERACT_BLOCKING
+ return ..()
+
+/obj/structure/table/reinforced/wrench_act_secondary(mob/living/user, obj/item/tool)
+ if(deconstruction_ready && attempt_electrocution(user))
+ return ITEM_INTERACT_BLOCKING
+ return ..()
+
+/// Attempts to shock the user, given the table is hooked up and they're within range.
+/// Returns TRUE on successful electrocution, FALSE otherwise.
+/obj/structure/table/reinforced/proc/attempt_electrocution(mob/user)
+ if(!anchored) // If for whatever reason it's not anchored, it can't be shocked either.
+ return FALSE
+ if(!in_range(src, user)) // To prevent TK and mech users from getting shocked.
+ return FALSE
+
+ var/turf/our_turf = get_turf(src)
+ if(our_turf.overfloor_placed) // Can't have a floor in the way.
+ return FALSE
+
+ var/obj/structure/cable/cable_node = our_turf.get_cable_node()
+ if(isnull(cable_node))
+ return FALSE
+ if(!electrocute_mob(user, cable_node, src, 1, TRUE))
+ return FALSE
+
+ var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread
+ sparks.set_up(3, TRUE, src)
+ sparks.start()
+
+ return TRUE
+
/obj/structure/table/bronze
name = "bronze table"
desc = "A solid table made out of bronze."
diff --git a/code/game/sound.dm b/code/game/sound.dm
index fb9d70eb2cb03..d3553b1d6f926 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -43,6 +43,9 @@
if(isarea(source))
CRASH("playsound(): source is an area")
+ if(islist(soundin))
+ CRASH("playsound(): soundin attempted to pass a list! Consider using pick()")
+
var/turf/turf_source = get_turf(source)
if (!turf_source || !soundin || !vol)
@@ -226,7 +229,10 @@
if(SFX_CLOWN_STEP)
soundin = pick('sound/effects/footstep/clownstep1.ogg','sound/effects/footstep/clownstep2.ogg')
if(SFX_SUIT_STEP)
- soundin = pick('sound/effects/suitstep1.ogg','sound/effects/suitstep2.ogg')
+ soundin = pick(
+ 'sound/items/handling/armor_rustle/riot_armor/suitstep1.ogg',
+ 'sound/items/handling/armor_rustle/riot_armor/suitstep2.ogg',
+ )
if(SFX_SWING_HIT)
soundin = pick('sound/items/weapons/genhit1.ogg', 'sound/items/weapons/genhit2.ogg', 'sound/items/weapons/genhit3.ogg')
if(SFX_HISS)
@@ -523,4 +529,60 @@
'sound/effects/liquid_pour/liquid_pour2.ogg',
'sound/effects/liquid_pour/liquid_pour3.ogg',
)
+ if(SFX_SNORE_FEMALE)
+ soundin = pick_weight(list(
+ 'sound/mobs/humanoids/human/snore/snore_female1.ogg' = 33,
+ 'sound/mobs/humanoids/human/snore/snore_female2.ogg' = 33,
+ 'sound/mobs/humanoids/human/snore/snore_female3.ogg' = 33,
+ 'sound/mobs/humanoids/human/snore/snore_mimimi1.ogg' = 1,
+ ))
+ if(SFX_SNORE_MALE)
+ soundin = pick_weight(list(
+ 'sound/mobs/humanoids/human/snore/snore_male1.ogg' = 20,
+ 'sound/mobs/humanoids/human/snore/snore_male2.ogg' = 20,
+ 'sound/mobs/humanoids/human/snore/snore_male3.ogg' = 20,
+ 'sound/mobs/humanoids/human/snore/snore_male4.ogg' = 20,
+ 'sound/mobs/humanoids/human/snore/snore_male5.ogg' = 20,
+ 'sound/mobs/humanoids/human/snore/snore_mimimi2.ogg' = 1,
+ ))
+ if(SFX_CAT_MEOW)
+ soundin = pick_weight(list(
+ 'sound/creatures/cat/cat_meow1.ogg' = 33,
+ 'sound/creatures/cat/cat_meow2.ogg' = 33,
+ 'sound/creatures/cat/cat_meow3.ogg' = 33,
+ 'sound/creatures/cat/oranges_meow1.ogg' = 1,
+ ))
+ if(SFX_CAT_PURR)
+ soundin = pick(
+ 'sound/creatures/cat/cat_purr1.ogg',
+ 'sound/creatures/cat/cat_purr2.ogg',
+ 'sound/creatures/cat/cat_purr3.ogg',
+ 'sound/creatures/cat/cat_purr4.ogg',
+ )
+ if(SFX_DEFAULT_LIQUID_SLOSH)
+ soundin = pick(
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh1.ogg',
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh2.ogg',
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh3.ogg',
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh4.ogg',
+ 'sound/items/handling/reagent_containers/default/default_liquid_slosh5.ogg',
+ )
+ if(SFX_PLASTIC_BOTTLE_LIQUID_SLOSH)
+ soundin = pick(
+ 'sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh1.ogg',
+ 'sound/items/handling/reagent_containers/plastic_bottle/plastic_bottle_liquid_slosh2.ogg',
+ )
+ if(SFX_PLATE_ARMOR_RUSTLE)
+ soundin = pick_weight(list(
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle1.ogg' = 8, //longest sound is rarer.
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle2.ogg' = 23,
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle3.ogg' = 23,
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle4.ogg' = 23,
+ 'sound/items/handling/armor_rustle/plate_armor/plate_armor_rustle5.ogg' = 23,
+ ))
+ if(SFX_PIG_OINK)
+ soundin = pick(
+ 'sound/mobs/non-humanoids/pig/pig1.ogg',
+ 'sound/mobs/non-humanoids/pig/pig2.ogg',
+ )
return soundin
diff --git a/code/game/turfs/closed/wall/material_walls.dm b/code/game/turfs/closed/wall/material_walls.dm
index 5f16a68584f3e..f8f9d58c220fb 100644
--- a/code/game/turfs/closed/wall/material_walls.dm
+++ b/code/game/turfs/closed/wall/material_walls.dm
@@ -22,6 +22,7 @@
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."
+/turf/closed/wall/material/finalize_material_effects(list/materials)
+ . = ..()
+ desc = "A huge chunk of [get_material_english_list(materials)] used to separate rooms."
diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm
index b62de34f14690..c2b9963c5ed41 100644
--- a/code/game/turfs/open/_open.dm
+++ b/code/game/turfs/open/_open.dm
@@ -486,6 +486,15 @@
playsound(src, 'sound/items/weapons/genhit.ogg', 50, TRUE)
new used_tiles.tile_type(src)
+/turf/open/apply_main_material_effects(datum/material/main_material, amount, multipier)
+ . = ..()
+ if(!main_material.turf_sound_override)
+ return
+ footstep = main_material.turf_sound_override
+ barefootstep = main_material.turf_sound_override + "barefoot"
+ clawfootstep = main_material.turf_sound_override + "claw"
+ heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+
/// Very similar to build_with_rods, this exists to allow building transport/tram girders on openspace
/turf/open/proc/build_with_titanium(obj/item/stack/sheet/mineral/titanium/used_stack, user)
var/obj/structure/transport/linear/platform = locate(/obj/structure/transport/linear, src)
diff --git a/code/game/turfs/open/chasm.dm b/code/game/turfs/open/chasm.dm
index f81ac4c7f536e..2699b4933626d 100644
--- a/code/game/turfs/open/chasm.dm
+++ b/code/game/turfs/open/chasm.dm
@@ -50,8 +50,8 @@
/turf/open/chasm/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/floors.dmi'
- underlay_appearance.icon_state = "basalt"
+ underlay_appearance.icon = /turf/open/misc/asteroid/basalt::icon
+ underlay_appearance.icon_state = /turf/open/misc/asteroid/basalt::icon_state
return TRUE
/turf/open/chasm/attackby(obj/item/C, mob/user, params, area/area_restriction)
@@ -100,6 +100,11 @@
light_power = 0.65
light_color = LIGHT_COLOR_PURPLE
+/turf/open/chasm/icemoon/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
+ underlay_appearance.icon = /turf/open/misc/asteroid/snow/icemoon::icon
+ underlay_appearance.icon_state = /turf/open/misc/asteroid/snow/icemoon::icon_state
+ return TRUE
+
// Chasms for the jungle, with planetary atmos and a different icon
/turf/open/chasm/jungle
icon = 'icons/turf/floors/junglechasm.dmi'
@@ -109,8 +114,8 @@
baseturfs = /turf/open/chasm/jungle
/turf/open/chasm/jungle/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/floors.dmi'
- underlay_appearance.icon_state = "dirt"
+ underlay_appearance.icon = /turf/open/misc/dirt::icon
+ underlay_appearance.icon_state = /turf/open/misc/dirt::icon_state
return TRUE
// Chasm that doesn't do any z-level nonsense and just kills/stores whoever steps into it.
diff --git a/code/game/turfs/open/floor.dm b/code/game/turfs/open/floor.dm
index dad46fd8de6b6..c3ea369404331 100644
--- a/code/game/turfs/open/floor.dm
+++ b/code/game/turfs/open/floor.dm
@@ -382,4 +382,4 @@
. = ..()
if(.)
var/obj/item/stack/tile = .
- tile.set_mats_per_unit(custom_materials, 1)
+ tile.set_custom_materials(custom_materials)
diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm
index eebb74b72897b..1a9f24ce50ebd 100644
--- a/code/game/turfs/open/lava.dm
+++ b/code/game/turfs/open/lava.dm
@@ -178,11 +178,6 @@
/turf/open/lava/singularity_pull(S, current_size)
return
-/turf/open/lava/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/floors.dmi'
- underlay_appearance.icon_state = "basalt"
- return TRUE
-
/turf/open/lava/GetHeatCapacity()
. = 700000
@@ -341,6 +336,12 @@
underfloor_accessibility = 2 //This avoids strangeness when routing pipes / wires along catwalks over lava
immerse_overlay_color = "#F98511"
+/// Smooth lava needs to take after basalt in order to blend better. If you make a /turf/open/lava/smooth subtype for an area NOT surrounded by basalt; you should override this proc.
+/turf/open/lava/smooth/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
+ underlay_appearance.icon = /turf/open/misc/asteroid/basalt::icon
+ underlay_appearance.icon_state = /turf/open/misc/asteroid/basalt::icon_state
+ return TRUE
+
/turf/open/lava/smooth/lava_land_surface
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
planetary_atmos = TRUE
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 38976bcfdd6df..5b39caf571faa 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -270,7 +270,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
* * type_list - are we checking for types of atoms to ignore and not physical atoms
*/
/turf/proc/is_blocked_turf(exclude_mobs = FALSE, source_atom = null, list/ignore_atoms, type_list = FALSE)
- if(density)
+ if((!isnull(source_atom) && !CanPass(source_atom, get_dir(src, source_atom))) || density)
return TRUE
for(var/atom/movable/movable_content as anything in contents)
@@ -770,6 +770,23 @@ GLOBAL_LIST_EMPTY(station_turfs)
inherent_explosive_resistance = explosion_block
explosive_resistance += get_explosive_block()
+/turf/apply_main_material_effects(datum/material/main_material, amount, multipier)
+ . = ..()
+ if(alpha < 255)
+ AddElement(/datum/element/turf_z_transparency)
+ main_material.setup_glow(src)
+ rust_resistance = main_material.mat_rust_resistance
+
+/turf/remove_main_material_effects(datum/material/custom_material, amount, multipier)
+ . = ..()
+ rust_resistance = initial(rust_resistance)
+ if(alpha == 255)
+ return
+ RemoveElement(/datum/element/turf_z_transparency)
+ // yeets glow
+ UnregisterSignal(SSdcs, COMSIG_STARLIGHT_COLOR_CHANGED)
+ set_light(0, 0, null)
+
/// Returns whether it is safe for an atom to move across this turf
/turf/proc/can_cross_safely(atom/movable/crossing)
return TRUE
diff --git a/code/game/world.dm b/code/game/world.dm
index 765aa7c18ef5d..184b13be6427f 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -399,10 +399,10 @@ GLOBAL_VAR(tracy_log)
new_status += " | Shuttle: [SSshuttle.emergency.getModeStr()] [SSshuttle.emergency.getTimerStr()]"
else if(SSticker.current_state == GAME_STATE_FINISHED)
new_status += "
RESTARTING"
- if(SSmapping.config)
- new_status += "
Map: [SSmapping.config.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmapping.config.map_name]"
- if(SSmapping.next_map_config)
- new_status += "[SSmapping.config ? " | " : "
"]Next: [SSmapping.next_map_config.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmapping.next_map_config.map_name]"
+ if(SSmapping.current_map)
+ new_status += "
Map: [SSmapping.current_map.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmapping.current_map.map_name]"
+ if(SSmap_vote.next_map_config)
+ new_status += "[SSmapping.current_map ? " | " : "
"]Next: [SSmap_vote.next_map_config.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmap_vote.next_map_config.map_name]"
status = new_status
diff --git a/code/modules/actionspeed/modifiers/mobs.dm b/code/modules/actionspeed/modifiers/mobs.dm
new file mode 100644
index 0000000000000..adc1a1d120750
--- /dev/null
+++ b/code/modules/actionspeed/modifiers/mobs.dm
@@ -0,0 +1,3 @@
+///speed bonus given by the fish tail organ when inside water.
+/datum/actionspeed_modifier/fish_on_water
+ multiplicative_slowdown = -0.15
diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm
index b3c61e8e787f2..649ae3e3a34f8 100644
--- a/code/modules/admin/verbs/adminevents.dm
+++ b/code/modules/admin/verbs/adminevents.dm
@@ -121,103 +121,6 @@ ADMIN_VERB(cmd_admin_add_freeform_ai_law, R_ADMIN, "Add Custom AI Law", "Add a c
BLACKBOX_LOG_ADMIN_VERB("Add Custom AI Law")
-ADMIN_VERB(call_shuttle, R_ADMIN, "Call Shuttle", "Force a shuttle call with additional modifiers.", ADMIN_CATEGORY_EVENTS)
- if(EMERGENCY_AT_LEAST_DOCKED)
- return
-
- var/confirm = tgui_alert(user, "You sure?", "Confirm", list("Yes", "Yes (No Recall)", "No"))
- switch(confirm)
- if(null, "No")
- return
- if("Yes (No Recall)")
- SSshuttle.admin_emergency_no_recall = TRUE
- SSshuttle.emergency.mode = SHUTTLE_IDLE
-
- SSshuttle.emergency.request()
- BLACKBOX_LOG_ADMIN_VERB("Call Shuttle")
- log_admin("[key_name(user)] admin-called the emergency shuttle.")
- message_admins(span_adminnotice("[key_name_admin(user)] admin-called the emergency shuttle[confirm == "Yes (No Recall)" ? " (non-recallable)" : ""]."))
-
-ADMIN_VERB(cancel_shuttle, R_ADMIN, "Cancel Shuttle", "Recall the shuttle, regardless of circumstances.", ADMIN_CATEGORY_EVENTS)
- if(EMERGENCY_AT_LEAST_DOCKED)
- return
-
- if(tgui_alert(user, "You sure?", "Confirm", list("Yes", "No")) != "Yes")
- return
- SSshuttle.admin_emergency_no_recall = FALSE
- SSshuttle.emergency.cancel()
- BLACKBOX_LOG_ADMIN_VERB("Cancel Shuttle")
- log_admin("[key_name(user)] admin-recalled the emergency shuttle.")
- message_admins(span_adminnotice("[key_name_admin(user)] admin-recalled the emergency shuttle."))
-
-ADMIN_VERB(disable_shuttle, R_ADMIN, "Disable Shuttle", "Those fuckers aren't getting out.", ADMIN_CATEGORY_EVENTS)
- if(SSshuttle.emergency.mode == SHUTTLE_DISABLED)
- to_chat(user, span_warning("Error, shuttle is already disabled."))
- return
-
- if(tgui_alert(user, "You sure?", "Confirm", list("Yes", "No")) != "Yes")
- return
-
- message_admins(span_adminnotice("[key_name_admin(user)] disabled the shuttle."))
-
- SSshuttle.last_mode = SSshuttle.emergency.mode
- SSshuttle.last_call_time = SSshuttle.emergency.timeLeft(1)
- SSshuttle.admin_emergency_no_recall = TRUE
- SSshuttle.emergency.setTimer(0)
- SSshuttle.emergency.mode = SHUTTLE_DISABLED
- priority_announce(
- text = "Emergency Shuttle uplink failure, shuttle disabled until further notice.",
- title = "Uplink Failure",
- sound = 'sound/announcer/announcement/announce_dig.ogg',
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "grey",
- )
-
-ADMIN_VERB(enable_shuttle, R_ADMIN, "Enable Shuttle", "Those fuckers ARE getting out.", ADMIN_CATEGORY_EVENTS)
- if(SSshuttle.emergency.mode != SHUTTLE_DISABLED)
- to_chat(user, span_warning("Error, shuttle not disabled."))
- return
-
- if(tgui_alert(user, "You sure?", "Confirm", list("Yes", "No")) != "Yes")
- return
-
- message_admins(span_adminnotice("[key_name_admin(user)] enabled the emergency shuttle."))
- SSshuttle.admin_emergency_no_recall = FALSE
- SSshuttle.emergency_no_recall = FALSE
- if(SSshuttle.last_mode == SHUTTLE_DISABLED) //If everything goes to shit, fix it.
- SSshuttle.last_mode = SHUTTLE_IDLE
-
- SSshuttle.emergency.mode = SSshuttle.last_mode
- if(SSshuttle.last_call_time < 10 SECONDS && SSshuttle.last_mode != SHUTTLE_IDLE)
- SSshuttle.last_call_time = 10 SECONDS //Make sure no insta departures.
- SSshuttle.emergency.setTimer(SSshuttle.last_call_time)
- priority_announce(
- text = "Emergency Shuttle uplink reestablished, shuttle enabled.",
- title = "Uplink Restored",
- sound = 'sound/announcer/announcement/announce_dig.ogg',
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "green",
- )
-
-ADMIN_VERB(hostile_environment, R_ADMIN, "Hostile Environment", "Disable the shuttle, naturally.", ADMIN_CATEGORY_EVENTS)
- switch(tgui_alert(user, "Select an Option", "Hostile Environment Manager", list("Enable", "Disable", "Clear All")))
- if("Enable")
- if (SSshuttle.hostile_environments["Admin"] == TRUE)
- to_chat(user, span_warning("Error, admin hostile environment already enabled."))
- else
- message_admins(span_adminnotice("[key_name_admin(user)] Enabled an admin hostile environment"))
- SSshuttle.registerHostileEnvironment("Admin")
- if("Disable")
- if (!SSshuttle.hostile_environments["Admin"])
- to_chat(user, span_warning("Error, no admin hostile environment found."))
- else
- message_admins(span_adminnotice("[key_name_admin(user)] Disabled the admin hostile environment"))
- SSshuttle.clearHostileEnvironment("Admin")
- if("Clear All")
- message_admins(span_adminnotice("[key_name_admin(user)] Disabled all current hostile environment sources"))
- SSshuttle.hostile_environments.Cut()
- SSshuttle.checkHostileEnvironment()
-
ADMIN_VERB(toggle_nuke, R_DEBUG|R_ADMIN, "Toggle Nuke", "Arm or disarm a nuke.", ADMIN_CATEGORY_EVENTS)
var/list/nukes = list()
for (var/obj/machinery/nuclearbomb/bomb in world)
diff --git a/code/modules/admin/verbs/adminshuttle.dm b/code/modules/admin/verbs/adminshuttle.dm
new file mode 100644
index 0000000000000..6f2a0d42b190c
--- /dev/null
+++ b/code/modules/admin/verbs/adminshuttle.dm
@@ -0,0 +1,196 @@
+ADMIN_VERB(change_shuttle_events, R_ADMIN|R_FUN, "Change Shuttle Events", "Change the events on a shuttle.", ADMIN_CATEGORY_SHUTTLE)
+ //At least for now, just letting admins modify the emergency shuttle is fine
+ var/obj/docking_port/mobile/port = SSshuttle.emergency
+
+ if(!port)
+ to_chat(user, span_admin("Uh oh, couldn't find the escape shuttle!"))
+
+ var/list/options = list("Clear"="Clear")
+
+ //Grab the active events so we know which ones we can Add or Remove
+ var/list/active = list()
+ for(var/datum/shuttle_event/event in port.event_list)
+ active[event.type] = event
+
+ for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event))
+ options[((event in active) ? "(Remove)" : "(Add)") + initial(event.name)] = event
+
+ //Throw up an ugly menu with the shuttle events and the options to add or remove them, or clear them all
+ var/result = input(user, "Choose an event to add/remove", "Shuttle Events") as null|anything in sort_list(options)
+
+ if(result == "Clear")
+ port.event_list.Cut()
+ message_admins("[key_name_admin(user)] has cleared the shuttle events on: [port]")
+ else if(options[result])
+ var/typepath = options[result]
+ if(typepath in active)
+ port.event_list.Remove(active[options[result]])
+ message_admins("[key_name_admin(user)] has removed '[active[result]]' from [port].")
+ else
+ message_admins("[key_name_admin(user)] has added '[typepath]' to [port].")
+ port.add_shuttle_event(typepath)
+
+ADMIN_VERB(call_shuttle, R_ADMIN, "Call Shuttle", "Force a shuttle call with additional modifiers.", ADMIN_CATEGORY_SHUTTLE)
+ if(EMERGENCY_AT_LEAST_DOCKED)
+ return
+
+ var/confirm = tgui_alert(user, "You sure?", "Confirm", list("Yes", "Yes (No Recall)", "No"))
+ switch(confirm)
+ if(null, "No")
+ return
+ if("Yes (No Recall)")
+ SSshuttle.admin_emergency_no_recall = TRUE
+ SSshuttle.emergency.mode = SHUTTLE_IDLE
+
+ SSshuttle.emergency.request()
+ BLACKBOX_LOG_ADMIN_VERB("Call Shuttle")
+ log_admin("[key_name(user)] admin-called the emergency shuttle.")
+ message_admins(span_adminnotice("[key_name_admin(user)] admin-called the emergency shuttle[confirm == "Yes (No Recall)" ? " (non-recallable)" : ""]."))
+
+ADMIN_VERB(cancel_shuttle, R_ADMIN, "Cancel Shuttle", "Recall the shuttle, regardless of circumstances.", ADMIN_CATEGORY_SHUTTLE)
+ if(EMERGENCY_AT_LEAST_DOCKED)
+ return
+
+ if(tgui_alert(user, "You sure?", "Confirm", list("Yes", "No")) != "Yes")
+ return
+ SSshuttle.admin_emergency_no_recall = FALSE
+ SSshuttle.emergency.cancel()
+ BLACKBOX_LOG_ADMIN_VERB("Cancel Shuttle")
+ log_admin("[key_name(user)] admin-recalled the emergency shuttle.")
+ message_admins(span_adminnotice("[key_name_admin(user)] admin-recalled the emergency shuttle."))
+
+ADMIN_VERB(disable_shuttle, R_ADMIN, "Disable Shuttle", "Those fuckers aren't getting out.", ADMIN_CATEGORY_SHUTTLE)
+ if(SSshuttle.emergency.mode == SHUTTLE_DISABLED)
+ to_chat(user, span_warning("Error, shuttle is already disabled."))
+ return
+
+ if(tgui_alert(user, "You sure?", "Confirm", list("Yes", "No")) != "Yes")
+ return
+
+ message_admins(span_adminnotice("[key_name_admin(user)] disabled the shuttle."))
+
+ SSshuttle.last_mode = SSshuttle.emergency.mode
+ SSshuttle.last_call_time = SSshuttle.emergency.timeLeft(1)
+ SSshuttle.admin_emergency_no_recall = TRUE
+ SSshuttle.emergency.setTimer(0)
+ SSshuttle.emergency.mode = SHUTTLE_DISABLED
+ priority_announce(
+ text = "Emergency Shuttle uplink failure, shuttle disabled until further notice.",
+ title = "Uplink Failure",
+ sound = 'sound/announcer/announcement/announce_dig.ogg',
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "grey",
+ )
+
+ADMIN_VERB(enable_shuttle, R_ADMIN, "Enable Shuttle", "Those fuckers ARE getting out.", ADMIN_CATEGORY_SHUTTLE)
+ if(SSshuttle.emergency.mode != SHUTTLE_DISABLED)
+ to_chat(user, span_warning("Error, shuttle not disabled."))
+ return
+
+ if(tgui_alert(user, "You sure?", "Confirm", list("Yes", "No")) != "Yes")
+ return
+
+ message_admins(span_adminnotice("[key_name_admin(user)] enabled the emergency shuttle."))
+ SSshuttle.admin_emergency_no_recall = FALSE
+ SSshuttle.emergency_no_recall = FALSE
+ if(SSshuttle.last_mode == SHUTTLE_DISABLED) //If everything goes to shit, fix it.
+ SSshuttle.last_mode = SHUTTLE_IDLE
+
+ SSshuttle.emergency.mode = SSshuttle.last_mode
+ if(SSshuttle.last_call_time < 10 SECONDS && SSshuttle.last_mode != SHUTTLE_IDLE)
+ SSshuttle.last_call_time = 10 SECONDS //Make sure no insta departures.
+ SSshuttle.emergency.setTimer(SSshuttle.last_call_time)
+ priority_announce(
+ text = "Emergency Shuttle uplink reestablished, shuttle enabled.",
+ title = "Uplink Restored",
+ sound = 'sound/announcer/announcement/announce_dig.ogg',
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "green",
+ )
+
+ADMIN_VERB(hostile_environment, R_ADMIN, "Hostile Environment", "Disable the shuttle, naturally.", ADMIN_CATEGORY_SHUTTLE)
+ switch(tgui_alert(user, "Select an Option", "Hostile Environment Manager", list("Enable", "Disable", "Clear All")))
+ if("Enable")
+ if (SSshuttle.hostile_environments["Admin"] == TRUE)
+ to_chat(user, span_warning("Error, admin hostile environment already enabled."))
+ else
+ message_admins(span_adminnotice("[key_name_admin(user)] Enabled an admin hostile environment"))
+ SSshuttle.registerHostileEnvironment("Admin")
+ if("Disable")
+ if (!SSshuttle.hostile_environments["Admin"])
+ to_chat(user, span_warning("Error, no admin hostile environment found."))
+ else
+ message_admins(span_adminnotice("[key_name_admin(user)] Disabled the admin hostile environment"))
+ SSshuttle.clearHostileEnvironment("Admin")
+ if("Clear All")
+ message_admins(span_adminnotice("[key_name_admin(user)] Disabled all current hostile environment sources"))
+ SSshuttle.hostile_environments.Cut()
+ SSshuttle.checkHostileEnvironment()
+
+ADMIN_VERB(shuttle_panel, R_ADMIN, "Shuttle Manipulator", "Opens the shuttle manipulator UI.", ADMIN_CATEGORY_SHUTTLE)
+ SSshuttle.ui_interact(user.mob)
+
+/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user)
+ var/list/options = list()
+
+ 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.shuttle_id] = S
+
+ options += "--------"
+ options += "Infinite Transit"
+ options += "Delete Shuttle"
+ options += "Into The Sunset (delete & greentext 'escape')"
+
+ var/selection = tgui_input_list(user, "Select where to fly [name || shuttle_id]:", "Fly Shuttle", options)
+ if(isnull(selection))
+ return
+
+ switch(selection)
+ if("Infinite Transit")
+ destination = null
+ mode = SHUTTLE_IGNITING
+ setTimer(ignitionTime)
+
+ if("Delete Shuttle")
+ 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 || shuttle_id] and greentext escape objectives?", "Delete Shuttle", list("Cancel", "Really!")) != "Really!")
+ return
+ intoTheSunset()
+
+ else
+ if(options[selection])
+ request(options[selection])
+
+/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user)
+ return // use the existing verbs for this
+
+/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user)
+ switch(tgui_alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", list("Fly", "Retarget", "Cancel")))
+ if("Cancel")
+ return
+ if("Fly")
+ return ..()
+
+ var/list/options = list()
+
+ 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.shuttle_id] = S
+
+ var/selection = tgui_input_list(user, "New arrivals destination", "Fly Shuttle", options)
+ if(isnull(selection))
+ return
+ target_dock = options[selection]
+ if(!QDELETED(target_dock))
+ destination = target_dock
diff --git a/code/modules/admin/verbs/change_shuttle_events.dm b/code/modules/admin/verbs/change_shuttle_events.dm
deleted file mode 100644
index 90f7e03672e73..0000000000000
--- a/code/modules/admin/verbs/change_shuttle_events.dm
+++ /dev/null
@@ -1,31 +0,0 @@
-ADMIN_VERB(change_shuttle_events, R_ADMIN|R_FUN, "Change Shuttle Events", "Change the events on a shuttle.", ADMIN_CATEGORY_EVENTS)
- //At least for now, just letting admins modify the emergency shuttle is fine
- var/obj/docking_port/mobile/port = SSshuttle.emergency
-
- if(!port)
- to_chat(user, span_admin("Uh oh, couldn't find the escape shuttle!"))
-
- var/list/options = list("Clear"="Clear")
-
- //Grab the active events so we know which ones we can Add or Remove
- var/list/active = list()
- for(var/datum/shuttle_event/event in port.event_list)
- active[event.type] = event
-
- for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event))
- options[((event in active) ? "(Remove)" : "(Add)") + initial(event.name)] = event
-
- //Throw up an ugly menu with the shuttle events and the options to add or remove them, or clear them all
- var/result = input(user, "Choose an event to add/remove", "Shuttle Events") as null|anything in sort_list(options)
-
- if(result == "Clear")
- port.event_list.Cut()
- message_admins("[key_name_admin(user)] has cleared the shuttle events on: [port]")
- else if(options[result])
- var/typepath = options[result]
- if(typepath in active)
- port.event_list.Remove(active[options[result]])
- message_admins("[key_name_admin(user)] has removed '[active[result]]' from [port].")
- else
- port.event_list.Add(new typepath (port))
- message_admins("[key_name_admin(user)] has added '[typepath]' to [port].")
diff --git a/code/modules/admin/verbs/ert.dm b/code/modules/admin/verbs/ert.dm
index 23f1627503e06..71722eb6d64ab 100644
--- a/code/modules/admin/verbs/ert.dm
+++ b/code/modules/admin/verbs/ert.dm
@@ -77,6 +77,8 @@
else
ertemplate = new /datum/ert/centcom_official
+ var/human_authority_setting = CONFIG_GET(string/human_authority)
+
var/list/settings = list(
"preview_callback" = CALLBACK(src, PROC_REF(makeERTPreviewIcon)),
"mainsettings" = list(
@@ -84,7 +86,7 @@
"teamsize" = list("desc" = "Team Size", "type" = "number", "value" = ertemplate.teamsize),
"mission" = list("desc" = "Mission", "type" = "string", "value" = ertemplate.mission),
"polldesc" = list("desc" = "Ghost poll description", "type" = "string", "value" = ertemplate.polldesc),
- "enforce_human" = list("desc" = "Enforce human authority", "type" = "boolean", "value" = "[(CONFIG_GET(flag/enforce_human_authority) ? "Yes" : "No")]"),
+ "enforce_human" = list("desc" = "Enforce human authority", "type" = "boolean", "value" = "[(human_authority_setting == HUMAN_AUTHORITY_ENFORCED ? "Yes" : "No")]"),
"open_armory" = list("desc" = "Open armory doors", "type" = "boolean", "value" = "[(ertemplate.opendoors ? "Yes" : "No")]"),
"leader_experience" = list("desc" = "Pick an experienced leader", "type" = "boolean", "value" = "[(ertemplate.leader_experience ? "Yes" : "No")]"),
"random_names" = list("desc" = "Randomize names", "type" = "boolean", "value" = "[(ertemplate.random_names ? "Yes" : "No")]"),
diff --git a/code/modules/admin/verbs/grant_dna_infusion.dm b/code/modules/admin/verbs/grant_dna_infusion.dm
index 06cfa8110d60d..2e087a160574d 100644
--- a/code/modules/admin/verbs/grant_dna_infusion.dm
+++ b/code/modules/admin/verbs/grant_dna_infusion.dm
@@ -19,7 +19,7 @@
// This is necessary because list propererties are not defined until initialization
picked_infusion = infusions[picked_infusion]
- picked_infusion = new picked_infusion
+ picked_infusion = new picked_infusion()
if(!length(picked_infusion.output_organs))
return FALSE
@@ -27,7 +27,8 @@
. = picked_infusion
for(var/obj/item/organ/infusion_organ as anything in picked_infusion.output_organs)
var/obj/item/organ/new_organ = new infusion_organ()
- if(!new_organ.replace_into(target))
+ new_organ.replace_into(target)
+ if(new_organ.owner != target)
to_chat(usr, span_notice("[target] is unable to carry [new_organ]!"))
qdel(new_organ)
. = FALSE
diff --git a/code/modules/admin/verbs/lawpanel.dm b/code/modules/admin/verbs/lawpanel.dm
index 6de3ff70182b8..32815b73cbd8f 100644
--- a/code/modules/admin/verbs/lawpanel.dm
+++ b/code/modules/admin/verbs/lawpanel.dm
@@ -48,7 +48,8 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
borgo.laws.add_inherent_law(lawtext)
if(LAW_SUPPLIED)
borgo.laws.add_supplied_law(length(borgo.laws.supplied), lawtext) // Just goes to the end of the list
-
+ log_admin("[key_name(user)] has UPLOADED a [lawtype] law to [key_name(borgo)] stating: [lawtext]")
+ message_admins("[key_name(user)] has UPLOADED a [lawtype] law to [key_name(borgo)] stating: [lawtext]")
return TRUE
/datum/law_panel/proc/move_law_helper(mob/living/user, mob/living/silicon/borgo, direction, law)
@@ -104,6 +105,9 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
return FALSE
relevant_laws[lawindex] = newlaw
+ log_admin("[key_name(user)] has EDITED [key_name(borgo)] [lawtype] law. OLD LAW: [oldlaw] \
+ NEW LAW: [newlaw]")
+ message_admins("[key_name(user)] has EDITED a [lawtype] law on [key_name(borgo)]")
return TRUE
/datum/law_panel/proc/edit_law_priority_helper(mob/living/user, mob/living/silicon/borgo, law)
@@ -145,10 +149,12 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
if(swap_or_remove == "Swap")
borgo.laws.supplied.Swap(old_prio, new_prio)
+ log_admin("[key_name(user)] has SWAPPED [key_name(borgo)] law [old_prio] and [new_prio]")
return TRUE
if(swap_or_remove == "Replace")
borgo.laws.remove_supplied_law_by_num(new_prio, law)
borgo.laws.add_supplied_law(new_prio, law)
+ log_admin("[key_name(user)] has REPLACED [key_name(borgo)] law: [law] with priority [new_prio]")
return TRUE
var/new_prio_for_old_law = new_prio + (swap_or_remove == "Move up" ? 1 : -1)
@@ -157,6 +163,7 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
borgo.laws.remove_supplied_law_by_num(new_prio)
borgo.laws.add_supplied_law(new_prio, law)
borgo.laws.add_supplied_law(new_prio_for_old_law, existing_law)
+ log_admin("[key_name(user)] has changed the priority of an existing law on [key_name(borgo)]. LAW: [law] PRIORITY: [new_prio]")
return TRUE
// Sanity
@@ -167,6 +174,8 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
// At this point the slot is free, insert it as normal
borgo.laws.remove_supplied_law_by_num(old_prio)
borgo.laws.add_supplied_law(new_prio, law)
+ log_admin("[key_name(user)] has UPLOADED a supplied law to [key_name(borgo)] stating: [law]") // Normal insertion, I.E upload
+ message_admins("[key_name(user)] has UPLOADED a supplied law to [key_name(borgo)] stating: [law]")
return TRUE
/datum/law_panel/proc/remove_law_helper(mob/living/user, mob/living/silicon/borgo, lawtype, law)
@@ -184,7 +193,8 @@ ADMIN_VERB(law_panel, R_ADMIN, "Law Panel", "View the AI laws.", ADMIN_CATEGORY_
borgo.laws.protected_zeroth = FALSE
else
return FALSE
-
+ log_admin("[key_name(user)] has REMOVED a law from [key_name(borgo)]. LAW: [law]")
+ message_admins("[key_name(user)] has REMOVED a law from [key_name(borgo)]. LAW: [law]")
return TRUE
/datum/law_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
diff --git a/code/modules/admin/verbs/maprotation.dm b/code/modules/admin/verbs/maprotation.dm
index 09d6d93bee632..38d7535758fc7 100644
--- a/code/modules/admin/verbs/maprotation.dm
+++ b/code/modules/admin/verbs/maprotation.dm
@@ -1,12 +1,3 @@
-
-ADMIN_VERB(force_random_rotate, R_SERVER, "Trigger 'Random' Map Rotation", "Force a map vote.", ADMIN_CATEGORY_SERVER)
- var/rotate = tgui_alert(user,"Force a random map rotation to trigger?", "Rotate map?", list("Yes", "Cancel"))
- if (rotate != "Yes")
- return
- message_admins("[key_name_admin(user)] is forcing a random map rotation.")
- log_admin("[key_name(user)] is forcing a random map rotation.")
- SSmapping.maprotate()
-
ADMIN_VERB(admin_change_map, R_SERVER, "Change Map", "Set the next map.", ADMIN_CATEGORY_SERVER)
var/list/maprotatechoices = list()
for (var/map in config.maplist)
@@ -119,14 +110,14 @@ ADMIN_VERB(admin_change_map, R_SERVER, "Change Map", "Set the next map.", ADMIN_
fdel(PATH_TO_NEXT_MAP_JSON)
text2file(json_encode(json_value), PATH_TO_NEXT_MAP_JSON)
- if(SSmapping.changemap(virtual_map))
+ if(SSmap_vote.set_next_map(virtual_map))
message_admins("[key_name_admin(user)] has changed the map to [virtual_map.map_name]")
- SSmapping.map_force_chosen = TRUE
+ SSmap_vote.admin_override = TRUE
fdel("data/custom_map_json/[config_file]")
else
var/datum/map_config/virtual_map = maprotatechoices[chosenmap]
message_admins("[key_name_admin(user)] is changing the map to [virtual_map.map_name]")
log_admin("[key_name(user)] is changing the map to [virtual_map.map_name]")
- if (SSmapping.changemap(virtual_map))
+ if (SSmap_vote.set_next_map(virtual_map))
message_admins("[key_name_admin(user)] has changed the map to [virtual_map.map_name]")
- SSmapping.map_force_chosen = TRUE
+ SSmap_vote.admin_override = TRUE
diff --git a/code/modules/admin/verbs/shuttlepanel.dm b/code/modules/admin/verbs/shuttlepanel.dm
deleted file mode 100644
index ae0ad4dcde3c5..0000000000000
--- a/code/modules/admin/verbs/shuttlepanel.dm
+++ /dev/null
@@ -1,68 +0,0 @@
-ADMIN_VERB(shuttle_panel, R_ADMIN, "Shuttle Manipulator", "Opens the shuttle manipulator UI.", ADMIN_CATEGORY_EVENTS)
- SSshuttle.ui_interact(user.mob)
-
-/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user)
- var/list/options = list()
-
- 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.shuttle_id] = S
-
- options += "--------"
- options += "Infinite Transit"
- options += "Delete Shuttle"
- options += "Into The Sunset (delete & greentext 'escape')"
-
- var/selection = tgui_input_list(user, "Select where to fly [name || shuttle_id]:", "Fly Shuttle", options)
- if(isnull(selection))
- return
-
- switch(selection)
- if("Infinite Transit")
- destination = null
- mode = SHUTTLE_IGNITING
- setTimer(ignitionTime)
-
- if("Delete Shuttle")
- 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 || shuttle_id] and greentext escape objectives?", "Delete Shuttle", list("Cancel", "Really!")) != "Really!")
- return
- intoTheSunset()
-
- else
- if(options[selection])
- request(options[selection])
-
-/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user)
- return // use the existing verbs for this
-
-/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user)
- switch(tgui_alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", list("Fly", "Retarget", "Cancel")))
- if("Cancel")
- return
- if("Fly")
- return ..()
-
- var/list/options = list()
-
- 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.shuttle_id] = S
-
- var/selection = tgui_input_list(user, "New arrivals destination", "Fly Shuttle", options)
- if(isnull(selection))
- return
- target_dock = options[selection]
- if(!QDELETED(target_dock))
- destination = target_dock
-
diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm
index 75ed4aab4a59c..4fde1e30d1a0e 100644
--- a/code/modules/admin/view_variables/topic.dm
+++ b/code/modules/admin/view_variables/topic.dm
@@ -113,10 +113,51 @@
admin_ticket_log(L, "[log_msg]")
vv_update_display(L, Text, "[newamt]")
+ else if(href_list["item_to_tweak"] && href_list["var_tweak"])
+ if(!check_rights(NONE))
+ return
+
+ var/obj/item/editing = locate(href_list["item_to_tweak"])
+ if(!istype(editing) || QDELING(editing))
+ return
+
+ var/existing_val = -1
+ switch(href_list["var_tweak"])
+ if("damtype")
+ existing_val = editing.damtype
+ if("force")
+ existing_val = editing.force
+ if("wound")
+ existing_val = editing.wound_bonus
+ if("bare wound")
+ existing_val = editing.bare_wound_bonus
+ else
+ CRASH("Invalid var_tweak passed to item vv set var: [href_list["var_tweak"]]")
+
+ var/new_val
+ if(href_list["var_tweak"] == "damtype")
+ new_val = input("Enter the new damage type for [editing]","Set Damtype", existing_val) in list(BRUTE, BURN, TOX, OXY, STAMINA, BRAIN)
+ else
+ new_val = input("Enter the new value for [editing]'s [href_list["var_tweak"]]","Set [href_list["var_tweak"]]", existing_val) as num|null
+ if(isnull(new_val) || new_val == existing_val || QDELETED(editing) || !check_rights(NONE))
+ return
+
+ switch(href_list["var_tweak"])
+ if("damtype")
+ editing.damtype = new_val
+ if("force")
+ editing.force = new_val
+ if("wound")
+ editing.wound_bonus = new_val
+ if("bare wound")
+ editing.bare_wound_bonus = new_val
+
+ message_admins("[key_name(usr)] set [editing]'s [href_list["var_tweak"]] to [new_val] (was [existing_val])")
+ log_admin("[key_name(usr)] set [editing]'s [href_list["var_tweak"]] to [new_val] (was [existing_val])")
+ vv_update_display(editing, href_list["var_tweak"], istext(new_val) ? uppertext(new_val) : new_val)
//Finally, refresh if something modified the list.
if(href_list["datumrefresh"])
var/datum/DAT = locate(href_list["datumrefresh"])
if(isdatum(DAT) || istype(DAT, /client) || islist(DAT))
debug_variables(DAT)
-
diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm
index 27c1fd5a0ea89..741bdeeed7b7c 100644
--- a/code/modules/antagonists/_common/antag_spawner.dm
+++ b/code/modules/antagonists/_common/antag_spawner.dm
@@ -381,7 +381,10 @@
if(ishuman(spawned_mob))
var/mob/living/carbon/human/human_mob = spawned_mob
- human_mob.set_species(species_type)
+ // ignore if it's already the same
+ if(human_mob.dna.species != species_type)
+ human_mob.set_species(species_type)
+
human_mob.equipOutfit(outfit)
op_mind.special_role = role_to_play
@@ -411,6 +414,7 @@
desc = "Call up some backup from ARC for monkey mayhem."
icon = 'icons/obj/devices/voice.dmi'
icon_state = "walkietalkie"
+ spawn_type = /mob/living/carbon/human/species/monkey
species_type = /datum/species/monkey
outfit = /datum/outfit/syndicate_monkey
antag_datum = /datum/antagonist/syndicate_monkey
@@ -424,13 +428,26 @@
monkey_man.fully_replace_character_name(monkey_man.real_name, pick(GLOB.syndicate_monkey_names))
- monkey_man.make_clever_and_no_dna_scramble()
+ monkey_man.crewlike_monkify()
+
+ // fuck you i am no longer playing around. this goes against the entire soul of the item
+ RegisterSignal(monkey_man, COMSIG_SPECIES_GAIN, PROC_REF(allergy))
+
monkey_man.mind.enslave_mind_to_creator(user)
var/obj/item/implant/explosive/imp = new(src)
imp.implant(monkey_man, user)
+/obj/item/antag_spawner/loadout/monkey_man/proc/allergy(mob/living/second_lifer, datum/species/folly_species)
+ SIGNAL_HANDLER
+ if(is_simian(second_lifer))
+ return
+ // timer is long to let them panic and consider their folly, and because allergies take a while
+ second_lifer.visible_message(span_bolddanger("[second_lifer] starts swelling unhealthily in size. It looks like they had an allergic reaction to becoming a [folly_species]!"), span_userdanger("As your monkey features morph, you feel your allergies coming in. Oh no."))
+ // no brain or items. organs are funny though
+ second_lifer.inflate_gib(drop_bitflags = DROP_ORGANS|DROP_BODYPARTS, gib_time = 25 SECONDS, anim_time = 40 SECONDS)
+
/datum/outfit/syndicate_monkey
name = "Syndicate Monkey Agent Kit"
diff --git a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
index 109f27e82225f..8739a9de74950 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm
@@ -50,6 +50,13 @@
/obj/item/abductor/silencer = 1
)
+/datum/outfit/abductor/agent/cardboard
+ name = "Abductor Agent"
+ head = /obj/item/clothing/head/helmet/abductor
+ suit = /obj/item/clothing/suit/armor/abductor/vest
+ l_hand = /obj/item/melee/baton/abductor
+ belt = /obj/item/storage/belt/military/abductor/full
+
/datum/outfit/abductor/scientist
name = "Abductor Scientist"
diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm
index f4fe0fb1875f8..d1f240b7f6821 100644
--- a/code/modules/antagonists/abductor/equipment/gland.dm
+++ b/code/modules/antagonists/abductor/equipment/gland.dm
@@ -51,7 +51,7 @@
return
var/image/holder = owner.hud_list[GLAND_HUD]
var/icon/I = icon(owner.icon, owner.icon_state, owner.dir)
- holder.pixel_y = I.Height() - world.icon_size
+ holder.pixel_y = I.Height() - ICON_SIZE_Y
if(active_mind_control)
holder.icon_state = "hudgland_active"
else if(mind_control_uses)
diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm
index 59610861756e6..297437e0bc65f 100644
--- a/code/modules/antagonists/changeling/changeling.dm
+++ b/code/modules/antagonists/changeling/changeling.dm
@@ -15,6 +15,7 @@
default_custom_objective = "Consume the station's most valuable genomes."
hardcore_random_bonus = TRUE
stinger_sound = 'sound/music/antag/ling_alert.ogg'
+
/// Whether to give this changeling objectives or not
var/give_objectives = TRUE
/// Weather we assign objectives which compete with other lings
@@ -996,11 +997,11 @@
var/icon/final_icon = render_preview_outfit(/datum/outfit/changeling)
var/icon/split_icon = render_preview_outfit(/datum/outfit/job/engineer)
- final_icon.Shift(WEST, world.icon_size / 2)
- final_icon.Shift(EAST, world.icon_size / 2)
+ final_icon.Shift(WEST, ICON_SIZE_X / 2)
+ final_icon.Shift(EAST, ICON_SIZE_X / 2)
- split_icon.Shift(EAST, world.icon_size / 2)
- split_icon.Shift(WEST, world.icon_size / 2)
+ split_icon.Shift(EAST, ICON_SIZE_X / 2)
+ split_icon.Shift(WEST, ICON_SIZE_X / 2)
final_icon.Blend(split_icon, ICON_OVERLAY)
diff --git a/code/modules/antagonists/changeling/powers/transform.dm b/code/modules/antagonists/changeling/powers/transform.dm
index 852e8fca53c8c..733e0495118bc 100644
--- a/code/modules/antagonists/changeling/powers/transform.dm
+++ b/code/modules/antagonists/changeling/powers/transform.dm
@@ -142,7 +142,7 @@
if(hud_icon)
var/image/holder = user.hud_list[ID_HUD]
var/icon/I = icon(user.icon, user.icon_state, user.dir)
- holder.pixel_y = I.Height() - world.icon_size
+ holder.pixel_y = I.Height() - ICON_SIZE_Y
holder.icon_state = hud_icon
/**
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index 6c3135fbe4b32..d4350a9c67966 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -41,7 +41,7 @@
if(!moving_button)
continue
var/first_available_slot = position_list[1]
- var/our_x = position[1] + first_available_slot * world.icon_size // Offset any new buttons into our list
+ var/our_x = position[1] + first_available_slot * ICON_SIZE_X // Offset any new buttons into our list
hud.position_action(moving_button, offset_to_screen_loc(our_x, position[2], our_view))
blood_spell.positioned = first_available_slot
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index e7cde6d1425c2..e334b17e3603a 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -34,7 +34,8 @@ Runes can either be invoked by one's self or with many different cultists. Each
icon = 'icons/obj/antags/cult/rune.dmi'
icon_state = "1"
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
- layer = SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
color = RUNE_COLOR_RED
/// The name of the rune to cultists
diff --git a/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm b/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
index 5491251d1aa53..20eccc6977900 100644
--- a/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
+++ b/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
@@ -249,7 +249,7 @@
/obj/item/clothing/mask/chameleon = 20,
/obj/item/language_manual/codespeak_manual/unlimited = 10,
/obj/item/storage/mail_counterfeit_device = 10,
- /obj/item/traitor_machine_trapper = 10,
+ /obj/item/clothing/glasses/thermal = 10,
/obj/item/gun/ballistic/automatic/pistol/clandestine/fisher = 10,
))
diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm
index 4c9c25945c9e8..d6e21bed03bea 100644
--- a/code/modules/antagonists/heretic/heretic_antag.dm
+++ b/code/modules/antagonists/heretic/heretic_antag.dm
@@ -27,6 +27,7 @@
default_custom_objective = "Turn a department into a testament for your dark knowledge."
hardcore_random_bonus = TRUE
stinger_sound = 'sound/music/antag/heretic/heretic_gain.ogg'
+
/// Whether we give this antagonist objectives on gain.
var/give_objectives = TRUE
/// Whether we've ascended! (Completed one of the final rituals)
diff --git a/code/modules/antagonists/heretic/items/unfathomable_curio.dm b/code/modules/antagonists/heretic/items/unfathomable_curio.dm
index 716b0927f54c6..eff1fa7ea2fe2 100644
--- a/code/modules/antagonists/heretic/items/unfathomable_curio.dm
+++ b/code/modules/antagonists/heretic/items/unfathomable_curio.dm
@@ -21,6 +21,7 @@
atom_storage.max_total_storage = 21
atom_storage.set_holdable(list(
/obj/item/ammo_box/strilka310/lionhunter,
+ /obj/item/heretic_labyrinth_handbook,
/obj/item/bodypart, // Bodyparts are often used in rituals.
/obj/item/clothing/neck/eldritch_amulet,
/obj/item/clothing/neck/heretic_focus,
diff --git a/code/modules/antagonists/heretic/magic/cosmic_runes.dm b/code/modules/antagonists/heretic/magic/cosmic_runes.dm
index 1003920dfa9ad..be8f103678e09 100644
--- a/code/modules/antagonists/heretic/magic/cosmic_runes.dm
+++ b/code/modules/antagonists/heretic/magic/cosmic_runes.dm
@@ -57,7 +57,8 @@
icon = 'icons/obj/service/hand_of_god_structures.dmi'
icon_state = "cosmic_rune"
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
- layer = SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
/// The other rune this rune is linked with
var/datum/weakref/linked_rune
/// Effect for when someone teleports
@@ -133,7 +134,8 @@
name = "cosmic rune"
icon = 'icons/obj/service/hand_of_god_structures.dmi'
icon_state = "cosmic_rune_fade"
- layer = SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
anchored = TRUE
duration = 5
@@ -147,7 +149,8 @@
name = "cosmic rune"
icon = 'icons/obj/service/hand_of_god_structures.dmi'
icon_state = "cosmic_rune_light"
- layer = SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
anchored = TRUE
duration = 5
diff --git a/code/modules/antagonists/heretic/magic/space_crawl.dm b/code/modules/antagonists/heretic/magic/space_crawl.dm
index 6a96403872f11..cce9f46085bc6 100644
--- a/code/modules/antagonists/heretic/magic/space_crawl.dm
+++ b/code/modules/antagonists/heretic/magic/space_crawl.dm
@@ -1,3 +1,5 @@
+#define SPACE_PHASING "space-phasing"
+
/**
* ### Space Crawl
*
@@ -16,6 +18,8 @@
invocation_type = INVOCATION_NONE
spell_requirements = NONE
+ ///List of traits that are added to the heretic while in space phase jaunt
+ var/static/list/jaunting_traits = list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD, TRAIT_NOBREATH)
/datum/action/cooldown/spell/jaunt/space_crawl/Grant(mob/grant_to)
. = ..()
@@ -82,6 +86,7 @@
jaunter.put_in_hands(left_hand)
jaunter.put_in_hands(right_hand)
+ jaunter.add_traits(jaunting_traits, SPACE_PHASING)
RegisterSignal(jaunter, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING), PROC_REF(on_focus_lost))
playsound(our_turf, 'sound/effects/magic/cosmic_energy.ogg', 50, TRUE, -1)
our_turf.visible_message(span_warning("[jaunter] sinks into [our_turf]!"))
@@ -101,6 +106,7 @@
if(!exit_jaunt(jaunter, our_turf))
return FALSE
+ jaunter.remove_traits(jaunting_traits, SPACE_PHASING)
our_turf.visible_message(span_boldwarning("[jaunter] rises out of [our_turf]!"))
return TRUE
@@ -131,3 +137,5 @@
/obj/item/space_crawl/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
+
+#undef SPACE_PHASING
diff --git a/code/modules/antagonists/heretic/rust_effect.dm b/code/modules/antagonists/heretic/rust_effect.dm
index ad86fa5a747f5..9af6c4f6d89a0 100644
--- a/code/modules/antagonists/heretic/rust_effect.dm
+++ b/code/modules/antagonists/heretic/rust_effect.dm
@@ -3,9 +3,9 @@
icon = 'icons/effects/eldritch.dmi'
icon_state = "small_rune_1"
anchored = TRUE
- layer = LOW_SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = LOWER_RUNE_LAYER
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- plane = GAME_PLANE
/obj/effect/glowing_rune/Initialize(mapload)
. = ..()
diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm
index d60129ae3d930..387301f2c489d 100644
--- a/code/modules/antagonists/heretic/status_effects/buffs.dm
+++ b/code/modules/antagonists/heretic/status_effects/buffs.dm
@@ -254,13 +254,13 @@
var/static/list/caretaking_traits = list(TRAIT_GODMODE, TRAIT_HANDS_BLOCKED, TRAIT_IGNORESLOWDOWN, TRAIT_SECLUDED_LOCATION)
/datum/status_effect/caretaker_refuge/on_apply()
- owner.add_traits(caretaking_traits, TRAIT_STATUS_EFFECT(id))
animate(owner, alpha = 45,time = 0.5 SECONDS)
owner.density = FALSE
RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING), PROC_REF(on_focus_lost))
RegisterSignal(owner, COMSIG_MOB_BEFORE_SPELL_CAST, PROC_REF(prevent_spell_usage))
RegisterSignal(owner, COMSIG_ATOM_HOLYATTACK, PROC_REF(nullrod_handler))
RegisterSignal(owner, COMSIG_CARBON_CUFF_ATTEMPTED, PROC_REF(prevent_cuff))
+ owner.add_traits(caretaking_traits, TRAIT_STATUS_EFFECT(id))
return TRUE
/datum/status_effect/caretaker_refuge/on_remove()
@@ -288,7 +288,7 @@
/datum/status_effect/caretaker_refuge/proc/on_focus_lost()
SIGNAL_HANDLER
to_chat(owner, span_danger("Without a focus, your refuge weakens and dissipates!"))
- owner.remove_status_effect(type)
+ qdel(src)
/datum/status_effect/caretaker_refuge/proc/prevent_spell_usage(datum/source, datum/spell)
SIGNAL_HANDLER
diff --git a/code/modules/antagonists/heretic/structures/carving_knife.dm b/code/modules/antagonists/heretic/structures/carving_knife.dm
index 72b224d117dd4..b93b52eb8e8e9 100644
--- a/code/modules/antagonists/heretic/structures/carving_knife.dm
+++ b/code/modules/antagonists/heretic/structures/carving_knife.dm
@@ -175,7 +175,7 @@
/obj/structure/trap/eldritch/on_entered(datum/source, atom/movable/entering_atom)
if(!isliving(entering_atom))
- return ..()
+ return
var/mob/living/living_mob = entering_atom
if(WEAKREF(living_mob) == owner)
return
diff --git a/code/modules/antagonists/heretic/transmutation_rune.dm b/code/modules/antagonists/heretic/transmutation_rune.dm
index 02c27c353a90e..a2bf4af77f4fa 100644
--- a/code/modules/antagonists/heretic/transmutation_rune.dm
+++ b/code/modules/antagonists/heretic/transmutation_rune.dm
@@ -7,7 +7,8 @@
anchored = TRUE
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
- layer = SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
///Used mainly for summoning ritual to prevent spamming the rune to create millions of monsters.
var/is_in_use = FALSE
@@ -222,8 +223,8 @@
pixel_x = -30
pixel_y = 18
pixel_z = -48
- plane = GAME_PLANE
- layer = SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
greyscale_config = /datum/greyscale_config/heretic_rune
/// We only set this state after setting the colour, otherwise the animation doesn't colour correctly
var/animation_state = "transmutation_rune_draw"
diff --git a/code/modules/antagonists/ninja/ninja_clothing.dm b/code/modules/antagonists/ninja/ninja_clothing.dm
index 54ed46c9c3ec0..4eaf40f9c79cb 100644
--- a/code/modules/antagonists/ninja/ninja_clothing.dm
+++ b/code/modules/antagonists/ninja/ninja_clothing.dm
@@ -15,7 +15,8 @@
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
flags_inv = HIDEFACIALHAIR | HIDEFACE | HIDESNOUT
flags_cover = MASKCOVERSMOUTH | PEPPERPROOF
- has_fov = FALSE
+ pepper_tint = FALSE
+
/obj/item/clothing/under/syndicate/ninja
name = "ninja suit"
diff --git a/code/modules/antagonists/nukeop/datums/operative.dm b/code/modules/antagonists/nukeop/datums/operative.dm
index 859cee578b265..ebaaf8692b2f4 100644
--- a/code/modules/antagonists/nukeop/datums/operative.dm
+++ b/code/modules/antagonists/nukeop/datums/operative.dm
@@ -9,6 +9,7 @@
hijack_speed = 2 //If you can't take out the station, take the shuttle instead.
suicide_cry = "FOR THE SYNDICATE!!"
stinger_sound = 'sound/music/antag/ops.ogg'
+
/// Which nukie team are we on?
var/datum/team/nuclear/nuke_team
/// If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team.
@@ -114,8 +115,8 @@
var/icon/teammate = render_preview_outfit(preview_outfit_behind)
teammate.Blend(rgb(128, 128, 128, 128), ICON_MULTIPLY)
- final_icon.Blend(teammate, ICON_UNDERLAY, -world.icon_size / 4, 0)
- final_icon.Blend(teammate, ICON_UNDERLAY, world.icon_size / 4, 0)
+ final_icon.Blend(teammate, ICON_UNDERLAY, -ICON_SIZE_X / 4, 0)
+ final_icon.Blend(teammate, ICON_UNDERLAY, ICON_SIZE_X / 4, 0)
if (!isnull(nuke_icon_state))
var/icon/nuke = icon('icons/obj/machines/nuke.dmi', nuke_icon_state)
diff --git a/code/modules/antagonists/pirate/pirate_outfits.dm b/code/modules/antagonists/pirate/pirate_outfits.dm
index 14729908e65be..8976e212c5475 100644
--- a/code/modules/antagonists/pirate/pirate_outfits.dm
+++ b/code/modules/antagonists/pirate/pirate_outfits.dm
@@ -57,6 +57,10 @@
id_trim = /datum/id_trim/pirate/captain
+/datum/outfit/pirate/space/captain/cardboard
+ name = "Space Pirate Captain (EVA)"
+ l_hand = /obj/item/nullrod/claymore/saber/pirate
+
/datum/outfit/pirate/silverscale
name = "Silver Scale Member"
diff --git a/code/modules/antagonists/sentient_creature/sentient_creature.dm b/code/modules/antagonists/sentient_creature/sentient_creature.dm
index d1197265ced5d..9fbafdfe2cce9 100644
--- a/code/modules/antagonists/sentient_creature/sentient_creature.dm
+++ b/code/modules/antagonists/sentient_creature/sentient_creature.dm
@@ -10,11 +10,11 @@
var/icon/pandora = icon('icons/mob/simple/lavaland/lavaland_elites.dmi', "pandora")
pandora.Blend(rgb(128, 128, 128, 128), ICON_MULTIPLY)
- final_icon.Blend(pandora, ICON_UNDERLAY, -world.icon_size / 4, 0)
+ final_icon.Blend(pandora, ICON_UNDERLAY, -ICON_SIZE_X / 4, 0)
var/icon/rat = icon('icons/mob/simple/animal.dmi', "regalrat")
rat.Blend(rgb(128, 128, 128, 128), ICON_MULTIPLY)
- final_icon.Blend(rat, ICON_UNDERLAY, world.icon_size / 4, 0)
+ final_icon.Blend(rat, ICON_UNDERLAY, ICON_SIZE_X / 4, 0)
final_icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
return final_icon
diff --git a/code/modules/antagonists/space_dragon/carp_rift.dm b/code/modules/antagonists/space_dragon/carp_rift.dm
index 6038c5e6820b7..6299bde9275cd 100644
--- a/code/modules/antagonists/space_dragon/carp_rift.dm
+++ b/code/modules/antagonists/space_dragon/carp_rift.dm
@@ -111,6 +111,8 @@
healing_color = COLOR_BLUE, \
)
+ AddComponent(/datum/component/fishing_spot, /datum/fish_source/carp_rift)
+
gravity_aura = new(
/* host = */src,
/* range = */15,
diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm
index 090c8f66dc82e..74b0c60a872ce 100644
--- a/code/modules/antagonists/space_dragon/space_dragon.dm
+++ b/code/modules/antagonists/space_dragon/space_dragon.dm
@@ -112,7 +112,7 @@
var/icon/icon = icon('icons/mob/nonhuman-player/spacedragon.dmi', "spacedragon")
icon.Blend(COLOR_STRONG_VIOLET, ICON_MULTIPLY)
- icon.Blend(icon('icons/mob/nonhuman-player/spacedragon.dmi', "overlay_base"), ICON_OVERLAY)
+ icon.Blend(icon('icons/mob/nonhuman-player/spacedragon.dmi', "spacedragon_overlay_base"), ICON_OVERLAY)
icon.Crop(10, 9, 54, 53)
icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
diff --git a/code/modules/antagonists/traitor/objectives/final_objective/final_objective.dm b/code/modules/antagonists/traitor/objectives/final_objective/final_objective.dm
index 6e722b1515eb4..3367540239703 100644
--- a/code/modules/antagonists/traitor/objectives/final_objective/final_objective.dm
+++ b/code/modules/antagonists/traitor/objectives/final_objective/final_objective.dm
@@ -7,6 +7,7 @@
/datum/traitor_objective/ultimate/infect_ai = 1,
/datum/traitor_objective/ultimate/romerol = 1,
/datum/traitor_objective/ultimate/supermatter_cascade = 1,
+ /datum/traitor_objective/ultimate/no_escape = 1,
)
weight = 100
diff --git a/code/modules/antagonists/traitor/objectives/final_objective/no_escape.dm b/code/modules/antagonists/traitor/objectives/final_objective/no_escape.dm
new file mode 100644
index 0000000000000..12cbdcf2d01fa
--- /dev/null
+++ b/code/modules/antagonists/traitor/objectives/final_objective/no_escape.dm
@@ -0,0 +1,48 @@
+/datum/traitor_objective/ultimate/no_escape
+ name = "Attach a beacon to the escape shuttle that will attract a singularity to consume everything."
+ description = "Go to %AREA%, and receive the smuggled beacon. Set up the beacon anywhere on the shuttle, \
+ and charge it using an inducer then, IT COMES. Warning: The singularity will consume all in it's path, you included."
+
+ ///area type the objective owner must be in to receive the satellites
+ var/area/beacon_spawn_area_type
+ ///checker on whether we have sent the beacon yet
+ var/sent_beacon = FALSE
+
+/datum/traitor_objective/ultimate/no_escape/generate_objective(datum/mind/generating_for, list/possible_duplicates)
+ var/list/possible_areas = GLOB.the_station_areas.Copy()
+ for(var/area/possible_area as anything in possible_areas)
+ if(!ispath(possible_area, /area/station/maintenance/solars) && !ispath(possible_area, /area/station/solars))
+ possible_areas -= possible_area
+ if(length(possible_areas) == 0)
+ return FALSE
+ beacon_spawn_area_type = pick(possible_areas)
+ replace_in_name("%AREA%", initial(beacon_spawn_area_type.name))
+ return TRUE
+
+/datum/traitor_objective/ultimate/no_escape/generate_ui_buttons(mob/user)
+ var/list/buttons = list()
+ if(!sent_beacon)
+ buttons += add_ui_button("", "Pressing this will call down a pod with the smuggled beacon.", "beacon", "beacon")
+ return buttons
+
+/datum/traitor_objective/ultimate/no_escape/ui_perform_action(mob/living/user, action)
+ . = ..()
+ switch(action)
+ if("beacon")
+ if(sent_beacon)
+ return
+ var/area/delivery_area = get_area(user)
+ if(delivery_area.type != beacon_spawn_area_type)
+ to_chat(user, span_warning("You must be in [initial(beacon_spawn_area_type.name)] to receive the smuggled beacon."))
+ return
+ sent_beacon = TRUE
+ podspawn(list(
+ "target" = get_turf(user),
+ "style" = /datum/pod_style/syndicate,
+ "spawn" = list(
+ /obj/item/sbeacondrop/no_escape,
+ /obj/item/inducer/syndicate,
+ /obj/item/wrench
+ )
+ ))
+
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm
index 9b67aa24a5380..3716a684c6a3d 100644
--- a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm
@@ -38,7 +38,7 @@
var/static/list/doom_options = list()
if (!length(doom_options))
doom_options = list(DOOM_SINGULARITY, DOOM_TESLA)
- if (!SSmapping.config.planetary)
+ if (!SSmapping.is_planetary())
doom_options += DOOM_METEORS
switch(pick(doom_options))
diff --git a/code/modules/antagonists/wizard/grand_ritual/grand_ritual.dm b/code/modules/antagonists/wizard/grand_ritual/grand_ritual.dm
index 2134e2862a461..e5611411a67e8 100644
--- a/code/modules/antagonists/wizard/grand_ritual/grand_ritual.dm
+++ b/code/modules/antagonists/wizard/grand_ritual/grand_ritual.dm
@@ -299,8 +299,8 @@
pixel_y = 16
pixel_z = -48
anchored = TRUE
- layer = SIGIL_LAYER
- plane = GAME_PLANE
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
duration = 0 SECONDS
/obj/effect/temp_visual/wizard_rune/Initialize(mapload)
diff --git a/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm b/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm
index 009853bed22d1..6d08cd539fed5 100644
--- a/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm
+++ b/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm
@@ -21,7 +21,8 @@
anchored = TRUE
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_ATTACK_PAW
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
- layer = SIGIL_LAYER
+ plane = FLOOR_PLANE
+ layer = RUNE_LAYER
/// How many prior grand rituals have been completed?
var/potency = 0
/// Time to take per invocation of rune.
@@ -393,7 +394,7 @@
mergeable_decal = FALSE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
clean_type = CLEAN_TYPE_HARD_DECAL
- layer = SIGIL_LAYER
+ layer = RUNE_LAYER
/obj/effect/decal/cleanable/grand_remains/cheese
name = "cheese soot marks"
diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm
index 67b9a971bad5b..d0c99f03bbf06 100644
--- a/code/modules/antagonists/wizard/wizard.dm
+++ b/code/modules/antagonists/wizard/wizard.dm
@@ -15,6 +15,7 @@ GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key)
can_assign_self_objectives = TRUE
default_custom_objective = "Demonstrate your incredible and destructive magical powers."
hardcore_random_bonus = TRUE
+
var/give_objectives = TRUE
var/strip = TRUE //strip before equipping
var/allow_rename = TRUE
diff --git a/code/modules/art/statues.dm b/code/modules/art/statues.dm
index ff5cbc38f850d..eeb0cfa9cb432 100644
--- a/code/modules/art/statues.dm
+++ b/code/modules/art/statues.dm
@@ -348,7 +348,7 @@ Moving interrupts
//How long whole process takes
var/sculpting_time = 30 SECONDS
//Single interruptible progress period
- var/sculpting_period = round(sculpting_time / world.icon_size) //this is just so it reveals pixels line by line for each.
+ var/sculpting_period = round(sculpting_time / ICON_SIZE_Y) //this is just so it reveals pixels line by line for each.
var/interrupted = FALSE
var/remaining_time = sculpting_time - (prepared_block.completion * sculpting_time)
@@ -473,7 +473,7 @@ Moving interrupts
return FALSE
//No big icon things
var/list/icon_dimensions = get_icon_dimensions(target.icon)
- if(icon_dimensions["width"] != world.icon_size || icon_dimensions["height"] != world.icon_size)
+ if(icon_dimensions["width"] != ICON_SIZE_X || icon_dimensions["height"] != ICON_SIZE_Y)
user.balloon_alert(user, "sculpt target is too big!")
return FALSE
return TRUE
@@ -509,7 +509,7 @@ Moving interrupts
remove_filter("partial_uncover")
target_appearance_with_filters = null
else
- var/mask_offset = min(world.icon_size,round(completion * world.icon_size))
+ var/mask_offset = min(ICON_SIZE_Y,round(completion * ICON_SIZE_Y))
remove_filter("partial_uncover")
add_filter("partial_uncover", 1, alpha_mask_filter(icon = white, y = -mask_offset))
target_appearance_with_filters.filters = filter(type="alpha",icon=white,y=-mask_offset,flags=MASK_INVERSE)
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index 16a33b2a9a344..f0e9b5136ee8d 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -255,15 +255,15 @@
var/x_move = 0
var/y_move = 0
if(movement_dir & NORTH)
- y_move = -32
+ y_move = -ICON_SIZE_Y
else if(movement_dir & SOUTH)
- y_move = 32
+ y_move = ICON_SIZE_Y
if(movement_dir & WEST)
- x_move = 32
+ x_move = ICON_SIZE_X
else if(movement_dir & EAST)
- x_move = -32
+ x_move = -ICON_SIZE_X
- var/fake_glide_time = round(world.icon_size / glide_size * world.tick_lag, world.tick_lag)
+ var/fake_glide_time = round(ICON_SIZE_ALL / glide_size * world.tick_lag, world.tick_lag)
for(var/obj/effect/ebeam/beam as anything in active_beam?.elements)
var/matrix/base_transform = matrix(beam.transform)
beam.transform = beam.transform.Translate(x_move, y_move)
diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm
index bc302a188d825..39e9cf925da62 100644
--- a/code/modules/asset_cache/asset_list.dm
+++ b/code/modules/asset_cache/asset_list.dm
@@ -391,6 +391,11 @@ GLOBAL_LIST_EMPTY(asset_datums)
to_generate += list(args.Copy())
/datum/asset/spritesheet/proc/queuedInsert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
+#ifdef UNIT_TESTS
+ if (I && icon_state && !(icon_state in icon_states(I))) // check the base icon prior to extracting the state we want
+ stack_trace("Tried to insert nonexistent icon_state '[icon_state]' from [I] into spritesheet [name] ([type])")
+ return
+#endif
I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
if (!I || !length(icon_states(I))) // that direction or state doesn't exist
return
diff --git a/code/modules/asset_cache/assets/plumbing.dm b/code/modules/asset_cache/assets/plumbing.dm
index 73b1dfc7df57d..980a85e83b040 100644
--- a/code/modules/asset_cache/assets/plumbing.dm
+++ b/code/modules/asset_cache/assets/plumbing.dm
@@ -17,7 +17,6 @@
"synthesizer",
"reaction_chamber",
"grinder_chemical",
- "growing_vat",
"fermenter",
"pump",
"disposal",
diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
index 5f073d17fc1e2..1e9045d82279c 100644
--- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
+++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
@@ -99,13 +99,14 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
tlv_collection = list()
tlv_collection["pressure"] = new /datum/tlv/pressure
tlv_collection["temperature"] = new /datum/tlv/temperature
- var/list/meta_info = GLOB.meta_gas_info // shorthand
- for(var/gas_path in meta_info)
+
+ var/list/cached_gas_info = GLOB.meta_gas_info
+ for(var/datum/gas/gas_path as anything in cached_gas_info)
if(ispath(gas_path, /datum/gas/oxygen))
tlv_collection[gas_path] = new /datum/tlv/oxygen
else if(ispath(gas_path, /datum/gas/carbon_dioxide))
tlv_collection[gas_path] = new /datum/tlv/carbon_dioxide
- else if(meta_info[gas_path][META_GAS_DANGER])
+ else if(cached_gas_info[gas_path][META_GAS_DANGER])
tlv_collection[gas_path] = new /datum/tlv/dangerous
else
tlv_collection[gas_path] = new /datum/tlv/no_checks
@@ -137,6 +138,12 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
/obj/machinery/airalarm/Destroy()
if(my_area)
my_area = null
+ if(connected_sensor)
+ UnregisterSignal(connected_sensor, COMSIG_QDELETING)
+ UnregisterSignal(connected_sensor.loc, COMSIG_TURF_EXPOSE)
+ connected_sensor.connected_airalarm = null
+ connected_sensor = null
+
QDEL_NULL(alarm_manager)
GLOB.air_alarms -= src
return ..()
@@ -201,10 +208,16 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
return .
if(istype(multi_tool.buffer, /obj/machinery/air_sensor))
+ var/obj/machinery/air_sensor/sensor = multi_tool.buffer
+
if(!allow_link_change)
balloon_alert(user, "linking disabled")
return ITEM_INTERACT_BLOCKING
- connect_sensor(multi_tool.buffer)
+ if(connected_sensor || sensor.connected_airalarm)
+ balloon_alert(user, "sensor already connected!")
+ return ITEM_INTERACT_BLOCKING
+
+ connect_sensor(sensor)
balloon_alert(user, "connected sensor")
return ITEM_INTERACT_SUCCESS
@@ -568,34 +581,40 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
danger_level = max(danger_level, tlv_collection["pressure"].check_value(pressure))
danger_level = max(danger_level, tlv_collection["temperature"].check_value(temp))
if(total_moles)
- for(var/gas_path in GLOB.meta_gas_info)
+ var/list/cached_gas_info = GLOB.meta_gas_info
+ for(var/datum/gas/gas_path as anything in cached_gas_info)
var/moles = environment.gases[gas_path] ? environment.gases[gas_path][MOLES] : 0
danger_level = max(danger_level, tlv_collection[gas_path].check_value(pressure * moles / total_moles))
if(danger_level)
alarm_manager.send_alarm(ALARM_ATMOS)
- if(pressure <= tlv_collection["pressure"].hazard_min && temp <= tlv_collection["temperature"].hazard_min)
+ var/is_high_pressure = tlv_collection["pressure"].hazard_max != TLV_VALUE_IGNORE && pressure >= tlv_collection["pressure"].hazard_max
+ var/is_high_temp = tlv_collection["temperature"].hazard_max != TLV_VALUE_IGNORE && temp >= tlv_collection["temperature"].hazard_max
+ var/is_low_pressure = tlv_collection["pressure"].hazard_min != TLV_VALUE_IGNORE && pressure <= tlv_collection["pressure"].hazard_min
+ var/is_low_temp = tlv_collection["temperature"].hazard_min != TLV_VALUE_IGNORE && temp <= tlv_collection["temperature"].hazard_min
+
+ if(is_low_pressure && is_low_temp)
warning_message = "Danger! Low pressure and temperature detected."
return
- if(pressure <= tlv_collection["pressure"].hazard_min && temp >= tlv_collection["temperature"].hazard_max)
+ if(is_low_pressure && is_high_temp)
warning_message = "Danger! Low pressure and high temperature detected."
return
- if(pressure >= tlv_collection["pressure"].hazard_max && temp >= tlv_collection["temperature"].hazard_max)
+ if(is_high_pressure && is_high_temp)
warning_message = "Danger! High pressure and temperature detected."
return
- if(pressure >= tlv_collection["pressure"].hazard_max && temp <= tlv_collection["temperature"].hazard_min)
+ if(is_high_pressure && is_low_temp)
warning_message = "Danger! High pressure and low temperature detected."
return
- if(pressure <= tlv_collection["pressure"].hazard_min)
+ if(is_low_pressure)
warning_message = "Danger! Low pressure detected."
return
- if(pressure >= tlv_collection["pressure"].hazard_max)
+ if(is_high_pressure)
warning_message = "Danger! High pressure detected."
return
- if(temp <= tlv_collection["temperature"].hazard_min)
+ if(is_low_temp)
warning_message = "Danger! Low temperature detected."
return
- if(temp >= tlv_collection["temperature"].hazard_max)
+ if(is_high_temp)
warning_message = "Danger! High temperature detected."
return
else
@@ -686,14 +705,22 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/airalarm, 27)
if(isnull(sensor))
log_mapping("[src] at [AREACOORD(src)] tried to connect to a sensor, but no sensor with chamber_id:[air_sensor_chamber_id] found!")
return
+ if(connected_sensor)
+ log_mapping("[src] at [AREACOORD(src)] tried to connect to more than one sensor!")
+ return
connect_sensor(sensor)
///Used to connect air alarm with a sensor
/obj/machinery/airalarm/proc/connect_sensor(obj/machinery/air_sensor/sensor)
- if(!isnull(connected_sensor))
- UnregisterSignal(connected_sensor, COMSIG_QDELETING)
+ sensor.connected_airalarm = src
connected_sensor = sensor
+
RegisterSignal(connected_sensor, COMSIG_QDELETING, PROC_REF(disconnect_sensor))
+
+ // Transfer signal from air alarm to sensor
+ UnregisterSignal(loc, COMSIG_TURF_EXPOSE)
+ RegisterSignal(connected_sensor.loc, COMSIG_TURF_EXPOSE, PROC_REF(check_danger), override=TRUE)
+
my_area = get_area(connected_sensor)
check_enviroment()
@@ -704,6 +731,12 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/airalarm, 27)
///Used to reset the air alarm to default configuration after disconnecting from air sensor
/obj/machinery/airalarm/proc/disconnect_sensor()
UnregisterSignal(connected_sensor, COMSIG_QDELETING)
+
+ // Transfer signal from sensor to air alarm
+ UnregisterSignal(connected_sensor.loc, COMSIG_TURF_EXPOSE)
+ RegisterSignal(loc, COMSIG_TURF_EXPOSE, PROC_REF(check_danger), override=TRUE)
+
+ connected_sensor.connected_airalarm = null
connected_sensor = null
my_area = get_area(src)
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index 95758c22707c6..186ef8ea9aace 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -614,8 +614,8 @@
our_client.set_eye(target_move)
// Let's smooth out that movement with an animate yeah?
// If the new x is greater (move is left to right) we get a negative offset. vis versa
- our_client.pixel_x = (x - target_move.x) * world.icon_size
- our_client.pixel_y = (y - target_move.y) * world.icon_size
+ our_client.pixel_x = (x - target_move.x) * ICON_SIZE_X
+ our_client.pixel_y = (y - target_move.y) * ICON_SIZE_Y
animate(our_client, pixel_x = 0, pixel_y = 0, time = 0.05 SECONDS)
our_client.move_delay = world.time + 0.05 SECONDS
diff --git a/code/modules/atmospherics/machinery/components/tank.dm b/code/modules/atmospherics/machinery/components/tank.dm
index f76f3dc470f57..6aa23f84e934b 100644
--- a/code/modules/atmospherics/machinery/components/tank.dm
+++ b/code/modules/atmospherics/machinery/components/tank.dm
@@ -88,7 +88,6 @@
air_contents = new
air_contents.temperature = T20C
air_contents.volume = volume
- refresh_pressure_limit()
if(gas_type)
fill_to_pressure(gas_type)
@@ -121,7 +120,7 @@
. += span_notice("The pipe port can be moved or closed with a [wrench_hint].")
. += span_notice("A holographic sticker on it says that its maximum safe pressure is: [siunit_pressure(max_pressure, 0)].")
-/obj/machinery/atmospherics/components/tank/set_custom_materials(list/materials, multiplier)
+/obj/machinery/atmospherics/components/tank/finalize_material_effects(list/materials)
. = ..()
refresh_pressure_limit()
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm
index 804f868e9c189..2cde2acd0ace8 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm
@@ -48,6 +48,8 @@
var/allowed_pressure_error = ONE_ATMOSPHERE / 100
///Minimal distro pressure to start cycling
var/min_distro_pressure = ONE_ATMOSPHERE / 10
+ ///Which pressure holds docked vessel\station for override of external_pressure_target
+ var/docked_side_pressure
///Rate of the pump to remove gases from the air
var/volume_rate = 1000
///The start time of the current cycle to calculate cycle duration
@@ -125,7 +127,31 @@
/obj/machinery/atmospherics/components/unary/airlock_pump/post_machine_initialize()
. = ..()
set_links()
+ // If we are on docked shuttle - setup docking variables
+ // Example - 'build your own shuttle' evac vessel
+ var/turf/local_turf = get_turf(src)
+ if (!cycling_set_up || !isshuttleturf(local_turf))
+ return
+ var/tile_air_pressure
+ for(var/obj/machinery/door/airlock/external_airlock in external_airlocks)
+ var/current_area = get_area(external_airlock)
+ for(var/obj/machinery/door/airlock/other_airlock in orange(2, external_airlock)) // does not include src, extended because some escape pods have 1 plating turf exposed to space
+ if(get_area(other_airlock) != current_area) // does not include double-wide airlocks unless actually docked
+ // Cycle linking is only disabled if we are actually adjacent to another airlock
+ external_airlock.shuttledocked = TRUE
+ other_airlock.shuttledocked = TRUE
+ if (other_airlock.cycle_pump)
+ INVOKE_ASYNC(other_airlock.cycle_pump, TYPE_PROC_REF(/obj/machinery/atmospherics/components/unary/airlock_pump, on_dock_request), internal_pressure_target) // Only case when airlock pumps speaking to each other directly
+ // Save external airlocks turf in case our own docking purpouses
+ local_turf = get_turf(other_airlock)
+
+ if (local_turf)
+ local_turf = get_step(local_turf, REVERSE_DIR(dir))
+ tile_air_pressure = 0
+ if (local_turf)
+ tile_air_pressure = max(0, local_turf.return_air().return_pressure())
+ on_dock_request(tile_air_pressure)
/obj/machinery/atmospherics/components/unary/airlock_pump/New()
. = ..()
@@ -239,6 +265,22 @@
airlock.say("Airlock pair not found.")
return
if(airlock in external_airlocks)
+ // If it's not null - we shuttledocked
+ // (it may be 0. Maybe badmin set internal pressure to 0 as well, who knows)
+ if(docked_side_pressure != null)
+ // Space-faced airlock detection
+ var/turf/external_tile = get_step(airlock, REVERSE_DIR(dir))
+ // Map edge or space turf
+ if (external_tile == null || is_space_or_openspace(external_tile))
+ airlock.run_animation(DOOR_DENY_ANIMATION)
+ return
+ var/tile_air_pressure = max(0, external_tile.return_air().return_pressure())
+ var/pressure_delta = docked_side_pressure - tile_air_pressure
+ if (pressure_delta > 0 ? (pressure_delta > allowed_pressure_error*10) : (pressure_delta*-1 > allowed_pressure_error*10))
+ // Disabled to avoid airlocks close-open spam
+ airlock.run_animation(DOOR_DENY_ANIMATION)
+ return
+
start_cycle(ATMOS_DIRECTION_SIPHONING, airlock)
else if(airlock in internal_airlocks)
start_cycle(ATMOS_DIRECTION_RELEASING, airlock)
@@ -281,17 +323,12 @@
if(is_cycling_audible)
source_airlock.say("Pressurizing airlock.")
else
- cycle_pressure_target = external_pressure_target
+ cycle_pressure_target = docked_side_pressure != null ? docked_side_pressure : external_pressure_target
var/pressure_delta = tile_air_pressure - cycle_pressure_target
if(pressure_delta <= allowed_pressure_error)
stop_cycle("Pressure nominal, cycle skipped.")
return TRUE
- for(var/obj/machinery/door/airlock/airlock as anything in external_airlocks)
- if(airlock.shuttledocked)
- stop_cycle("Shuttle docked, cycle skipped.")
- return TRUE
-
if(!source_airlock)
source_airlock = external_airlocks[1]
if(is_cycling_audible)
@@ -307,6 +344,11 @@
return FALSE
on = FALSE
+ // In case we can open both sides safe_dock will do it for us
+ // it also handles its own messages. If we can't - procceed
+ if (docked_side_pressure != null && safe_dock(unbolt_only))
+ return TRUE
+
var/list/obj/machinery/door/airlock/unlocked_airlocks = pump_direction == ATMOS_DIRECTION_RELEASING ? internal_airlocks : external_airlocks
for(var/obj/machinery/door/airlock/airlock as anything in unlocked_airlocks)
airlock.unbolt()
@@ -323,6 +365,106 @@
update_appearance()
return TRUE
+/obj/machinery/atmospherics/components/unary/airlock_pump/proc/on_dock_request(requester_pressure = 0)
+ if (docked_side_pressure != null)
+ return
+
+ docked_side_pressure = requester_pressure
+
+ if (!powered() || !cycling_set_up)
+ return
+
+ // We just finishing previous cycle
+ if (airlocks_animating)
+ say("Docking request queued.")
+ stoplag(1.1 SECONDS) // Wait for opening animation
+ if (airlocks_animating) // Should (almost) never happened
+ say("ERROR: D11. Please re-initiate docking sequence.")
+ return
+
+ if (on)
+ // You can't go there, there is a shuttle now
+ if (pump_direction == ATMOS_DIRECTION_SIPHONING)
+ stop_cycle("Cycling sequence overriden by docking sequence.", TRUE)
+ start_cycle(ATMOS_DIRECTION_RELEASING)
+ // If cycling inside, docking will be handled by stop_cycle proc
+ return
+
+ // Check if we need cycle in
+ var/turf/local_turf = get_turf(src)
+ var/tile_air_pressure = max(0, local_turf.return_air().return_pressure())
+ var/pressure_delta = internal_pressure_target - tile_air_pressure
+ if(pressure_delta <= allowed_pressure_error)
+ // We fine
+ safe_dock()
+ else
+ var/obj/machinery/door/airlock/source_airlock = pick(internal_airlocks)
+ source_airlock.say("Docking sequence initiated")
+ start_cycle(ATMOS_DIRECTION_RELEASING)
+
+
+/obj/machinery/atmospherics/components/unary/airlock_pump/proc/safe_dock(unbolt_only = FALSE)
+ var/pressure_delta = internal_pressure_target - docked_side_pressure
+ // Docked vessel has pressure higher then our internal
+ if ((pressure_delta + allowed_pressure_error) < 0)
+ return FALSE
+ // Pressure is too different, its unsafe to open both sides
+ else if (pressure_delta > allowed_pressure_error * 10)
+ return FALSE
+ // No power handles by stop_cycle pretty good
+ else if (!powered())
+ return FALSE
+
+ var/turf/local_turf = get_turf(src)
+ var/tile_air_pressure = max(0, local_turf.return_air().return_pressure())
+ pressure_delta = internal_pressure_target - tile_air_pressure
+ // Chamber is not pressurised
+ if(pressure_delta > allowed_pressure_error)
+ return FALSE
+
+ for(var/obj/machinery/door/airlock/airlock as anything in (external_airlocks + internal_airlocks))
+ if (airlock in external_airlocks)
+ airlock.air_tight = TRUE
+ local_turf = get_step(airlock, REVERSE_DIR(dir))
+ // Map edge or space turf
+ if (local_turf == null || is_space_or_openspace(local_turf))
+ continue
+
+ tile_air_pressure = max(0, local_turf.return_air().return_pressure())
+ pressure_delta = docked_side_pressure - tile_air_pressure
+ // Do not open airlocks leading in space
+ // If docked entity now has pressure lower or higher then was declared on docking
+ // We will keep airlocks closed until redocking or fixing atmos
+ if (pressure_delta > 0 ? (pressure_delta > allowed_pressure_error*10) : (pressure_delta*-1 > allowed_pressure_error*10))
+ continue
+
+ airlock.unbolt()
+ if(open_airlock_on_cycle && !unbolt_only)
+ INVOKE_ASYNC(airlock, TYPE_PROC_REF(/obj/machinery/door/airlock, secure_open))
+
+ airlocks_animating = TRUE
+ stoplag(1 SECONDS) // Wait for closing animation
+ airlocks_animating = FALSE
+ update_appearance()
+ say("Docking complete.")
+ return TRUE
+
+
+/obj/machinery/atmospherics/components/unary/airlock_pump/proc/undock()
+ if (docked_side_pressure == null)
+ return
+ docked_side_pressure = null
+ if(!powered())
+ return
+
+ for(var/obj/machinery/door/airlock/airlock as anything in external_airlocks)
+ INVOKE_ASYNC(airlock, TYPE_PROC_REF(/obj/machinery/door/airlock, secure_close), TRUE)
+
+ say("Docking connection terminated.")
+ airlocks_animating = TRUE
+ stoplag(1 SECONDS) // Wait for closing animation
+ airlocks_animating = FALSE
+
///Update adjacent_turfs with atmospherically adjacent tiles
/obj/machinery/atmospherics/components/unary/airlock_pump/proc/check_turfs()
diff --git a/code/modules/awaymissions/away_props.dm b/code/modules/awaymissions/away_props.dm
index 843e55b963fe4..90fc3031088f2 100644
--- a/code/modules/awaymissions/away_props.dm
+++ b/code/modules/awaymissions/away_props.dm
@@ -137,3 +137,16 @@
if(!istype(mover))
return
return isnull(mover.ckey) == reverse
+
+/obj/effect/invisible_wall // why didnt we have this already
+ name = "invisible wall"
+ desc = "You shall not pass"
+ icon = 'icons/effects/mapping_helpers.dmi'
+ icon_state = "blocker"
+ color = COLOR_BLUE_LIGHT
+ invisibility = INVISIBILITY_MAXIMUM
+ anchored = TRUE
+
+/obj/effect/invisible_wall/CanAllowThrough(mob/living/mover, border_dir)
+ ..()
+ return FALSE // NO
diff --git a/code/modules/balloon_alert/balloon_alert.dm b/code/modules/balloon_alert/balloon_alert.dm
index db8c529198631..cf890c0130807 100644
--- a/code/modules/balloon_alert/balloon_alert.dm
+++ b/code/modules/balloon_alert/balloon_alert.dm
@@ -44,7 +44,7 @@
if (isnull(viewer_client))
return
- var/bound_width = world.icon_size
+ var/bound_width = ICON_SIZE_X
if (ismovable(src))
var/atom/movable/movable_source = src
bound_width = movable_source.bound_width
@@ -64,7 +64,7 @@
animate(
balloon_alert,
- pixel_y = world.icon_size * 1.2,
+ pixel_y = ICON_SIZE_Y * 1.2,
time = BALLOON_TEXT_TOTAL_LIFETIME(length_mult),
easing = SINE_EASING | EASE_OUT,
)
diff --git a/code/modules/bitrunning/objects/clothing.dm b/code/modules/bitrunning/objects/clothing.dm
index de2b6789d5812..731b7dc2cca58 100644
--- a/code/modules/bitrunning/objects/clothing.dm
+++ b/code/modules/bitrunning/objects/clothing.dm
@@ -8,3 +8,4 @@
name = "trenchcoat"
desc = "A long, black trenchcoat. Makes you feel like you're the one, but you're not."
icon_state = "trenchcoat"
+ flags_inv = HIDEBELT
diff --git a/code/modules/cargo/materials_market.dm b/code/modules/cargo/materials_market.dm
index 4037a51c6916b..dd3093a0aafec 100644
--- a/code/modules/cargo/materials_market.dm
+++ b/code/modules/cargo/materials_market.dm
@@ -143,7 +143,7 @@
trend_string = "down"
//get mat color
- var/initial_colors = initial(traded_mat.greyscale_colors)
+ var/initial_colors = initial(traded_mat.greyscale_color) || initial(traded_mat.color)
if(initial_colors)
color_string = splicetext(initial_colors, 7, length(initial_colors), "") //slice it to a standard 6 char hex
else
diff --git a/code/modules/cargo/packs/imports.dm b/code/modules/cargo/packs/imports.dm
index fc35e473f5ada..8720cb554fd43 100644
--- a/code/modules/cargo/packs/imports.dm
+++ b/code/modules/cargo/packs/imports.dm
@@ -143,7 +143,7 @@
/obj/item/gun/ballistic/automatic/wt550 = 2,
/obj/item/ammo_box/magazine/wt550m9 = 2,
)
- crate_type = /obj/structure/closet/crate/secure/gorlex_weapons/jammed
+ crate_type = /obj/structure/closet/crate/secure/syndicate/gorlex/weapons/bustedlock
/datum/supply_pack/imports/wt550ammo
name = "Smuggled WT-550 Ammo Crate"
@@ -156,7 +156,7 @@
/obj/item/ammo_box/magazine/wt550m9/wtic = 2,
)
crate_name = "emergency crate"
- crate_type = /obj/structure/closet/crate/secure/gorlex_weapons/jammed
+ crate_type = /obj/structure/closet/crate/secure/syndicate/gorlex/weapons/bustedlock
/datum/supply_pack/imports/shocktrooper
name = "Shocktrooper Crate"
@@ -172,7 +172,7 @@
/obj/item/clothing/suit/armor/vest,
/obj/item/clothing/head/helmet,
)
- crate_type = /obj/structure/closet/crate/secure/gorlex_weapons/jammed
+ crate_type = /obj/structure/closet/crate/secure/syndicate/gorlex/weapons/bustedlock
/datum/supply_pack/imports/specialops
name = "Special Ops Crate"
@@ -188,7 +188,7 @@
/obj/item/switchblade,
/obj/item/grenade/mirage = 5,
)
- crate_type = /obj/structure/closet/crate/secure/gorlex_weapons/jammed
+ crate_type = /obj/structure/closet/crate/secure/syndicate/gorlex/weapons/bustedlock
/datum/supply_pack/imports/russian
name = "Russian Surplus Military Gear Crate"
diff --git a/code/modules/client/preferences/README.md b/code/modules/client/preferences/README.md
index fabfb779c902b..674f234d48ef6 100644
--- a/code/modules/client/preferences/README.md
+++ b/code/modules/client/preferences/README.md
@@ -398,11 +398,11 @@ For inspiration, here is changeling's:
var/icon/final_icon = render_preview_outfit(/datum/outfit/changeling)
var/icon/split_icon = render_preview_outfit(/datum/outfit/job/engineer)
- final_icon.Shift(WEST, world.icon_size / 2)
- final_icon.Shift(EAST, world.icon_size / 2)
+ final_icon.Shift(WEST, ICON_SIZE_X / 2)
+ final_icon.Shift(EAST, ICON_SIZE_X / 2)
- split_icon.Shift(EAST, world.icon_size / 2)
- split_icon.Shift(WEST, world.icon_size / 2)
+ split_icon.Shift(EAST, ICON_SIZE_X / 2)
+ split_icon.Shift(WEST, ICON_SIZE_X / 2)
final_icon.Blend(split_icon, ICON_OVERLAY)
diff --git a/code/modules/client/preferences/species_features/felinid.dm b/code/modules/client/preferences/species_features/felinid.dm
index be90d806323d3..5910e42894f02 100644
--- a/code/modules/client/preferences/species_features/felinid.dm
+++ b/code/modules/client/preferences/species_features/felinid.dm
@@ -1,32 +1,32 @@
-/datum/preference/choiced/tail_human
- savefile_key = "feature_human_tail"
+/datum/preference/choiced/tail_felinid
+ savefile_key = "feature_human_tail" //savefile keys cannot be changed, blame whoever named them this way.
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
can_randomize = FALSE
relevant_external_organ = /obj/item/organ/external/tail/cat
-/datum/preference/choiced/tail_human/init_possible_values()
- return assoc_to_keys_features(SSaccessories.tails_list_human)
+/datum/preference/choiced/tail_felinid/init_possible_values()
+ return assoc_to_keys_features(SSaccessories.tails_list_felinid)
-/datum/preference/choiced/tail_human/apply_to_human(mob/living/carbon/human/target, value)
+/datum/preference/choiced/tail_felinid/apply_to_human(mob/living/carbon/human/target, value)
target.dna.features["tail_cat"] = value
-/datum/preference/choiced/tail_human/create_default_value()
- var/datum/sprite_accessory/tails/human/cat/tail = /datum/sprite_accessory/tails/human/cat
+/datum/preference/choiced/tail_felinid/create_default_value()
+ var/datum/sprite_accessory/tails/felinid/cat/tail = /datum/sprite_accessory/tails/felinid/cat
return initial(tail.name)
-/datum/preference/choiced/ears
- savefile_key = "feature_human_ears"
+/datum/preference/choiced/felinid_ears
+ savefile_key = "feature_human_ears" //savefile keys cannot be changed, blame whoever named them this way.
savefile_identifier = PREFERENCE_CHARACTER
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
can_randomize = FALSE
relevant_external_organ = /obj/item/organ/internal/ears/cat
-/datum/preference/choiced/ears/init_possible_values()
+/datum/preference/choiced/felinid_ears/init_possible_values()
return assoc_to_keys_features(SSaccessories.ears_list)
-/datum/preference/choiced/ears/apply_to_human(mob/living/carbon/human/target, value)
+/datum/preference/choiced/felinid_ears/apply_to_human(mob/living/carbon/human/target, value)
target.dna.features["ears"] = value
-/datum/preference/choiced/ears/create_default_value()
+/datum/preference/choiced/felinid_ears/create_default_value()
return /datum/sprite_accessory/ears/cat::name
diff --git a/code/modules/client/preferences/ui_style.dm b/code/modules/client/preferences/ui_style.dm
index 64a82b592c60a..fa002fd71b0e7 100644
--- a/code/modules/client/preferences/ui_style.dm
+++ b/code/modules/client/preferences/ui_style.dm
@@ -12,8 +12,8 @@
var/icon/icons = GLOB.available_ui_styles[value]
var/icon/icon = icon(icons, "hand_r")
- icon.Crop(1, 1, world.icon_size * 2, world.icon_size)
- icon.Blend(icon(icons, "hand_l"), ICON_OVERLAY, world.icon_size)
+ icon.Crop(1, 1, ICON_SIZE_X * 2, ICON_SIZE_Y)
+ icon.Blend(icon(icons, "hand_l"), ICON_OVERLAY, ICON_SIZE_X)
return icon
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index bd915cdf37a14..127840631d631 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -367,7 +367,7 @@ ADMIN_VERB(reset_ooc_color, R_FUN, "Reset Player OOC Color", "Returns player OOC
var/desired_width = 0
if(zoom_value)
- desired_width = round(view_size[1] * zoom_value * world.icon_size)
+ desired_width = round(view_size[1] * zoom_value * ICON_SIZE_X)
else
// Looks like we expect mapwindow.size to be "ixj" where i and j are numbers.
@@ -456,3 +456,9 @@ ADMIN_VERB(reset_ooc_color, R_FUN, "Reset Player OOC Color", "Returns player OOC
ASSERT(prefs, "User attempted to export preferences while preferences were null!") // what the fuck
prefs.savefile.export_json_to_client(usr, ckey)
+
+/client/verb/map_vote_tally_count()
+ set name = "Show Map Vote Tallies"
+ set desc = "View the current map vote tally counts."
+ set category = "Server"
+ to_chat(mob, SSmap_vote.tally_printout)
diff --git a/code/modules/clothing/masks/costume.dm b/code/modules/clothing/masks/costume.dm
index 844b823880ac4..ff980442565a4 100644
--- a/code/modules/clothing/masks/costume.dm
+++ b/code/modules/clothing/masks/costume.dm
@@ -38,6 +38,7 @@
icon_state = "kitsune"
inhand_icon_state = null
w_class = WEIGHT_CLASS_SMALL
+ adjusted_flags = ITEM_SLOT_HEAD
flags_inv = HIDEFACE|HIDEFACIALHAIR
custom_price = PAYCHECK_CREW
greyscale_colors = "#EEEEEE#AA0000"
@@ -45,6 +46,18 @@
greyscale_config_worn = /datum/greyscale_config/kitsune/worn
flags_1 = IS_PLAYER_COLORABLE_1
+/obj/item/clothing/mask/kitsune/examine(mob/user)
+ . = ..()
+ if(up)
+ . += "Use in-hand to wear as a mask!"
+ return
+ else
+ . += "Use in-hand to tie it up to wear as a hat!"
+
+/obj/item/clothing/mask/kitsune/attack_self(mob/user)
+ adjust_visor(user)
+ alternate_worn_layer = up ? ABOVE_BODY_FRONT_HEAD_LAYER : null
+
/obj/item/clothing/mask/rebellion
name = "rebellion mask"
desc = "Mask that is usually used during rebellions by insurgents. It covers the entire face and makes you unrecognizable."
diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm
index 6d92b80ebfd47..89d9a68530acb 100644
--- a/code/modules/clothing/masks/gasmask.dm
+++ b/code/modules/clothing/masks/gasmask.dm
@@ -25,19 +25,21 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
var/list/gas_filters
///Type of filter that spawns on roundstart
var/starting_filter_type = /obj/item/gas_filter
- ///Does the mask have an FOV?
- var/has_fov = TRUE
///Cigarette in the mask
var/obj/item/cigarette/cig
///How much does this mask affect fishing difficulty
var/fishing_modifier = 2
+ ///Applies clothing_dirt component to the pepperproof mask if true
+ var/pepper_tint = TRUE
/datum/armor/mask_gas
bio = 100
/obj/item/clothing/mask/gas/Initialize(mapload)
. = ..()
- init_fov()
+
+ if((flags_cover & PEPPERPROOF) && pepper_tint)
+ AddComponent(/datum/component/clothing_dirt)
if(fishing_modifier)
AddComponent(/datum/component/adjust_fishing_difficulty, fishing_modifier)
@@ -160,11 +162,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
has_filter = FALSE
return filtered_breath
-/// Initializes the FoV component for the gas mask
-/obj/item/clothing/mask/gas/proc/init_fov()
- if (has_fov)
- AddComponent(/datum/component/clothing_fov_visor, FOV_90_DEGREES)
-
/**
* Getter for overall filter durability, takes into consideration all filters filter_status
*/
@@ -267,7 +264,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
icon_state = "plaguedoctor"
flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT|HIDEHAIR
inhand_icon_state = "gas_mask"
- has_fov = FALSE
clothing_flags = BLOCK_GAS_SMOKE_EFFECT|MASKINTERNALS
/obj/item/clothing/mask/gas/syndicate
@@ -278,8 +274,8 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
resistance_flags = FIRE_PROOF | ACID_PROOF
strip_delay = 60
w_class = WEIGHT_CLASS_SMALL
- has_fov = FALSE
fishing_modifier = 0
+ pepper_tint = FALSE
/obj/item/clothing/mask/gas/clown_hat
name = "clown wig and mask"
@@ -296,7 +292,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
resistance_flags = FLAMMABLE
actions_types = list(/datum/action/item_action/adjust)
dog_fashion = /datum/dog_fashion/head/clown
- has_fov = FALSE
var/list/clownmask_designs = list()
voice_filter = null // performer masks expect to be talked through
fishing_modifier = 0
@@ -341,7 +336,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
righthand_file = 'icons/mob/inhands/clothing/hats_righthand.dmi'
flags_cover = MASKCOVERSEYES
resistance_flags = FLAMMABLE
- has_fov = FALSE
fishing_modifier = 0
/obj/item/clothing/mask/gas/mime
@@ -355,7 +349,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
resistance_flags = FLAMMABLE
actions_types = list(/datum/action/item_action/adjust)
species_exception = list(/datum/species/golem)
- has_fov = FALSE
fishing_modifier = 0
var/list/mimemask_designs = list()
@@ -400,7 +393,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
inhand_icon_state = "owl_mask"
flags_cover = MASKCOVERSEYES
resistance_flags = FLAMMABLE
- has_fov = FALSE
fishing_modifier = 0
/obj/item/clothing/mask/gas/sexymime
@@ -412,7 +404,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
flags_cover = MASKCOVERSEYES
resistance_flags = FLAMMABLE
species_exception = list(/datum/species/golem)
- has_fov = FALSE
fishing_modifier = 0
/obj/item/clothing/mask/gas/cyborg
@@ -420,7 +411,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
desc = "Beep boop."
icon_state = "death"
resistance_flags = FLAMMABLE
- has_fov = FALSE
flags_cover = MASKCOVERSEYES
fishing_modifier = 0
@@ -432,7 +422,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
clothing_flags = MASKINTERNALS
flags_cover = MASKCOVERSEYES
resistance_flags = FLAMMABLE
- has_fov = FALSE
fishing_modifier = -1
/obj/item/clothing/mask/gas/carp
@@ -440,7 +429,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
desc = "Gnash gnash."
icon_state = "carp_mask"
inhand_icon_state = null
- has_fov = FALSE
flags_cover = MASKCOVERSEYES
fishing_modifier = -3
@@ -451,7 +439,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
inhand_icon_state = null
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 1.25)
resistance_flags = FLAMMABLE
- has_fov = FALSE
flags_cover = MASKCOVERSEYES
max_integrity = 100
actions_types = list(/datum/action/item_action/adjust)
@@ -498,7 +485,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
inhand_icon_state = "gas_atmos"
resistance_flags = FIRE_PROOF | ACID_PROOF
flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEEARS|HIDEHAIR|HIDESNOUT
- has_fov = FALSE
fishing_modifier = -2
/obj/item/clothing/mask/gas/prop
@@ -509,7 +495,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
clothing_flags = NONE
flags_cover = MASKCOVERSMOUTH
resistance_flags = FLAMMABLE
- has_fov = FALSE
fishing_modifier = 0
/obj/item/clothing/mask/gas/atmosprop
@@ -521,7 +506,6 @@ GLOBAL_LIST_INIT(clown_mask_options, list(
clothing_flags = NONE
flags_cover = MASKCOVERSMOUTH
resistance_flags = FLAMMABLE
- has_fov = FALSE
fishing_modifier = 0
/obj/item/clothing/mask/gas/driscoll
diff --git a/code/modules/clothing/masks/hailer.dm b/code/modules/clothing/masks/hailer.dm
index aee1ac17b1b3e..182bc3ace7669 100644
--- a/code/modules/clothing/masks/hailer.dm
+++ b/code/modules/clothing/masks/hailer.dm
@@ -56,7 +56,6 @@ GLOBAL_LIST_INIT(hailer_phrases, list(
flags_cover = MASKCOVERSMOUTH
visor_flags_cover = MASKCOVERSMOUTH
tint = 0
- has_fov = FALSE
fishing_modifier = 0
unique_death = 'sound/items/sec_hailer/sec_death.ogg'
COOLDOWN_DECLARE(hailer_cooldown)
@@ -88,6 +87,7 @@ GLOBAL_LIST_INIT(hailer_phrases, list(
flags_cover = MASKCOVERSMOUTH | MASKCOVERSEYES | PEPPERPROOF
visor_flags_cover = MASKCOVERSMOUTH | MASKCOVERSEYES | PEPPERPROOF
fishing_modifier = 2
+ pepper_tint = FALSE
/obj/item/clothing/mask/gas/sechailer/swat/spacepol
name = "spacepol mask"
diff --git a/code/modules/clothing/outfits/plasmaman.dm b/code/modules/clothing/outfits/plasmaman.dm
index a0e927c631938..d6ab89cb8a855 100644
--- a/code/modules/clothing/outfits/plasmaman.dm
+++ b/code/modules/clothing/outfits/plasmaman.dm
@@ -4,7 +4,7 @@
uniform = /obj/item/clothing/under/plasmaman
gloves = /obj/item/clothing/gloves/color/plasmaman
head = /obj/item/clothing/head/helmet/space/plasmaman
- r_hand= /obj/item/tank/internals/plasmaman/belt/full
+ r_hand = /obj/item/tank/internals/plasmaman/belt/full
internals_slot = ITEM_SLOT_HANDS
/datum/outfit/plasmaman/security
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 88b388b47264d..53ab86b07718e 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -88,7 +88,7 @@
/obj/item/clothing/shoes/proc/restore_offsets(mob/user)
equipped_before_drop = FALSE
user.pixel_y -= offset
- worn_y_dimension = world.icon_size
+ worn_y_dimension = ICON_SIZE_Y
/obj/item/clothing/shoes/dropped(mob/user)
var/atom/movable/screen/alert/our_alert = our_alert_ref?.resolve()
diff --git a/code/modules/clothing/spacesuits/_spacesuits.dm b/code/modules/clothing/spacesuits/_spacesuits.dm
index 124dfcee41c0b..cd646d1df9ac2 100644
--- a/code/modules/clothing/spacesuits/_spacesuits.dm
+++ b/code/modules/clothing/spacesuits/_spacesuits.dm
@@ -93,6 +93,12 @@
if(fishing_modifier)
AddComponent(/datum/component/adjust_fishing_difficulty, fishing_modifier)
+/obj/item/clothing/suit/space/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot)
+ . = ..()
+ if(isnull(cell))
+ return
+ toggle_spacesuit(toggler = null, manual_toggle = FALSE) //turn on the thermal regulator by default.
+
/// Start Processing on the space suit when it is worn to heat the wearer
/obj/item/clothing/suit/space/equipped(mob/living/user, slot)
. = ..()
@@ -161,7 +167,10 @@
thermal_on = FALSE
// support for items that interact with the cell
-/obj/item/clothing/suit/space/get_cell()
+/obj/item/clothing/suit/space/get_cell(atom/movable/interface, mob/user)
+ if(istype(interface, /obj/item/inducer))
+ to_chat(user, span_alert("Error: unable to interface with [interface]."))
+ return null
return cell
// Show the status of the suit and the cell
diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm
index 88767b84b66d5..3de75657d337c 100644
--- a/code/modules/clothing/spacesuits/plasmamen.dm
+++ b/code/modules/clothing/spacesuits/plasmamen.dm
@@ -9,11 +9,10 @@
icon_state = "plasmaman_suit"
inhand_icon_state = "plasmaman_suit"
fishing_modifier = 0
- var/next_extinguish = 0
+ COOLDOWN_DECLARE(extinguish_timer)
var/extinguish_cooldown = 100
var/extinguishes_left = 10
-
/datum/armor/eva_plasmaman
bio = 100
fire = 100
@@ -23,21 +22,52 @@
. = ..()
. += span_notice("There [extinguishes_left == 1 ? "is" : "are"] [extinguishes_left] extinguisher charge\s left in this suit.")
+/obj/item/clothing/suit/space/eva/plasmaman/equipped(mob/living/user, slot)
+ . = ..()
+ if (slot & ITEM_SLOT_OCLOTHING)
+ RegisterSignals(user, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_LIVING_IGNITED, SIGNAL_ADDTRAIT(TRAIT_HEAD_ATMOS_SEALED)), PROC_REF(check_fire_state))
+ check_fire_state()
+
+/obj/item/clothing/suit/space/eva/plasmaman/dropped(mob/living/user)
+ . = ..()
+ UnregisterSignal(user, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_LIVING_IGNITED, SIGNAL_ADDTRAIT(TRAIT_HEAD_ATMOS_SEALED)))
+
+/obj/item/clothing/suit/space/eva/plasmaman/proc/check_fire_state(datum/source)
+ SIGNAL_HANDLER
+
+ if (!ishuman(loc))
+ return
+
+ // This is weird but basically we're calling this proc once the cooldown ends in case our wearer gets set on fire again during said cooldown
+ // This is why we're ignoring source and instead checking by loc
+ var/mob/living/carbon/human/owner = loc
+ if (!owner.on_fire || !owner.is_atmos_sealed(additional_flags = PLASMAMAN_PREVENT_IGNITION, check_hands = TRUE, ignore_chest_pressureprot = TRUE))
+ return
-/obj/item/clothing/suit/space/eva/plasmaman/proc/Extinguish(mob/living/carbon/human/H)
- if(!istype(H))
+ if (!extinguishes_left || !COOLDOWN_FINISHED(src, extinguish_timer))
return
- if(H.fire_stacks > 0)
- if(extinguishes_left)
- if(next_extinguish > world.time)
- return
- next_extinguish = world.time + extinguish_cooldown
- extinguishes_left--
- H.visible_message(span_warning("[H]'s suit automatically extinguishes [H.p_them()]!"),span_warning("Your suit automatically extinguishes you."))
- H.extinguish_mob()
- new /obj/effect/particle_effect/water(get_turf(H))
+ extinguishes_left -= 1
+ COOLDOWN_START(src, extinguish_timer, extinguish_cooldown)
+ // Check if our (possibly other) wearer is on fire once the cooldown ends
+ addtimer(CALLBACK(src, PROC_REF(check_fire_state)), extinguish_cooldown)
+ owner.visible_message(span_warning("[owner]'s suit automatically extinguishes [owner.p_them()]!"), span_warning("Your suit automatically extinguishes you."))
+ owner.extinguish_mob()
+ new /obj/effect/particle_effect/water(get_turf(owner))
+
+/obj/item/clothing/suit/space/eva/plasmaman/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ if (!istype(tool, /obj/item/extinguisher_refill))
+ return
+ if (extinguishes_left == 5)
+ to_chat(user, span_notice("The inbuilt extinguisher is full."))
+ return ITEM_INTERACT_BLOCKING
+
+ extinguishes_left = 5
+ to_chat(user, span_notice("You refill the suit's built-in extinguisher, using up the cartridge."))
+ check_fire_state()
+ qdel(tool)
+ return ITEM_INTERACT_SUCCESS
//I just want the light feature of helmets
/obj/item/clothing/head/helmet/space/plasmaman
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index 23e4d89ff2dbb..4569171c19a42 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -145,6 +145,10 @@
inhand_icon_state = "armor"
dog_fashion = null
+/obj/item/clothing/suit/armor/vest/cuirass/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/suit/armor/hos
name = "armored greatcoat"
desc = "A greatcoat enhanced with a special alloy for some extra protection and style for those with a commanding presence."
@@ -297,6 +301,10 @@
/obj/item/clothing/suit/armor/riot/Initialize(mapload)
. = ..()
AddComponent(/datum/component/adjust_fishing_difficulty, 5)
+ init_rustle_component()
+
+/obj/item/clothing/suit/armor/riot/proc/init_rustle_component()
+ AddComponent(/datum/component/item_equipped_movement_rustle)
/datum/armor/armor_riot
melee = 50
@@ -420,6 +428,10 @@
/obj/item/clothing/suit/armor/swat/Initialize(mapload)
. = ..()
AddComponent(/datum/component/adjust_fishing_difficulty, 5)
+ init_rustle_component()
+
+/obj/item/clothing/suit/armor/swat/proc/init_rustle_component()
+ AddComponent(/datum/component/item_equipped_movement_rustle)
//All of the armor below is mostly unused
@@ -519,6 +531,8 @@
/obj/item/tank/internals/emergency_oxygen,
/obj/item/tank/internals/plasmaman,
)
+/obj/item/clothing/suit/armor/riot/knight/init_rustle_component()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
/obj/item/clothing/suit/armor/riot/knight/yellow
icon_state = "knight_yellow"
diff --git a/code/modules/clothing/suits/bio.dm b/code/modules/clothing/suits/bio.dm
index 4fa5eeb5a90f7..25b28c74d1a7a 100644
--- a/code/modules/clothing/suits/bio.dm
+++ b/code/modules/clothing/suits/bio.dm
@@ -36,7 +36,7 @@
slowdown = 0.5
allowed = list(/obj/item/tank/internals, /obj/item/reagent_containers/dropper, /obj/item/flashlight/pen, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/cup/beaker, /obj/item/gun/syringe)
armor_type = /datum/armor/suit_bio_suit
- flags_inv = HIDEGLOVES|HIDEJUMPSUIT
+ flags_inv = HIDEGLOVES|HIDEJUMPSUIT|HIDEBELT
strip_delay = 70
equip_delay_other = 70
resistance_flags = ACID_PROOF
diff --git a/code/modules/clothing/suits/costume.dm b/code/modules/clothing/suits/costume.dm
index a720f49b9383f..d3918752056d1 100644
--- a/code/modules/clothing/suits/costume.dm
+++ b/code/modules/clothing/suits/costume.dm
@@ -121,7 +121,7 @@
icon_state = "imperium_monk"
inhand_icon_state = "imperium_monk"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- flags_inv = HIDESHOES|HIDEJUMPSUIT
+ flags_inv = HIDESHOES|HIDEJUMPSUIT|HIDEBELT
allowed = list(/obj/item/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/cup/glass/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/flashlight/flare/candle, /obj/item/tank/internals/emergency_oxygen)
/obj/item/clothing/suit/costume/chickensuit
@@ -219,6 +219,7 @@
icon_state = "classicponcho"
inhand_icon_state = null
species_exception = list(/datum/species/golem)
+ flags_inv = HIDEBELT
/obj/item/clothing/suit/costume/poncho/green
name = "green poncho"
@@ -248,7 +249,7 @@
icon_state = "white_dress"
inhand_icon_state = "w_suit"
body_parts_covered = CHEST|GROIN|LEGS|FEET
- flags_inv = HIDEJUMPSUIT|HIDESHOES
+ flags_inv = HIDEJUMPSUIT|HIDESHOES|HIDEBELT
/obj/item/clothing/suit/hooded/carp_costume
name = "carp costume"
@@ -503,6 +504,7 @@
desc = "Perfect for those who want to stalk around a corner of a bar."
icon_state = "gothcoat"
inhand_icon_state = null
+ flags_inv = HIDEBELT
/obj/item/clothing/suit/costume/xenos
name = "xenos suit"
@@ -510,7 +512,7 @@
icon_state = "xenos"
inhand_icon_state = "xenos_suit"
body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS
- flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT
+ flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDEBELT
allowed = list(/obj/item/clothing/mask/facehugger/toy)
/obj/item/clothing/suit/costume/nemes
diff --git a/code/modules/clothing/suits/ghostsheet.dm b/code/modules/clothing/suits/ghostsheet.dm
index 965adc9b7e2ff..52c19be3bd160 100644
--- a/code/modules/clothing/suits/ghostsheet.dm
+++ b/code/modules/clothing/suits/ghostsheet.dm
@@ -7,7 +7,7 @@
throw_speed = 1
throw_range = 2
w_class = WEIGHT_CLASS_TINY
- flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
+ flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT|HIDEBELT|HIDEJUMPSUIT
alternate_worn_layer = UNDER_HEAD_LAYER
species_exception = list(/datum/species/golem)
supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON
@@ -34,7 +34,7 @@
throw_speed = 1
throw_range = 2
w_class = WEIGHT_CLASS_TINY
- flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
+ flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT|HIDEBELT|HIDEJUMPSUIT
species_exception = list(/datum/species/golem)
supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON
alternate_worn_layer = ABOVE_BODY_FRONT_LAYER //so the bedsheet goes over everything but fire
diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm
index 2ba150ab692ee..504558229639c 100644
--- a/code/modules/clothing/suits/jobs.dm
+++ b/code/modules/clothing/suits/jobs.dm
@@ -124,6 +124,7 @@
armor_type = /datum/armor/jacket_det_suit
cold_protection = CHEST|GROIN|ARMS
heat_protection = CHEST|GROIN|ARMS
+ flags_inv = HIDEBELT
/datum/armor/jacket_det_suit
melee = 25
diff --git a/code/modules/clothing/under/accessories/badges.dm b/code/modules/clothing/under/accessories/badges.dm
index 335eded4d4c82..9d7d87a084687 100644
--- a/code/modules/clothing/under/accessories/badges.dm
+++ b/code/modules/clothing/under/accessories/badges.dm
@@ -4,6 +4,10 @@
desc = "Fills you with the conviction of JUSTICE. Lawyers tend to want to show it to everyone they meet."
icon_state = "lawyerbadge"
+/obj/item/clothing/accessory/lawyers_badge/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/bubble_icon_override, "lawyer", BUBBLE_ICON_PRIORITY_ACCESSORY)
+
/obj/item/clothing/accessory/lawyers_badge/interact(mob/user)
. = ..()
if(prob(1))
@@ -12,11 +16,9 @@
/obj/item/clothing/accessory/lawyers_badge/accessory_equipped(obj/item/clothing/under/clothes, mob/living/user)
RegisterSignal(user, COMSIG_LIVING_SLAM_TABLE, PROC_REF(table_slam))
- user.bubble_icon = "lawyer"
/obj/item/clothing/accessory/lawyers_badge/accessory_dropped(obj/item/clothing/under/clothes, mob/living/user)
UnregisterSignal(user, COMSIG_LIVING_SLAM_TABLE)
- user.bubble_icon = initial(user.bubble_icon)
/obj/item/clothing/accessory/lawyers_badge/proc/table_slam(mob/living/source, obj/structure/table/the_table)
SIGNAL_HANDLER
diff --git a/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm b/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm
index ee70fbb3d6ab6..1d2166653aef3 100644
--- a/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm
+++ b/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm
@@ -11,7 +11,7 @@
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
can_adjust = FALSE
strip_delay = 80
- var/next_extinguish = 0
+ COOLDOWN_DECLARE(extinguish_timer)
var/extinguish_cooldown = 100
var/extinguishes_left = 5
@@ -22,31 +22,54 @@
/obj/item/clothing/under/plasmaman/examine(mob/user)
. = ..()
- . += span_notice("There are [extinguishes_left] extinguisher charges left in this suit.")
+ . += span_notice("There [extinguishes_left == 1 ? "is" : "are"] [extinguishes_left] extinguisher charges left in this suit.")
-/obj/item/clothing/under/plasmaman/proc/Extinguish(mob/living/carbon/human/H)
- if(!istype(H))
+/obj/item/clothing/under/plasmaman/equipped(mob/living/user, slot)
+ . = ..()
+ if (slot & ITEM_SLOT_ICLOTHING)
+ RegisterSignals(user, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_LIVING_IGNITED, SIGNAL_ADDTRAIT(TRAIT_HEAD_ATMOS_SEALED)), PROC_REF(check_fire_state))
+ check_fire_state()
+
+/obj/item/clothing/under/plasmaman/dropped(mob/living/user)
+ . = ..()
+ UnregisterSignal(user, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_LIVING_IGNITED, SIGNAL_ADDTRAIT(TRAIT_HEAD_ATMOS_SEALED)))
+
+/obj/item/clothing/under/plasmaman/proc/check_fire_state(datum/source)
+ SIGNAL_HANDLER
+
+ if (!ishuman(loc))
+ return
+
+ // This is weird but basically we're calling this proc once the cooldown ends in case our wearer gets set on fire again during said cooldown
+ // This is why we're ignoring source and instead checking by loc
+ var/mob/living/carbon/human/owner = loc
+ if (!owner.on_fire || !owner.is_atmos_sealed(additional_flags = PLASMAMAN_PREVENT_IGNITION, check_hands = TRUE, ignore_chest_pressureprot = TRUE))
+ return
+
+ if (!extinguishes_left || !COOLDOWN_FINISHED(src, extinguish_timer))
return
- if(H.on_fire)
- if(extinguishes_left)
- if(next_extinguish > world.time)
- return
- next_extinguish = world.time + extinguish_cooldown
- extinguishes_left--
- H.visible_message(span_warning("[H]'s suit automatically extinguishes [H.p_them()]!"),span_warning("Your suit automatically extinguishes you."))
- H.extinguish_mob()
- new /obj/effect/particle_effect/water(get_turf(H))
-
-/obj/item/clothing/under/plasmaman/attackby(obj/item/E, mob/user, params)
- ..()
- if (istype(E, /obj/item/extinguisher_refill))
- if (extinguishes_left == 5)
- to_chat(user, span_notice("The inbuilt extinguisher is full."))
- else
- extinguishes_left = 5
- to_chat(user, span_notice("You refill the suit's built-in extinguisher, using up the cartridge."))
- qdel(E)
+ extinguishes_left -= 1
+ COOLDOWN_START(src, extinguish_timer, extinguish_cooldown)
+ // Check if our (possibly other) wearer is on fire once the cooldown ends
+ addtimer(CALLBACK(src, PROC_REF(check_fire_state)), extinguish_cooldown)
+ owner.visible_message(span_warning("[owner]'s suit automatically extinguishes [owner.p_them()]!"), span_warning("Your suit automatically extinguishes you."))
+ owner.extinguish_mob()
+ new /obj/effect/particle_effect/water(get_turf(owner))
+
+/obj/item/clothing/under/plasmaman/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ if (!istype(tool, /obj/item/extinguisher_refill))
+ return
+
+ if (extinguishes_left == 5)
+ to_chat(user, span_notice("The inbuilt extinguisher is full."))
+ return ITEM_INTERACT_BLOCKING
+
+ extinguishes_left = 5
+ to_chat(user, span_notice("You refill the suit's built-in extinguisher, using up the cartridge."))
+ check_fire_state()
+ qdel(tool)
+ return ITEM_INTERACT_SUCCESS
/obj/item/extinguisher_refill
name = "envirosuit extinguisher cartridge"
@@ -54,7 +77,6 @@
icon_state = "plasmarefill"
icon = 'icons/obj/canisters.dmi'
-
/obj/item/clothing/under/plasmaman/cargo
name = "cargo plasma envirosuit"
desc = "A joint envirosuit used by plasmamen quartermasters and cargo techs alike, due to the logistical problems of differenciating the two with the length of their pant legs."
@@ -134,20 +156,27 @@
sensor_mode = SENSOR_COORDS
random_sensor = FALSE
-/obj/item/clothing/under/plasmaman/clown/Extinguish(mob/living/carbon/human/H)
- if(!istype(H))
+/obj/item/clothing/under/plasmaman/clown/check_fire_state(datum/source, datum/status_effect/fire_handler/status_effect)
+ if (!ishuman(loc))
+ return
+
+ // This is weird but basically we're calling this proc once the cooldown ends in case our wearer gets set on fire again during said cooldown
+ // This is why we're ignoring source and instead checking by loc
+ var/mob/living/carbon/human/owner = loc
+ if (!owner.on_fire || !owner.is_atmos_sealed(additional_flags = PLASMAMAN_PREVENT_IGNITION, check_hands = TRUE, ignore_chest_pressureprot = TRUE))
+ return
+
+ if (!extinguishes_left || !COOLDOWN_FINISHED(src, extinguish_timer))
return
- if(H.on_fire)
- if(extinguishes_left)
- if(next_extinguish > world.time)
- return
- next_extinguish = world.time + extinguish_cooldown
- extinguishes_left--
- H.visible_message(span_warning("[H]'s suit spews space lube everywhere!"),span_warning("Your suit spews space lube everywhere!"))
- H.extinguish_mob()
- var/datum/effect_system/fluid_spread/foam/foam = new
- var/datum/reagents/foamreagent = new /datum/reagents(15)
- foamreagent.add_reagent(/datum/reagent/lube, 15)
- foam.set_up(4, holder = src, location = H.loc, carry = foamreagent)
- foam.start() //Truly terrifying.
+ extinguishes_left -= 1
+ COOLDOWN_START(src, extinguish_timer, extinguish_cooldown)
+ // Check if our (possibly other) wearer is on fire once the cooldown ends
+ addtimer(CALLBACK(src, PROC_REF(check_fire_state)), extinguish_cooldown)
+ owner.visible_message(span_warning("[owner]'s suit spews space lube everywhere!"), span_warning("Your suit spews space lube everywhere!"))
+ owner.extinguish_mob()
+ var/datum/effect_system/fluid_spread/foam/foam = new
+ var/datum/reagents/foamreagent = new /datum/reagents(15)
+ foamreagent.add_reagent(/datum/reagent/lube, 15)
+ foam.set_up(4, holder = src, location = get_turf(owner), carry = foamreagent)
+ foam.start() //Truly terrifying.
diff --git a/code/modules/deathmatch/deathmatch_loadouts.dm b/code/modules/deathmatch/deathmatch_loadouts.dm
index 1e3d792f67b53..5670995512001 100644
--- a/code/modules/deathmatch/deathmatch_loadouts.dm
+++ b/code/modules/deathmatch/deathmatch_loadouts.dm
@@ -606,7 +606,7 @@
l_hand = /obj/item/melee/energy/sword
r_pocket = /obj/item/reagent_containers/hypospray/medipen/stimulants
l_pocket = /obj/item/soap/syndie
- belt = /obj/item/gun/ballistic/revolver/syndicate
+ belt = /obj/item/gun/ballistic/revolver
/datum/outfit/deathmatch_loadout/nukie
name = "Deathmatch: Nuclear Operative"
@@ -768,7 +768,7 @@
id_trim = /datum/id_trim/job/bridge_assistant // half tider half command
id = /obj/item/card/id/advanced/chameleon
uniform = /obj/item/clothing/under/trek/command/next
- l_pocket = /obj/item/gun/energy/e_gun/mini
+ l_pocket = /obj/item/gun/energy/e_gun/mini // they are thej best race in the end. not as impactful as you may think
r_pocket = /obj/item/extinguisher/mini
gloves = /obj/item/clothing/gloves/fingerless
belt = /obj/item/storage/belt/utility/full/inducer
@@ -846,10 +846,11 @@
back = /obj/item/storage/backpack/science
backpack_contents = list(
- /obj/item/dnainjector/shock,
/obj/item/etherealballdeployer,
)
+ mutations_to_add = list(/obj/item/dnainjector/shock) // pretend ethereals are interesting
+
/datum/outfit/deathmatch_loadout/plasmamen
name = "Deathmatch: Plasmaman Species"
display_name = "Plasmamen"
@@ -907,16 +908,10 @@
uniform = /obj/item/clothing/under/pants/jeans
suit = /obj/item/clothing/suit/costume/wellworn_shirt/graphic
- back = /obj/item/storage/backpack
+ r_pocket = /obj/item/stack/rods/twentyfive
+ l_pocket = /obj/item/stack/rods/twentyfive
r_hand = /obj/item/wirecutters
- backpack_contents = list(
- /obj/item/stack/rods/fifty,
- /obj/item/stack/rods/fifty,
- /obj/item/stack/rods/fifty,
- /obj/item/stack/rods/fifty,
- )
-
// We don't want them to just punch each other to death
/datum/outfit/deathmatch_loadout/lattice_battles/pre_equip(mob/living/carbon/human/user, visualsOnly)
@@ -983,14 +978,12 @@
back = /obj/item/storage/backpack/cultpack
backpack_contents = list(
- /obj/item/cult_shift,
/obj/item/reagent_containers/cup/beaker/unholywater,
/obj/item/reagent_containers/cup/beaker/unholywater,
/obj/item/reagent_containers/cup/beaker/unholywater,
)
spells_to_add = list(
- /datum/action/innate/cult/blood_spell/horror,
/datum/action/innate/cult/blood_spell/horror,
/datum/action/innate/cult/blood_spell/stun,
/datum/action/innate/cult/blood_spell/stun,
@@ -999,7 +992,7 @@
/datum/outfit/deathmatch_loadout/cultish/artificer/post_equip(mob/living/carbon/human/user, visualsOnly)
. = ..()
- var/datum/action/innate/cult/blood_spell/manipulation/magick = locate() in user
+ var/datum/action/innate/cult/blood_spell/manipulation/magick = locate() in user.get_all_contents()
magick.charges = 300
/datum/outfit/deathmatch_loadout/heresy
@@ -1018,13 +1011,10 @@
// Heretic Warrior
-// Has spells of Ash, Blade, and Rust. Overall aggressive
-
/datum/outfit/deathmatch_loadout/heresy/warrior
name = "Deathmatch: Heretic Warrior"
display_name = "Heretic Warrior"
- desc = "Prove the furious strength of the Mansus."
- //species_override = /datum/species/plasmaman
+ desc = "Prove the furious strength of the Mansus!"
head = /obj/item/clothing/head/hooded/cult_hoodie/eldritch
neck = /obj/item/clothing/neck/heretic_focus
@@ -1032,7 +1022,7 @@
suit_store = /obj/item/melee/sickly_blade/dark
uniform = /obj/item/clothing/under/color/darkgreen
id_trim = null
- belt = /obj/item/melee/sickly_blade/ash
+ belt = /obj/item/melee/sickly_blade/rust
gloves = null
shoes = /obj/item/clothing/shoes/sandal
l_pocket = /obj/item/flashlight/lantern/jade/on
@@ -1061,16 +1051,12 @@
/datum/action/cooldown/spell/touch/mansus_grasp,
/datum/action/cooldown/spell/realignment,
/datum/action/cooldown/spell/pointed/projectile/furious_steel,
- /datum/action/cooldown/spell/charged/beam/fire_blast,
- /datum/action/cooldown/spell/aoe/fiery_rebirth,
/datum/action/cooldown/spell/cone/staggered/entropic_plume,
/datum/action/cooldown/spell/pointed/rust_construction,
)
// Heretic Scribe
-// Has spells of Void, Moon, and Cosmos. Overall defensive/mobile
-
/datum/outfit/deathmatch_loadout/heresy/scribe
name = "Deathmatch: Heretic Scribe"
display_name = "Heretic Scribe"
@@ -1087,29 +1073,27 @@
gloves = null
shoes = /obj/item/clothing/shoes/winterboots/ice_boots
l_pocket = /obj/item/ammo_box/strilka310/lionhunter
- r_pocket = /obj/item/codex_cicatrix
+ r_pocket = /obj/item/ammo_box/strilka310/lionhunter
back = /obj/item/gun/ballistic/rifle/lionhunter // for his neutral b, he wields a gun
belt_contents = list(
/obj/item/heretic_labyrinth_handbook,
/obj/item/heretic_labyrinth_handbook,
- /obj/item/eldritch_potion/crucible_soul,
- /obj/item/clothing/neck/heretic_focus/moon_amulet = 3,
+ /obj/item/eldritch_potion/duskndawn,
+ /obj/item/eldritch_potion/duskndawn,
)
knowledge_to_grant = list(
/datum/heretic_knowledge/cosmic_grasp,
/datum/heretic_knowledge/moon_grasp,
- /datum/heretic_knowledge/mark/moon_mark,
)
spells_to_add = list(
/datum/action/cooldown/spell/touch/mansus_grasp,
- /datum/action/cooldown/spell/conjure/cosmic_expansion,
/datum/action/cooldown/spell/pointed/projectile/star_blast,
/datum/action/cooldown/spell/touch/star_touch,
- /datum/action/cooldown/spell/cone/staggered/cone_of_cold/void,
+ /datum/action/cooldown/spell/pointed/mind_gate,
/datum/action/cooldown/spell/aoe/void_pull,
)
@@ -1127,7 +1111,7 @@
suit_store = /obj/item/book/bible/booze
uniform = /obj/item/clothing/under/rank/civilian/chaplain
id_trim = null
- belt = /obj/item/nullrod // choose any!
+ belt = /obj/item/nullrod/non_station // choose any!
gloves = /obj/item/clothing/gloves/plate
shoes = /obj/item/clothing/shoes/plate
l_pocket = /obj/item/flashlight/lantern/on
@@ -1150,7 +1134,6 @@
name = "Deathmatch: Clock Cultist"
display_name = "Rat'var Apostate"
desc = "You're in a fight between the servants of gods, and yours is dead. Good luck?"
- //species_override = /datum/species/plasmaman
head = /obj/item/clothing/head/costume/bronze
suit = /obj/item/clothing/suit/costume/bronze
@@ -1160,5 +1143,5 @@
belt = /obj/item/brass_spear
gloves = /obj/item/clothing/gloves/tinkerer
shoes = /obj/item/clothing/shoes/bronze
- l_pocket = /obj/item/reagent_containers/cup/beaker/synthflesh // they used to turn their dmg into tox with a spell. close enough
- r_pocket = /obj/item/reagent_containers/cup/beaker/synthflesh
+ l_pocket = /obj/item/reagent_containers/cup/beaker/synthflesh/named // they used to turn their dmg into tox with a spell. close enough
+ r_pocket = /obj/item/reagent_containers/cup/beaker/synthflesh/named
diff --git a/code/modules/deathmatch/deathmatch_mapping.dm b/code/modules/deathmatch/deathmatch_mapping.dm
index b83419490be76..a0651f7da121b 100644
--- a/code/modules/deathmatch/deathmatch_mapping.dm
+++ b/code/modules/deathmatch/deathmatch_mapping.dm
@@ -31,3 +31,4 @@
icon_state = /turf/open/floor/wood::icon_state
base_icon_state = /turf/open/floor/wood::base_icon_state
icon = /turf/open/floor/wood::icon
+ smoothing_flags = NONE
diff --git a/code/modules/discord/tgs_commands.dm b/code/modules/discord/tgs_commands.dm
index 42d6d19ba99a7..8c6796bc1ea16 100644
--- a/code/modules/discord/tgs_commands.dm
+++ b/code/modules/discord/tgs_commands.dm
@@ -4,7 +4,7 @@
/datum/tgs_chat_command/tgscheck/Run(datum/tgs_chat_user/sender, params)
var/server = CONFIG_GET(string/server)
- return new /datum/tgs_message_content("[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]")
+ return new /datum/tgs_message_content("[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.current_map.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]")
/datum/tgs_chat_command/gameversion
name = "gameversion"
diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm
index 1cf617ce4513e..6585f92f9b61c 100644
--- a/code/modules/error_handler/error_handler.dm
+++ b/code/modules/error_handler/error_handler.dm
@@ -26,7 +26,9 @@ GLOBAL_VAR_INIT(total_runtimes_skipped, 0)
Reboot(reason = 1)
return
- var/static/regex/stack_workaround = regex("[WORKAROUND_IDENTIFIER](.+?)[WORKAROUND_IDENTIFIER]")
+ var/static/regex/stack_workaround
+ if(isnull(stack_workaround))
+ stack_workaround = regex("[WORKAROUND_IDENTIFIER](.+?)[WORKAROUND_IDENTIFIER]")
var/static/list/error_last_seen = list()
var/static/list/error_cooldown = list() /* Error_cooldown items will either be positive(cooldown time) or negative(silenced error)
If negative, starts at -1, and goes down by 1 each time that error gets skipped*/
diff --git a/code/modules/escape_menu/details.dm b/code/modules/escape_menu/details.dm
index 49bd19ce97fd7..ab6ff05d3ea46 100644
--- a/code/modules/escape_menu/details.dm
+++ b/code/modules/escape_menu/details.dm
@@ -35,7 +35,7 @@ GLOBAL_DATUM(escape_menu_details, /atom/movable/screen/escape_menu/details)
Round ID: [GLOB.round_id || "Unset"]
Round Time: [ROUND_TIME()]
- Map: [SSmapping.config?.map_name || "Loading..."]
+ Map: [SSmapping.current_map.map_name || "Loading..."]
Time Dilation: [round(SStime_track.time_dilation_current,1)]%
"}
diff --git a/code/modules/events/meteors/dark_matteor_event.dm b/code/modules/events/meteors/dark_matteor_event.dm
index 412354b16f13d..a2352a1927e01 100644
--- a/code/modules/events/meteors/dark_matteor_event.dm
+++ b/code/modules/events/meteors/dark_matteor_event.dm
@@ -22,7 +22,7 @@
target = potential_target
break
//if target was never chosen the target is null aka the matteor will act as spacedust (and can technically miss)
- spawn_meteor(list(/obj/effect/meteor/dark_matteor = 1), null, target)
+ spawn_meteor(list(/obj/effect/meteor/dark_matteor = 1), null, target, distance_from_edge = 10)
/datum/round_event/dark_matteor/announce(fake)
priority_announce("Warning. Excessive tampering of meteor satellites has attracted a dark matt-eor. Signature approaching [GLOB.station_name]. Please brace for impact.", "Meteor Alert", 'sound/announcer/alarm/airraid.ogg')
diff --git a/code/modules/fishing/bait.dm b/code/modules/fishing/bait.dm
index 9bd36daa7bebd..ec758be704201 100644
--- a/code/modules/fishing/bait.dm
+++ b/code/modules/fishing/bait.dm
@@ -147,7 +147,7 @@
name = "plastic dropping"
desc = "A fishing lure to catch all sort of slimy, ratty, disgusting and/or junk-loving fish."
icon_state = "dropping"
- spin_frequency = list(1.5 SECONDS, 2.5 SECONDS)
+ spin_frequency = list(1.5 SECONDS, 2.8 SECONDS)
/obj/item/fishing_lure/dropping/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties)
var/list/sources = list(/datum/fish_source/toilet, /datum/fish_source/moisture_trap)
@@ -167,7 +167,7 @@
name = "\improper Indy spoon lure"
desc = "A lustrous piece of metal mimicking the scales of a fish. Good for catching small to medium freshwater omnivore fish."
icon_state = "spoon"
- spin_frequency = list(1.25 SECONDS, 2.1 SECONDS)
+ spin_frequency = list(1.25 SECONDS, 2.25 SECONDS)
/obj/item/fishing_lure/spoon/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties)
var/avg_size = initial(fish_type.average_size)
@@ -187,7 +187,7 @@
name = "\improper Silkbuzz artificial fly"
desc = "A fishing lure resembling a large wooly fly. Good for catching all sort of picky fish."
icon_state = "artificial_fly"
- spin_frequency = list(1.1 SECONDS, 1.9 SECONDS)
+ spin_frequency = list(1.1 SECONDS, 2 SECONDS)
/obj/item/fishing_lure/artificial_fly/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties)
var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS]
@@ -199,7 +199,7 @@
name = "\improper LED fishing lure"
desc = "A heavy, waterproof and fish-looking LED stick, used to catch abyssal and demersal fish alike."
icon_state = "led"
- spin_frequency = list(3 SECONDS, 3.75 SECONDS)
+ spin_frequency = list(3 SECONDS, 3.8 SECONDS)
/obj/item/fishing_lure/led/Initialize(mapload)
. = ..()
@@ -220,7 +220,7 @@
name = "\improper Maneki-Coin lure"
desc = "A faux-gold lure used to attract shiny-loving fish."
icon_state = "lucky_coin"
- spin_frequency = list(1.5 SECONDS, 2.5 SECONDS)
+ spin_frequency = list(1.5 SECONDS, 2.7 SECONDS)
/obj/item/fishing_lure/lucky_coin/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties)
var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS]
@@ -244,7 +244,7 @@
name = "\improper Twister Worm lure"
desc = "A soft plastic lure with the body of a grub and a twisting tail. Good for panfish and other small omnivore fish."
icon_state = "grub"
- spin_frequency = list(1 SECONDS, 2.5 SECONDS)
+ spin_frequency = list(1 SECONDS, 2.7 SECONDS)
/obj/item/fishing_lure/grub/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties)
if(initial(fish_type.average_size) >= FISH_SIZE_SMALL_MAX)
@@ -258,7 +258,7 @@
name = "\improper Electric-Buzz lure"
desc = "A metallic, colored clanked attached to a series of cables that somehow attract shock-worthy fish."
icon_state = "buzzbait"
- spin_frequency = list(0.8 SECONDS, 1.6 SECONDS)
+ spin_frequency = list(0.8 SECONDS, 1.7 SECONDS)
/obj/item/fishing_lure/buzzbait/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties)
var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS]
diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm
index c97669c41155f..4a8efadcbcc9b 100644
--- a/code/modules/fishing/fish/_fish.dm
+++ b/code/modules/fishing/fish/_fish.dm
@@ -173,6 +173,9 @@
ADD_TRAIT(src, TRAIT_UNCOMPOSTABLE, REF(src)) //Composting a food that is not real food wouldn't work anyway.
START_PROCESSING(SSobj, src)
+ //Adding this because not all fish have the gore foodtype that makes them automatically eligible for dna infusion.
+ ADD_TRAIT(src, TRAIT_VALID_DNA_INFUSION, INNATE_TRAIT)
+
//stops new fish from being able to reproduce right away.
breeding_wait = world.time + (breeding_timeout * NEW_FISH_BREEDING_TIMEOUT_MULT)
last_feeding = world.time - (feeding_frequency * NEW_FISH_LAST_FEEDING_MULT)
@@ -181,7 +184,6 @@
apply_traits() //Make sure traits are applied before size and weight.
update_size_and_weight()
- register_evolutions()
register_context()
register_item_context()
@@ -674,7 +676,9 @@
continue
if(length(fish_traits & trait.incompatible_traits))
continue
- if((trait_type in same_traits) ? prob(trait.inheritability) : prob(trait.diff_traits_inheritability))
+ // If there's no partner, we've been reated through parthenogenesis or growth, therefore, traits are copied
+ // Otherwise, we do some probability checks.
+ if(!y_traits || ((trait_type in same_traits) ? prob(trait.inheritability) : prob(trait.diff_traits_inheritability)))
fish_traits |= trait_type
incompatible_traits |= trait.incompatible_traits
@@ -685,11 +689,6 @@
var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait_type]
trait.apply_to_fish(src)
-/obj/item/fish/proc/register_evolutions()
- for(var/evolution_type in evolution_types)
- var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type]
- evolution.register_fish(src)
-
/obj/item/fish/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
check_flopping()
@@ -718,17 +717,28 @@
/obj/item/fish/proc/feed(datum/reagents/fed_reagents)
if(status != FISH_ALIVE)
return
- var/fed_reagent_type
+
+ ///If one of the reagent with fish effects is also our food reagent this is set to TRUE
+ var/already_fed = FALSE
+ for(var/datum/reagent/reagent as anything in fed_reagents.reagent_list)
+ if(!fed_reagents.has_reagent(reagent.type, 0.1) || !reagent.used_on_fish(src))
+ continue
+ fed_reagents.remove_reagent(reagent.type, 0.1)
+ if(reagent.type == food)
+ already_fed = TRUE
+
+ if(already_fed)
+ sate_hunger()
+ return
+
if(fed_reagents.remove_reagent(food, 0.1))
- fed_reagent_type = food
sate_hunger()
- else
- var/datum/reagent/wrong_reagent = pick(fed_reagents.reagent_list)
- if(!wrong_reagent)
- return
- fed_reagent_type = wrong_reagent.type
- fed_reagents.remove_reagent(fed_reagent_type, 0.1)
- SEND_SIGNAL(src, COMSIG_FISH_FED, fed_reagents, fed_reagent_type)
+ return
+
+ var/datum/reagent/wrong_reagent = pick(fed_reagents.reagent_list)
+ if(!wrong_reagent)
+ return
+ fed_reagents.remove_reagent(wrong_reagent.type, 0.1)
/**
* Base multiplier of the difference between current size and weight and their maximum value
@@ -759,11 +769,14 @@
hunger_mult = 1 - (hunger - FISH_GROWTH_PEAK) * 4
if(hunger_mult <= 0)
return
+ var/base_mult = FISH_GROWTH_MULT
+ if(HAS_TRAIT(src, TRAIT_FISH_QUICK_GROWTH))
+ base_mult *= 2.5
if(size < maximum_size)
- new_size += CEILING((maximum_size - size) * FISH_GROWTH_MULT / (w_class * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1)
+ new_size += CEILING((maximum_size - size) * base_mult / (w_class * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1)
new_size = min(new_size, maximum_size)
if(weight < maximum_weight)
- new_weight += CEILING((maximum_weight - weight) * FISH_GROWTH_MULT / (get_weight_rank() * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1)
+ new_weight += CEILING((maximum_weight - weight) * base_mult / (get_weight_rank() * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1)
new_weight = min(new_weight, maximum_weight)
if(new_size != size || new_weight != weight)
update_size_and_weight(new_size, new_weight)
@@ -1012,6 +1025,9 @@
health_change_per_second -= 0.5 //Starving
else
health_change_per_second += 0.5 //Slowly healing
+ if(HAS_TRAIT(src, TRAIT_FISH_ON_TESLIUM))
+ health_change_per_second -= 0.65 //This becomes - 0.15 if safe and not starving.
+
adjust_health(health + health_change_per_second * seconds_per_tick)
/obj/item/fish/proc/adjust_health(amount)
@@ -1221,6 +1237,8 @@
if(istype(loc, /obj/structure/aquarium/bioelec_gen))
fish_zap_range = 5
fish_zap_power = GET_FISH_ELECTROGENESIS(src)
+ if(HAS_TRAIT(src, TRAIT_FISH_ON_TESLIUM))
+ fish_zap_power *= 0.5
fish_zap_flags |= (ZAP_GENERATES_POWER | ZAP_MOB_STUN)
tesla_zap(source = get_turf(src), zap_range = fish_zap_range, power = fish_zap_power, cutoff = 1 MEGA JOULES, zap_flags = fish_zap_flags)
@@ -1302,6 +1320,28 @@
/obj/item/fish/proc/undo_petted()
fish_flags &= ~FISH_FLAG_PETTED
+/obj/item/fish/update_atom_colour()
+ . = ..()
+ aquarium_vc_color = color || initial(aquarium_vc_color)
+
+/obj/item/fish/get_infusion_entry()
+ var/amphibious = required_fluid_type == AQUARIUM_FLUID_AIR || HAS_TRAIT(src, TRAIT_FISH_AMPHIBIOUS)
+ var/list/possible_infusions = list()
+ for(var/type in fish_traits)
+ var/datum/fish_trait/trait = GLOB.fish_traits[type]
+ if(!trait.infusion_entry)
+ continue
+ possible_infusions |= trait.infusion_entry
+ if(!length(possible_infusions) && !amphibious)
+ return GLOB.infuser_entries[/datum/infuser_entry/fish]
+ var/datum/infuser_entry/fish/entry = new
+ if(amphibious)
+ entry.output_organs -= /obj/item/organ/internal/lungs/fish
+ for(var/key in possible_infusions)
+ var/datum/infuser_entry/infusion = GLOB.infuser_entries[key]
+ entry.output_organs |= infusion.output_organs
+ return entry
+
/// Returns random fish, using random_case_rarity probabilities.
/proc/random_fish_type(required_fluid)
var/static/probability_table
diff --git a/code/modules/fishing/fish/fish_evolution.dm b/code/modules/fishing/fish/fish_evolution.dm
index 49eb3f46374bd..52708add566da 100644
--- a/code/modules/fishing/fish/fish_evolution.dm
+++ b/code/modules/fishing/fish/fish_evolution.dm
@@ -48,6 +48,8 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution)
if(aquarium)
//chances are halved if only one parent has this evolution.
var/real_probability = (mate && (type in mate.evolution_types)) ? probability : probability/2
+ if(HAS_TRAIT(source, TRAIT_FISH_MUTAGENIC) || (mate && HAS_TRAIT(mate, TRAIT_FISH_MUTAGENIC)))
+ real_probability *= 3
if(!prob(real_probability))
return FALSE
if(!ISINRANGE(aquarium.fluid_temp, required_temperature_min, required_temperature_max))
@@ -82,25 +84,12 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution)
. += " [conditions_note]"
return .
-///Proc called to let evolution register signals that are needed for various conditions.
-/datum/fish_evolution/proc/register_fish(obj/item/fish/fish)
- return
-
/datum/fish_evolution/lubefish
probability = 25
new_fish_type = /obj/item/fish/clownfish/lube
new_traits = list(/datum/fish_trait/lubed)
conditions_note = "The fish must be fed lube beforehand."
-/datum/fish_evolution/lubefish/register_fish(obj/item/fish/fish)
- RegisterSignal(fish, COMSIG_FISH_FED, PROC_REF(check_for_lube))
-
-/datum/fish_evolution/lubefish/proc/check_for_lube(obj/item/fish/source, datum/reagents/fed_reagents, wrong_reagent_type)
- SIGNAL_HANDLER
- if((wrong_reagent_type == /datum/reagent/lube) || fed_reagents.remove_reagent(/datum/reagent/lube, 0.1))
- ADD_TRAIT(source, TRAIT_FISH_FED_LUBE, FISH_EVOLUTION)
- addtimer(TRAIT_CALLBACK_REMOVE(source, TRAIT_FISH_FED_LUBE, FISH_EVOLUTION), source.feeding_frequency)
-
/datum/fish_evolution/lubefish/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium)
if(!HAS_TRAIT(source, TRAIT_FISH_FED_LUBE))
return FALSE
@@ -167,6 +156,7 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution)
/datum/fish_evolution/fritterish
new_fish_type = /obj/item/fish/fryish/fritterish
+ removed_traits = list(/datum/fish_trait/no_mating)
conditions_note = "Fryish will grow into it over time."
/datum/fish_evolution/nessie
diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm
index 22c7492fb8eaf..2030c1b61585b 100644
--- a/code/modules/fishing/fish/fish_traits.dm
+++ b/code/modules/fishing/fish/fish_traits.dm
@@ -39,6 +39,8 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
var/added_difficulty = 0
/// Reagents to add to the fish whenever the COMSIG_GENERATE_REAGENTS_TO_ADD signal is sent. Their values will be multiplied later.
var/list/reagents_to_add
+ /// If set, the fish may return this infusion entry when get_infusion_entry is called instead of /datum/infuser_entry/fish
+ var/infusion_entry
/// Difficulty modifier from this mod, needs to return a list with two values
/datum/fish_trait/proc/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
@@ -336,7 +338,11 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
name = "Mateless"
catalog_description = "This fish cannot reproduce with other fishes."
incompatible_traits = list(/datum/fish_trait/crossbreeder)
- spontaneous_manifest_types = list(/obj/item/fish/fryish = 100)
+ spontaneous_manifest_types = list(
+ /obj/item/fish/fryish = 100,
+ /obj/item/fish/fryish/fritterish = 0,
+ /obj/item/fish/fryish/nessie = 0
+ )
/datum/fish_trait/no_mating/apply_to_fish(obj/item/fish/fish)
. = ..()
@@ -423,6 +429,9 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
catalog_description = "This fish contains toxins. Feeding it to predatory fishes or people is not recommended."
diff_traits_inheritability = 25
reagents_to_add = list(/datum/reagent/toxin/tetrodotoxin = 1)
+ infusion_entry = /datum/infuser_entry/ttx_healing
+ ///The amount of venom injected if the fish has a stinger is multiplied by this value.
+ var/venom_mult = 1
/datum/fish_trait/toxic/apply_to_fish(obj/item/fish/fish)
. = ..()
@@ -434,13 +443,13 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
SIGNAL_HANDLER
if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
return
- add_venom(source, /datum/reagent/toxin/tetrodotoxin, new_weight, mult = source.status == FISH_DEAD ? 0.1 : 0.25)
+ add_venom(source, reagents_to_add[1], new_weight, mult = (source.status == FISH_DEAD ? 0.1 : 0.25) * venom_mult)
/datum/fish_trait/toxic/proc/on_status_change(obj/item/fish/source)
SIGNAL_HANDLER
if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
return
- change_venom_on_death(source, /datum/reagent/toxin/tetrodotoxin, 0.25, 0.1)
+ change_venom_on_death(source, reagents_to_add[1], 0.25 * venom_mult, 0.1 * venom_mult)
/datum/fish_trait/toxic/proc/on_eaten(obj/item/fish/source, obj/item/fish/predator)
if(HAS_TRAIT(predator, TRAIT_FISH_TOXIN_IMMUNE))
@@ -459,7 +468,15 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
/datum/fish_trait/toxic/apply_to_mob(mob/living/basic/mob)
. = ..()
- mob.AddElement(/datum/element/venomous, /datum/reagent/toxin/tetrodotoxin, 0.5 * mob.mob_size)
+ mob.AddElement(/datum/element/venomous, reagents_to_add[1], 0.5 * mob.mob_size * venom_mult)
+
+/datum/fish_trait/toxic/carpotoxin
+ name = "Carpotoxic"
+ catalog_description = "This fish contains carpotoxin. Definitely not safe for consumption."
+ diff_traits_inheritability = 50
+ reagents_to_add = list(/datum/reagent/toxin/carpotoxin = 4)
+ infusion_entry = null
+ venom_mult = 6
/datum/fish_trait/toxin_immunity
name = "Toxin Immunity"
@@ -530,6 +547,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
inheritability = 80
diff_traits_inheritability = 40
catalog_description = "This fish has developed a primitive adaptation to life on both land and water."
+ infusion_entry = /datum/infuser_entry/amphibious
/datum/fish_trait/amphibious/apply_to_fish(obj/item/fish/fish)
. = ..()
@@ -640,7 +658,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
/datum/fish_trait/stunted
name = "Stunted Growth"
catalog_description = "This chrab's development is stunted, and will not properly reach adulthood."
- spontaneous_manifest_types = list(/obj/item/fish/chasm_crab = 12, /obj/item/fish/chasm_crab/ice = 12)
+ spontaneous_manifest_types = list(/obj/item/fish/chasm_crab = 12)
fish_whitelist = list(/obj/item/fish/chasm_crab, /obj/item/fish/chasm_crab/ice)
diff_traits_inheritability = 40
@@ -708,6 +726,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
catalog_description = "This fish possess a sac that produces ink."
diff_traits_inheritability = 70
spontaneous_manifest_types = list(/obj/item/fish/squid = 35)
+ infusion_entry = /datum/infuser_entry/squid
/datum/fish_trait/ink/apply_to_fish(obj/item/fish/fish)
. = ..()
diff --git a/code/modules/fishing/fish/types/air_space.dm b/code/modules/fishing/fish/types/air_space.dm
index dda3794ff4e1c..177ae9c6e0e7d 100644
--- a/code/modules/fishing/fish/types/air_space.dm
+++ b/code/modules/fishing/fish/types/air_space.dm
@@ -99,3 +99,85 @@
///It spins, and dimly glows in the dark.
/obj/item/fish/starfish/flop_animation()
DO_FLOATING_ANIM(src)
+
+/obj/item/fish/baby_carp
+ name = "baby space carp"
+ desc = "A juvenile spawn of the dreaded space carp. Don't let the innocent looks fool you, they're aggressive little bastards."
+ icon_state = "baby_carp"
+ sprite_height = 3
+ sprite_width = 5
+ average_size = 35
+ average_weight = 550
+ stable_population = 7
+ required_fluid_type = AQUARIUM_FLUID_ANY_WATER
+ random_case_rarity = FISH_RARITY_VERY_RARE
+ required_temperature_min = 0
+ required_temperature_max = MIN_AQUARIUM_TEMP+200
+ safe_air_limits = null
+ fillet_type = /obj/item/food/fishmeat/carp/no_tox
+ fish_traits = list(
+ /datum/fish_trait/carnivore,
+ /datum/fish_trait/aggressive,
+ /datum/fish_trait/predator,
+ /datum/fish_trait/necrophage,
+ /datum/fish_trait/no_mating,
+ /datum/fish_trait/toxic/carpotoxin,
+ )
+ favorite_bait = list(
+ list(
+ FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE,
+ FISH_BAIT_VALUE = MEAT,
+ ),
+ )
+ disliked_bait = list(
+ list(
+ FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE,
+ FISH_BAIT_VALUE = GRAIN|DAIRY,
+ ),
+ )
+ beauty = FISH_BEAUTY_GREAT
+
+/obj/item/fish/baby_carp/Initialize(mapload, apply_qualities = TRUE)
+ color = pick_weight(GLOB.carp_colors)
+ . = ..()
+ RegisterSignal(src, COMSIG_FISH_BEFORE_GROWING, PROC_REF(growth_checks))
+ RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth))
+ update_appearance(UPDATE_OVERLAYS)
+
+/obj/item/fish/baby_carp/update_overlays()
+ . = ..()
+ var/mutable_appearance/eyes = mutable_appearance(icon, "baby_carp_eyes")
+ if(status == FISH_DEAD)
+ eyes.icon_state += "_dead"
+ else
+ eyes.appearance_flags = RESET_COLOR
+ . += eyes
+
+///Determines the speed at which the carp grows based on how big it's
+/obj/item/fish/baby_carp/update_size_and_weight(new_size = average_size, new_weight = average_weight)
+ . = ..()
+ var/growth_rate = 4.5 MINUTES
+ growth_rate *= clamp(size/average_size, 0.5, 2)
+ growth_rate *= clamp(weight/average_weight, 0.5, 2)
+
+ AddComponent(/datum/component/fish_growth, /mob/living/basic/carp/advanced, growth_rate)
+
+/obj/item/fish/baby_carp/proc/growth_checks(datum/source, seconds_per_tick)
+ SIGNAL_HANDLER
+ var/hunger = CLAMP01((world.time - last_feeding) / feeding_frequency)
+ if(health <= initial(health) * 0.6 || hunger >= 0.6) //if too hurt or hungry, don't grow.
+ return COMPONENT_DONT_GROW
+
+ if(!isaquarium(loc))
+ return
+
+ var/obj/structure/aquarium/aquarium = loc
+ if(!aquarium.reproduction_and_growth) //the aquarium has breeding disabled
+ return COMPONENT_DONT_GROW
+ if(length(aquarium.get_fishes()) > AQUARIUM_MAX_BREEDING_POPULATION * 0.5) //check if there's enough room to maturate.
+ return COMPONENT_DONT_GROW
+
+/obj/item/fish/baby_carp/proc/on_growth(datum/source, mob/living/basic/carp/result)
+ SIGNAL_HANDLER
+ //yes, this means that if we use a spraycan on the fish, the resulting space carp will be of spraycan color
+ result.set_greyscale(colors = list(color))
diff --git a/code/modules/fishing/fish/types/freshwater.dm b/code/modules/fishing/fish/types/freshwater.dm
index fe0ff437c7d8c..75d4891b4f0ad 100644
--- a/code/modules/fishing/fish/types/freshwater.dm
+++ b/code/modules/fishing/fish/types/freshwater.dm
@@ -175,7 +175,7 @@
/obj/item/fish/tadpole/Initialize(mapload, apply_qualities = TRUE)
. = ..()
- AddComponent(/datum/component/fish_growth, /mob/living/basic/frog, rand(2.5, 3 MINUTES))
+ AddComponent(/datum/component/fish_growth, /mob/living/basic/frog, rand(2 MINUTES, 3 MINUTES))
RegisterSignal(src, COMSIG_FISH_BEFORE_GROWING, PROC_REF(growth_checks))
RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth))
diff --git a/code/modules/fishing/fish/types/station.dm b/code/modules/fishing/fish/types/station.dm
index 70e641fa3e900..e811996d5af04 100644
--- a/code/modules/fishing/fish/types/station.dm
+++ b/code/modules/fishing/fish/types/station.dm
@@ -138,7 +138,7 @@
///The evolution datum of the next thing we grow into, given time (and food)
var/next_type = /datum/fish_evolution/fritterish
///How long does it take for us to grow up?
- var/growth_time = 5 MINUTES
+ var/growth_time = 3.5 MINUTES
/obj/item/fish/fryish/Initialize(mapload)
. = ..()
@@ -184,7 +184,7 @@
fish_movement_type = /datum/fish_movement
is_bait = FALSE
next_type = /datum/fish_evolution/nessie
- growth_time = 10 MINUTES
+ growth_time = 8 MINUTES
/obj/item/fish/fryish/fritterish/Initialize(mapload, apply_qualities = TRUE)
. = ..()
diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm
index 7e46618accf72..ccad3b4708017 100644
--- a/code/modules/fishing/fishing_equipment.dm
+++ b/code/modules/fishing/fishing_equipment.dm
@@ -387,7 +387,7 @@
name = "fishing tip"
desc = "A slip of paper containing a pearl of wisdom about fishing within it, though you wish it were an actual pearl."
-/obj/item/paper/paperslip/fortune/Initialize(mapload)
+/obj/item/paper/paperslip/fishing_tip/Initialize(mapload)
default_raw_text = pick(GLOB.fishing_tips)
return ..()
diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm
index bf4cd463ef1ab..30ce2b33ad0cc 100644
--- a/code/modules/fishing/fishing_minigame.dm
+++ b/code/modules/fishing/fishing_minigame.dm
@@ -271,7 +271,7 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user)
return
if(phase == WAIT_PHASE)
if(world.time < last_baiting_click + 0.25 SECONDS)
- return //Don't punish players if they accidentally double clicked.
+ return COMSIG_MOB_CANCEL_CLICKON //Don't punish players if they accidentally double clicked.
if(float.spin_frequency)
if(!float.spin_ready)
send_alert("too early!")
@@ -360,6 +360,7 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user)
playsound(location, 'sound/effects/fish_splash.ogg', 100)
if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH))
+ fish_icon = GLOB.specific_fish_icons[reward_path] || FISH_ICON_DEF
switch(fish_icon)
if(FISH_ICON_DEF)
send_alert("fish!!!")
@@ -466,9 +467,6 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user)
if(difficulty > FISHING_DEFAULT_DIFFICULTY)
completion -= MAX_FISH_COMPLETION_MALUS * (difficulty * 0.01)
- if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH))
- fish_icon = GLOB.specific_fish_icons[reward_path] || FISH_ICON_DEF
-
/// Fish minigame properties
if(ispath(reward_path,/obj/item/fish))
var/obj/item/fish/fish = reward_path
diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm
index 23aabcc3ece7c..bfa701d36eb0a 100644
--- a/code/modules/fishing/fishing_rod.dm
+++ b/code/modules/fishing/fishing_rod.dm
@@ -69,6 +69,11 @@
update_appearance()
+ //Bane effect that make it extra-effective against mobs with water adaptation (read: fish infusion)
+ AddElement(/datum/element/bane, target_type = /mob/living, damage_multiplier = 1.25)
+ RegisterSignal(src, COMSIG_OBJECT_PRE_BANING, PROC_REF(attempt_bane))
+ RegisterSignal(src, COMSIG_OBJECT_ON_BANING, PROC_REF(bane_effects))
+
/obj/item/fishing_rod/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(src == held_item)
if(currently_hooked)
@@ -135,6 +140,19 @@
QDEL_NULL(bait)
update_icon()
+///Fishing rodss should only bane fish DNA-infused spessman
+/obj/item/fishing_rod/proc/attempt_bane(datum/source, mob/living/fish)
+ SIGNAL_HANDLER
+ if(!force || !HAS_TRAIT(fish, TRAIT_WATER_ADAPTATION))
+ return COMPONENT_CANCEL_BANING
+
+///Fishing rods should hard-counter fish DNA-infused spessman
+/obj/item/fishing_rod/proc/bane_effects(datum/source, mob/living/fish)
+ SIGNAL_HANDLER
+ fish.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH, 4 SECONDS)
+ fish.adjust_confusion_up_to(1.5 SECONDS, 3 SECONDS)
+ fish.adjust_wet_stacks(-4)
+
/obj/item/fishing_rod/interact(mob/user)
if(currently_hooked)
reel(user)
@@ -503,6 +521,11 @@
line = null
show_in_wiki = FALSE
+///From the mining order console, meant to help miners rescue their fallen brethren
+/obj/item/fishing_rod/rescue
+ hook = /obj/item/fishing_hook/rescue
+ show_in_wiki = FALSE
+
/obj/item/fishing_rod/bone
name = "bone fishing rod"
desc = "A humble rod, made with whatever happened to be on hand."
diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm
index 38455068ce22e..c2db0a43fc9b5 100644
--- a/code/modules/fishing/sources/_fish_source.dm
+++ b/code/modules/fishing/sources/_fish_source.dm
@@ -41,6 +41,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons())
/obj/item/fish/stingray = FISH_ICON_WEAPON,
/obj/item/fish/swordfish = FISH_ICON_WEAPON,
/obj/item/fish/zipzap = FISH_ICON_ELECTRIC,
+ /obj/item/knife/carp = FISH_ICON_WEAPON,
/obj/item/seeds/grass = FISH_ICON_SEED,
/obj/item/seeds/random = FISH_ICON_SEED,
/obj/item/storage/wallet = FISH_ICON_COIN,
diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm
index 2f56ffaad3cd1..f6cbd969a8d6d 100644
--- a/code/modules/fishing/sources/source_types.dm
+++ b/code/modules/fishing/sources/source_types.dm
@@ -193,6 +193,7 @@
fish_table = list(
FISHING_DUD = 5,
/obj/item/fish/starfish = 6,
+ /obj/item/fish/baby_carp = 6,
/obj/item/stack/ore/bluespace_crystal = 2,
/mob/living/basic/carp = 2,
)
@@ -353,9 +354,6 @@
/datum/fish_source/lavaland/reason_we_cant_fish(obj/item/fishing_rod/rod, mob/fisherman, atom/parent)
. = ..()
- var/turf/approx = get_turf(fisherman) //todo pass the parent
- if(!SSmapping.level_trait(approx.z, ZTRAIT_MINING))
- return "There doesn't seem to be anything to catch here."
if(!rod.line || !(rod.line.fishing_line_traits & FISHING_LINE_REINFORCED))
return "You'll need reinforced fishing line to fish in there"
@@ -580,6 +578,25 @@
var/picked_path = pick(seeds_to_draw_from)
return new picked_path(get_turf(fishing_spot))
+/datum/fish_source/carp_rift
+ catalog_description = "Space Dragon Rifts"
+ radial_state = "carp"
+ fish_table = list(
+ FISHING_DUD = 3,
+ /obj/item/fish/baby_carp = 5,
+ /mob/living/basic/carp = 1,
+ /mob/living/basic/carp/passive = 1,
+ /mob/living/basic/carp/mega = 1,
+ /obj/item/clothing/head/fedora/carpskin = 1,
+ /obj/item/toy/plush/carpplushie = 1,
+ /obj/item/toy/plush/carpplushie/dehy_carp/peaceful = 1,
+ /obj/item/knife/carp = 1,
+ )
+ fish_counts = list(
+ /mob/living/basic/carp/mega = 2,
+ )
+ fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 18
+
/datum/fish_source/deepfryer
catalog_description = "Deep Fryers"
radial_state = "fryer"
diff --git a/code/modules/food_and_drinks/machinery/griddle.dm b/code/modules/food_and_drinks/machinery/griddle.dm
index e0c45e6c9af10..ed1884c89af27 100644
--- a/code/modules/food_and_drinks/machinery/griddle.dm
+++ b/code/modules/food_and_drinks/machinery/griddle.dm
@@ -73,8 +73,8 @@
return
if(user.transferItemToLoc(I, src, silent = FALSE))
//Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf)
- I.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(world.icon_size/2), world.icon_size/2)
- I.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(world.icon_size/2), world.icon_size/2)
+ I.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2)
+ I.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2)
to_chat(user, span_notice("You place [I] on [src]."))
AddToGrill(I, user)
else
diff --git a/code/modules/food_and_drinks/machinery/stove_component.dm b/code/modules/food_and_drinks/machinery/stove_component.dm
index c5e32b541995e..76f52345c8c35 100644
--- a/code/modules/food_and_drinks/machinery/stove_component.dm
+++ b/code/modules/food_and_drinks/machinery/stove_component.dm
@@ -267,7 +267,7 @@
return
// this gets badly murdered by sidemap
soup_smoke = new(parent, particle_type)
- soup_smoke.set_particle_position(container_x, round(world.icon_size * 0.66), 0)
+ soup_smoke.set_particle_position(container_x, round(ICON_SIZE_Y * 0.66), 0)
return
QDEL_NULL(soup_smoke)
diff --git a/code/modules/hallucination/mother.dm b/code/modules/hallucination/mother.dm
index d9cd7f1983119..7d407e43d8eb1 100644
--- a/code/modules/hallucination/mother.dm
+++ b/code/modules/hallucination/mother.dm
@@ -37,7 +37,7 @@
var/obj/visual = image('icons/hud/screen_gen.dmi', mother.loc, "arrow", FLY_LAYER)
INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay_global), visual, list(hallucinator.client), 2.5 SECONDS)
- animate(visual, pixel_x = (tile.x - mother.x) * world.icon_size, pixel_y = (tile.y - mother.y) * world.icon_size, time = 1.7, easing = EASE_OUT)
+ animate(visual, pixel_x = (tile.x - mother.x) * ICON_SIZE_X, pixel_y = (tile.y - mother.y) * ICON_SIZE_Y, time = 1.7, easing = EASE_OUT)
/datum/hallucination/your_mother/proc/talk(text)
var/plus_runechat = hallucinator.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat)
diff --git a/code/modules/hallucination/on_fire.dm b/code/modules/hallucination/on_fire.dm
index cb4a95dd4420c..7266de17f4669 100644
--- a/code/modules/hallucination/on_fire.dm
+++ b/code/modules/hallucination/on_fire.dm
@@ -37,7 +37,6 @@
return ..()
/datum/hallucination/fire/start()
- hallucinator.set_fire_stacks(max(hallucinator.fire_stacks, 0.1)) //Placebo flammability
fire_overlay = image(fire_icon, hallucinator, fire_icon_state, ABOVE_MOB_LAYER)
hallucinator.client?.images |= fire_overlay
to_chat(hallucinator, span_userdanger("You're set on fire!"))
@@ -47,7 +46,6 @@
return TRUE
/datum/hallucination/fire/Destroy()
- hallucinator.adjust_fire_stacks(-0.1)
hallucinator.clear_alert(ALERT_FIRE, clear_override = TRUE)
hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE)
if(fire_overlay)
diff --git a/code/modules/hallucination/screwy_health_doll.dm b/code/modules/hallucination/screwy_health_doll.dm
index 7bab267563c74..2a8eeba16e2b3 100644
--- a/code/modules/hallucination/screwy_health_doll.dm
+++ b/code/modules/hallucination/screwy_health_doll.dm
@@ -66,12 +66,11 @@
bodyparts -= source
/// Whenever a bodypart we're tracking has their health hud updated, override it with our fake overlay
-/datum/hallucination/fake_health_doll/proc/on_bodypart_hud_update(obj/item/bodypart/source, mob/living/carbon/human/owner)
+/datum/hallucination/fake_health_doll/proc/on_bodypart_hud_update(obj/item/bodypart/source, mob/living/carbon/human/owner, list/overridable_key)
SIGNAL_HANDLER
- var/mutable_appearance/fake_overlay = mutable_appearance('icons/hud/screen_gen.dmi', "[source.body_zone][bodyparts[source]]")
- owner.hud_used.healthdoll.add_overlay(fake_overlay)
- return COMPONENT_OVERRIDE_BODYPART_HEALTH_HUD
+ overridable_key[1] = bodyparts[source]
+ return OVERRIDE_BODYPART_HEALTH_HUD
/// Signal proc for [COMSIG_BODYPART_CHECKED_FOR_INJURY]. Our bodyparts look a lot more wounded than they actually are.
/datum/hallucination/fake_health_doll/proc/on_bodypart_checked(obj/item/bodypart/source, mob/living/carbon/examiner, list/check_list, list/limb_damage)
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index f0ebd470dd88f..f000efcfcd59a 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -120,8 +120,7 @@
/// Alternate titles to register as pointing to this job.
var/list/alternate_titles
- /// Does this job ignore human authority?
- var/ignore_human_authority = FALSE
+ var/human_authority = JOB_AUTHORITY_NON_HUMANS_ALLOWED
/// String key to track any variables we want to tie to this job in config, so we can avoid using the job title. We CAPITALIZE it in order to ensure it's unique and resistant to trivial formatting changes.
/// You'll probably break someone's config if you change this, so it's best to not to.
@@ -535,11 +534,28 @@
if(!player_client)
return // Disconnected while checking for the appearance ban.
- var/require_human = CONFIG_GET(flag/enforce_human_authority) && (job.job_flags & JOB_HEAD_OF_STAFF)
- if(require_human)
- var/all_authority_require_human = CONFIG_GET(flag/enforce_human_authority_on_everyone)
- if(!all_authority_require_human && job.ignore_human_authority)
- require_human = FALSE
+ var/human_authority_setting = CONFIG_GET(string/human_authority)
+ var/require_human = FALSE
+
+ // If the job in question is a head of staff,
+ // check the config to see if we should force the player onto a human character or not
+ if(job.job_flags & JOB_HEAD_OF_STAFF)
+ switch(human_authority_setting)
+
+ // If non-humans are the norm and jobs must be forced to be only for humans
+ // then we only force the player to be a human if the job exclusively allows humans
+ if(HUMAN_AUTHORITY_HUMAN_WHITELIST)
+ require_human = job.human_authority == JOB_AUTHORITY_HUMANS_ONLY
+
+ // If humans are the norm and jobs must be allowed to be played by non-humans
+ // then we only force the player to be a human if the job doesn't allow for non-humans to play it
+ if(HUMAN_AUTHORITY_NON_HUMAN_WHITELIST)
+ require_human = job.human_authority != JOB_AUTHORITY_NON_HUMANS_ALLOWED
+
+ // If humans are the norm and there is no chance that a non-human can be a head of staff
+ // always return true, since there is no chance that a non-human can be a head of staff.
+ if(HUMAN_AUTHORITY_ENFORCED)
+ require_human = TRUE
src.job = job.title
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
index 5b82c9adb7116..99bc35bb60320 100644
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -46,6 +46,8 @@
job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS
rpg_title = "Star Duke"
+ human_authority = JOB_AUTHORITY_HUMANS_ONLY
+
voice_of_god_power = 1.4 //Command staff has authority
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_costumes.dm b/code/modules/jobs/job_types/chaplain/chaplain_costumes.dm
index 0968569ae33af..9bd6436994df1 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_costumes.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_costumes.dm
@@ -40,7 +40,7 @@
icon_state = "holidaypriest"
inhand_icon_state = "w_suit"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- flags_inv = HIDEJUMPSUIT
+ flags_inv = HIDEJUMPSUIT|HIDEBELT
/obj/item/clothing/suit/chaplainsuit/nun
name = "nun robe"
@@ -48,7 +48,7 @@
icon_state = "nun"
inhand_icon_state = "nun"
body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS
- flags_inv = HIDEJUMPSUIT
+ flags_inv = HIDEJUMPSUIT|HIDEBELT
/obj/item/clothing/suit/chaplainsuit/habit
name = "religious tunic"
@@ -56,7 +56,7 @@
icon_state = "habit"
alternate_worn_layer = GLOVES_LAYER // since the sleeves cover a part of the hands, this way it looks better while retaining glove overlay correctly.
body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS
- flags_inv = HIDEJUMPSUIT
+ flags_inv = HIDEJUMPSUIT|HIDEBELT
/obj/item/clothing/suit/chaplainsuit/bishoprobe
name = "bishop's robes"
@@ -64,7 +64,7 @@
icon_state = "bishoprobe"
inhand_icon_state = "bishoprobe"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- flags_inv = HIDEJUMPSUIT
+ flags_inv = HIDEJUMPSUIT|HIDEBELT
/obj/item/clothing/suit/chaplainsuit/armor/studentuni
name = "student robe"
@@ -106,7 +106,7 @@
icon_state = "monkrobeeast"
inhand_icon_state = null
body_parts_covered = GROIN|LEGS
- flags_inv = HIDEJUMPSUIT
+ flags_inv = HIDEJUMPSUIT|HIDEBELT
/obj/item/clothing/suit/chaplainsuit/whiterobe
name = "white robe"
@@ -114,7 +114,7 @@
icon_state = "whiterobe"
inhand_icon_state = null
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- flags_inv = HIDEJUMPSUIT
+ flags_inv = HIDEJUMPSUIT|HIDEBELT
/obj/item/clothing/suit/chaplainsuit/clownpriest
name = "Robes of the Honkmother"
@@ -122,7 +122,7 @@
icon_state = "clownpriest"
inhand_icon_state = "clownpriest"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- flags_inv = HIDEJUMPSUIT
+ flags_inv = HIDEJUMPSUIT|HIDEBELT
allowed = list(/obj/item/megaphone/clown, /obj/item/soap, /obj/item/food/pie/cream, /obj/item/bikehorn, /obj/item/bikehorn/golden, /obj/item/bikehorn/airhorn, /obj/item/instrument/bikehorn, /obj/item/reagent_containers/cup/soda_cans/canned_laughter, /obj/item/toy/crayon, /obj/item/toy/crayon/spraycan, /obj/item/toy/crayon/spraycan/lubecan, /obj/item/grown/bananapeel, /obj/item/food/grown/banana)
/obj/item/clothing/head/helmet/chaplain/clock
@@ -151,6 +151,10 @@
inhand_icon_state = null
slowdown = 0
+/obj/item/clothing/suit/chaplainsuit/armor/clock/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/head/helmet/chaplain
name = "crusader helmet"
desc = "Deus Vult."
@@ -179,6 +183,10 @@
inhand_icon_state = null
slowdown = 0
+/obj/item/clothing/suit/chaplainsuit/armor/templar/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/head/helmet/chaplain/cage
name = "cage"
desc = "A cage that restrains the will of the self, allowing one to see the profane world for what it is."
@@ -199,6 +207,10 @@
icon_state = "knight_ancient"
inhand_icon_state = null
+/obj/item/clothing/suit/chaplainsuit/armor/ancient/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
/obj/item/clothing/head/helmet/chaplain/witchunter_hat
name = "witchunter hat"
desc = "This hat saw much use back in the day."
@@ -230,6 +242,11 @@
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
armor_type = /datum/armor/armor_crusader
+/obj/item/clothing/suit/chaplainsuit/armor/crusader/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/item_equipped_movement_rustle, SFX_PLATE_ARMOR_RUSTLE, 8)
+
+
/datum/armor/armor_crusader
melee = 50
bullet = 50
@@ -287,4 +304,4 @@
icon_state = "shrinehand"
inhand_icon_state = "shrinehand"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
- flags_inv = HIDEJUMPSUIT
+ flags_inv = HIDEJUMPSUIT|HIDEBELT
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
index 7d41ba36cdc3b..f8e94746d6030 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
@@ -22,6 +22,8 @@
var/menu_description = "A standard chaplain's weapon. Fits in pockets. Can be worn on the belt."
/// Lazylist, tracks refs()s to all cultists which have been crit or killed by this nullrod.
var/list/cultists_slain
+ /// Affects GLOB.holy_weapon_type. Disable to allow null rods to change at will and without affecting the station's type.
+ var/station_holy_item = TRUE
/obj/item/nullrod/Initialize(mapload)
. = ..()
@@ -35,7 +37,7 @@
)
AddElement(/datum/element/bane, target_type = /mob/living/basic/revenant, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
- if(!GLOB.holy_weapon_type && type == /obj/item/nullrod)
+ if((!GLOB.holy_weapon_type || !station_holy_item) && type == /obj/item/nullrod)
var/list/rods = list()
for(var/obj/item/nullrod/nullrod_type as anything in typesof(/obj/item/nullrod))
if(!initial(nullrod_type.chaplain_spawnable))
@@ -52,6 +54,8 @@
AddComponent(/datum/component/subtype_picker, rods, CALLBACK(src, PROC_REF(on_holy_weapon_picked)))
/obj/item/nullrod/proc/on_holy_weapon_picked(obj/item/nullrod/holy_weapon_type)
+ if(!station_holy_item)
+ return
GLOB.holy_weapon_type = holy_weapon_type
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NULLROD_PICKED)
SSblackbox.record_feedback("tally", "chaplain_weapon", 1, "[initial(holy_weapon_type.name)]")
@@ -90,6 +94,10 @@
. += span_cult_italic("It has the blood of [num_slain] fallen cultist[num_slain == 1 ? "" : "s"] on it. \
Offering it to Nar'sie will transform it into a [num_slain >= 3 ? "powerful" : "standard"] cult weapon.")
+/obj/item/nullrod/non_station
+ station_holy_item = FALSE
+ chaplain_spawnable = FALSE
+
/// Claymore Variant
/// This subtype possesses a block chance and is sharp.
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index 7ac1b6e29af9b..f85c2c54973b9 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -46,6 +46,8 @@
rpg_title = "Head Crystallomancer"
job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS
+ human_authority = JOB_AUTHORITY_HUMANS_ONLY
+
voice_of_god_power = 1.4 //Command staff has authority
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
index 3f580b8e932a5..c9f126bb750bc 100644
--- a/code/modules/jobs/job_types/chief_medical_officer.dm
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -43,6 +43,8 @@
rpg_title = "High Cleric"
job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS
+ human_authority = JOB_AUTHORITY_HUMANS_ONLY
+
voice_of_god_power = 1.4 //Command staff has authority
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
index e863a782d9b0c..1b8480f0d0b57 100644
--- a/code/modules/jobs/job_types/head_of_personnel.dm
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -41,6 +41,9 @@
family_heirlooms = list(/obj/item/reagent_containers/cup/glass/trophy/silver_cup)
rpg_title = "Guild Questgiver"
job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS
+
+ human_authority = JOB_AUTHORITY_HUMANS_ONLY
+
voice_of_god_power = 1.4 //Command staff has authority
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
index f3627c3c2a257..b9560708114be 100644
--- a/code/modules/jobs/job_types/head_of_security.dm
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -37,6 +37,8 @@
rpg_title = "Guard Leader"
job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS
+ human_authority = JOB_AUTHORITY_HUMANS_ONLY
+
voice_of_god_power = 1.4 //Command staff has authority
diff --git a/code/modules/jobs/job_types/paramedic.dm b/code/modules/jobs/job_types/paramedic.dm
index 2fd4f3a93a6da..80936dce2938a 100644
--- a/code/modules/jobs/job_types/paramedic.dm
+++ b/code/modules/jobs/job_types/paramedic.dm
@@ -24,7 +24,7 @@
/datum/job_department/medical,
)
- family_heirlooms = list(/obj/item/storage/medkit/ancient/heirloom)
+ family_heirlooms = list(/obj/item/storage/medkit/ancient/heirloom, /obj/item/fishing_hook/rescue)
mail_goodies = list(
/obj/item/reagent_containers/hypospray/medipen = 20,
diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm
index 858ce8b645536..32053daa5d8c8 100644
--- a/code/modules/jobs/job_types/quartermaster.dm
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -36,7 +36,7 @@
rpg_title = "Steward"
job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS
voice_of_god_power = 1.4 //Command staff has authority
- ignore_human_authority = TRUE
+ human_authority = JOB_AUTHORITY_NON_HUMANS_ALLOWED
/datum/outfit/job/quartermaster
name = "Quartermaster"
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index 5d3c620322759..420138a6b9fba 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -44,6 +44,8 @@
rpg_title = "Archmagister"
job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS
+ human_authority = JOB_AUTHORITY_HUMANS_ONLY
+
voice_of_god_power = 1.4 //Command staff has authority
diff --git a/code/modules/jobs/job_types/station_trait/bridge_assistant.dm b/code/modules/jobs/job_types/station_trait/bridge_assistant.dm
index b1ae57de1ffe6..d776ae6251f7f 100644
--- a/code/modules/jobs/job_types/station_trait/bridge_assistant.dm
+++ b/code/modules/jobs/job_types/station_trait/bridge_assistant.dm
@@ -33,7 +33,7 @@
rpg_title = "Royal Guard"
allow_bureaucratic_error = FALSE
job_flags = STATION_JOB_FLAGS | STATION_TRAIT_JOB_FLAGS
- ignore_human_authority = TRUE
+ human_authority = JOB_AUTHORITY_NON_HUMANS_ALLOWED
/datum/job/bridge_assistant/after_spawn(mob/living/spawned, client/player_client)
. = ..()
diff --git a/code/modules/jobs/job_types/station_trait/human_ai.dm b/code/modules/jobs/job_types/station_trait/human_ai.dm
index a6e77d77a510c..d6f89357b4489 100644
--- a/code/modules/jobs/job_types/station_trait/human_ai.dm
+++ b/code/modules/jobs/job_types/station_trait/human_ai.dm
@@ -40,7 +40,7 @@
random_spawns_possible = FALSE
allow_bureaucratic_error = FALSE
job_flags = STATION_JOB_FLAGS | STATION_TRAIT_JOB_FLAGS
- ignore_human_authority = TRUE //we can safely assume NT doesn't care what species AIs are made of, much less if they can't even afford an AI.
+ human_authority = JOB_AUTHORITY_NON_HUMANS_ALLOWED //we can safely assume NT doesn't care what species AIs are made of, much less if they can't even afford an AI.
/datum/job/human_ai/get_roundstart_spawn_point()
return get_latejoin_spawn_point()
@@ -111,6 +111,18 @@
l_hand = /obj/item/paper/default_lawset_list
+/datum/outfit/job/human_ai/pre_equip(mob/living/carbon/human/equipped, visualsOnly)
+ . = ..()
+ if(visualsOnly)
+ return
+ if(is_safe_turf(equipped.loc, dense_atoms = TRUE)) //skip this if it's safe. We allow dense atoms because we spawn out of the inactive core.
+ return
+ if(isnull(equipped.dna.species.outfit_important_for_life)) //custom species stuff will handle this for us.
+ internals_slot = ITEM_SLOT_SUITSTORE
+ suit_store = /obj/item/tank/internals/oxygen
+ suit = /obj/item/clothing/suit/space/nasavoid
+ head = /obj/item/clothing/head/helmet/space/nasavoid
+
/datum/outfit/job/human_ai/post_equip(mob/living/carbon/human/equipped, visualsOnly)
. = ..()
if(visualsOnly)
@@ -122,9 +134,6 @@
ADD_TRAIT(equipped, TRAIT_COMMISSIONED, INNATE_TRAIT)
equipped.faction |= list(FACTION_SILICON, FACTION_TURRET)
- var/static/list/allowed_areas = typecacheof(list(/area/station/ai_monitored))
- equipped.AddComponent(/datum/component/hazard_area, area_whitelist = allowed_areas)
-
/obj/item/paper/default_lawset_list
name = "Lawset Note"
desc = "A note explaining the lawset, quickly written yet everso important."
diff --git a/code/modules/jobs/job_types/station_trait/pun_pun.dm b/code/modules/jobs/job_types/station_trait/pun_pun.dm
index eca4861e77ef0..b6ac7b813bffc 100644
--- a/code/modules/jobs/job_types/station_trait/pun_pun.dm
+++ b/code/modules/jobs/job_types/station_trait/pun_pun.dm
@@ -37,7 +37,7 @@
/datum/job/pun_pun/after_spawn(mob/living/carbon/human/monkey, client/player_client)
. = ..()
- monkey.make_clever_and_no_dna_scramble()
+ monkey.crewlike_monkify()
/datum/outfit/job/pun_pun
name = "Pun Pun"
diff --git a/code/modules/library/skill_learning/generic_skillchips/misc.dm b/code/modules/library/skill_learning/generic_skillchips/misc.dm
new file mode 100644
index 0000000000000..aea850b8f49f2
--- /dev/null
+++ b/code/modules/library/skill_learning/generic_skillchips/misc.dm
@@ -0,0 +1,157 @@
+//Contains generic skillchips that are fairly short and simple
+
+/obj/item/skillchip/basketweaving
+ name = "Basketsoft 3000 skillchip"
+ desc = "Underwater edition."
+ auto_traits = list(TRAIT_UNDERWATER_BASKETWEAVING_KNOWLEDGE)
+ skill_name = "Underwater Basketweaving"
+ skill_description = "Master intricate art of using twine to create perfect baskets while submerged."
+ skill_icon = "shopping-basket"
+ activate_message = span_notice("You're one with the twine and the sea.")
+ deactivate_message = span_notice("Higher mysteries of underwater basketweaving leave your mind.")
+
+/obj/item/skillchip/wine_taster
+ name = "WINE skillchip"
+ desc = "Wine.Is.Not.Equal version 5."
+ auto_traits = list(TRAIT_WINE_TASTER)
+ skill_name = "Wine Tasting"
+ skill_description = "Recognize wine vintage from taste alone. Never again lack an opinion when presented with an unknown drink."
+ skill_icon = "wine-bottle"
+ activate_message = span_notice("You recall wine taste.")
+ deactivate_message = span_notice("Your memories of wine evaporate.")
+
+/obj/item/skillchip/bonsai
+ name = "Hedge 3 skillchip"
+ auto_traits = list(TRAIT_BONSAI)
+ skill_name = "Hedgetrimming"
+ skill_description = "Trim hedges and potted plants into marvelous new shapes with any old knife. Not applicable to plastic plants."
+ skill_icon = "spa"
+ activate_message = span_notice("Your mind is filled with plant arrangments.")
+ deactivate_message = span_notice("You can't remember what a hedge looks like anymore.")
+
+/obj/item/skillchip/useless_adapter
+ name = "Skillchip adapter"
+ skill_name = "Useless adapter"
+ skill_description = "Allows you to insert another skillchip into this adapter after it has been inserted into your brain..."
+ skill_icon = "plug"
+ activate_message = span_notice("You can now activate another chip through this adapter, but you're not sure why you did this...")
+ deactivate_message = span_notice("You no longer have the useless skillchip adapter.")
+ skillchip_flags = SKILLCHIP_ALLOWS_MULTIPLE
+ // Literally does nothing.
+ complexity = 0
+ slot_use = 0
+
+/obj/item/skillchip/light_remover
+ name = "N16H7M4R3 skillchip"
+ auto_traits = list(TRAIT_LIGHTBULB_REMOVER)
+ skill_name = "Lightbulb Removing"
+ skill_description = "Stop failing taking out lightbulbs today, no gloves needed!"
+ skill_icon = "lightbulb"
+ activate_message = span_notice("Your feel like your pain receptors are less sensitive to hot objects.")
+ deactivate_message = span_notice("You feel like hot objects could stop you again...")
+
+/obj/item/skillchip/disk_verifier
+ name = "K33P-TH4T-D15K skillchip"
+ auto_traits = list(TRAIT_DISK_VERIFIER)
+ skill_name = "Nuclear Disk Verification"
+ skill_description = "Nuclear authentication disks have an extremely long serial number for verification. This skillchip stores that number, which allows the user to automatically spot forgeries."
+ skill_icon = "save"
+ activate_message = span_notice("You feel your mind automatically verifying long serial numbers on disk shaped objects.")
+ deactivate_message = span_notice("The innate recognition of absurdly long disk-related serial numbers fades from your mind.")
+
+/obj/item/skillchip/entrails_reader
+ name = "3NTR41LS skillchip"
+ auto_traits = list(TRAIT_ENTRAILS_READER)
+ skill_name = "Entrails Reader"
+ skill_description = "Be able to learn about a person's life, by looking at their internal organs. Not to be confused with looking into the future."
+ skill_icon = "lungs"
+ activate_message = span_notice("You feel that you know a lot about interpreting organs.")
+ deactivate_message = span_notice("Knowledge of liver damage, heart strain and lung scars fades from your mind.")
+
+/obj/item/skillchip/appraiser
+ name = "GENUINE ID Appraisal Now! skillchip"
+ auto_traits = list(TRAIT_ID_APPRAISER)
+ skill_name = "ID Appraisal"
+ skill_description = "Appraise an ID and see if it's issued from centcom, or just a cruddy station-printed one."
+ skill_icon = "magnifying-glass"
+ activate_message = span_notice("You feel that you can recognize special, minute details on ID cards.")
+ deactivate_message = span_notice("Was there something special about certain IDs?")
+
+/obj/item/skillchip/sabrage
+ name = "Le S48R4G3 skillchip"
+ auto_traits = list(TRAIT_SABRAGE_PRO)
+ skill_name = "Sabrage Proficiency"
+ skill_description = "Grants the user knowledge of the intricate structure of a champagne bottle's structural weakness at the neck, \
+ improving their proficiency at being a show-off at officer parties."
+ skill_icon = "bottle-droplet"
+ activate_message = span_notice("You feel a new understanding of champagne bottles and methods on how to remove their corks.")
+ deactivate_message = span_notice("The knowledge of the subtle physics residing inside champagne bottles fades from your mind.")
+
+/obj/item/skillchip/brainwashing
+ name = "suspicious skillchip"
+ auto_traits = list(TRAIT_BRAINWASHING)
+ skill_name = "Brainwashing"
+ skill_description = "WARNING: The integrity of this chip is compromised. Please discard this skillchip."
+ skill_icon = "soap"
+ activate_message = span_notice("...But all at once it comes to you... something involving putting a brain in a washing machine?")
+ deactivate_message = span_warning("All knowledge of the secret brainwashing technique is GONE.")
+
+/obj/item/skillchip/brainwashing/examine(mob/user)
+ . = ..()
+ . += span_warning("It seems to have been corroded over time, putting this in your head may not be the best idea...")
+
+/obj/item/skillchip/brainwashing/on_activate(mob/living/carbon/user, silent = FALSE)
+ to_chat(user, span_danger("You get a pounding headache as the chip sends corrupt memories into your head!"))
+ user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20)
+ . = ..()
+
+/obj/item/skillchip/chefs_kiss
+ name = "K1SS skillchip"
+ auto_traits = list(TRAIT_CHEF_KISS)
+ skill_name = "Chef's Kiss"
+ skill_description = "Allows you to kiss food you've created to make them with love."
+ skill_icon = "cookie"
+ activate_message = span_notice("You recall learning from your grandmother how they baked their cookies with love.")
+ deactivate_message = span_notice("You forget all memories imparted upon you by your grandmother. Were they even your real grandma?")
+
+/obj/item/skillchip/intj
+ name = "Integrated Intuitive Thinking and Judging skillchip"
+ auto_traits = list(TRAIT_REMOTE_TASTING)
+ skill_name = "Mental Flavour Calculus"
+ skill_description = "When examining food, you can experience the flavours just as well as if you were eating it."
+ skill_icon = FA_ICON_DRUMSTICK_BITE
+ activate_message = span_notice("You think of your favourite food and realise that you can rotate its flavour in your mind.")
+ deactivate_message = span_notice("You feel your food-based mind palace crumbling...")
+
+/obj/item/skillchip/drunken_brawler
+ name = "F0RC3 4DD1CT10N skillchip"
+ auto_traits = list(TRAIT_DRUNKEN_BRAWLER)
+ skill_name = "Drunken Unarmed Proficiency"
+ skill_description = "When intoxicated, you gain increased unarmed effectiveness."
+ skill_icon = "wine-bottle"
+ activate_message = span_notice("You honestly could do with a drink. Never know when someone might try and jump you around here.")
+ deactivate_message = span_notice("You suddenly feel a lot safer going around the station sober... ")
+
+/obj/item/skillchip/master_angler
+ name = "Mast-Angl-Er skillchip"
+ auto_traits = list(TRAIT_REVEAL_FISH, TRAIT_EXAMINE_FISHING_SPOT, TRAIT_EXAMINE_FISH, TRAIT_EXAMINE_DEEPER_FISH)
+ skill_name = "Fisherman's Discernment"
+ skill_description = "Lists fishes when examining a fishing spot, gives a hint of whatever thing's biting the hook and more."
+ skill_icon = "fish"
+ activate_message = span_notice("You feel the knowledge and passion of several sunbaked, seasoned fishermen burn within you.")
+ deactivate_message = span_notice("You no longer feel like casting a fishing rod by the sunny riverside.")
+
+ actions_types = list(/datum/action/cooldown/fishing_tip)
+
+/datum/action/cooldown/fishing_tip
+ name = "Dispense Fishing Tip"
+ desc = "Recall a pearl of wisdom about fishing."
+ button_icon = 'icons/hud/radial_fishing.dmi'
+ button_icon_state = "river"
+ background_icon_state = "bg_default"
+ overlay_icon_state = "bg_default_border"
+ cooldown_time = 2.5 SECONDS //enough time to skim through tips.
+
+/datum/action/cooldown/fishing_tip/Activate(atom/target_atom)
+ . = ..()
+ send_tip_of_the_round(owner, pick(GLOB.fishing_tips), source = "Ancient fishing wisdom")
diff --git a/code/modules/library/skill_learning/generic_skillchips/musical.dm b/code/modules/library/skill_learning/generic_skillchips/musical.dm
new file mode 100644
index 0000000000000..0eea811ffab77
--- /dev/null
+++ b/code/modules/library/skill_learning/generic_skillchips/musical.dm
@@ -0,0 +1,91 @@
+/obj/item/skillchip/musical
+ name = "\improper Old Copy of \"Space Station 13: The Musical\""
+ desc = "An old copy of \"Space Station 13: The Musical\", \
+ ran on the station's 100th anniversary...Or maybe it was the 200th?"
+ skill_name = "Memory of a Musical"
+ skill_description = "Allows you to hit that high note, like those that came a century before us."
+ skill_icon = FA_ICON_MUSIC
+ activate_message = span_notice("You feel like you could \u2669 sing a soooong! \u266B")
+ deactivate_message = span_notice("The musical fades from your mind, leaving you with a sense of nostalgia.")
+ custom_premium_price = PAYCHECK_CREW * 4
+
+/obj/item/skillchip/musical/Initialize(mapload, is_removable)
+ . = ..()
+ name = replacetext(name, "Old", round(CURRENT_STATION_YEAR - pick(50, 100, 150, 200, 250), 5))
+
+/obj/item/skillchip/musical/on_activate(mob/living/carbon/user, silent = FALSE)
+ . = ..()
+ RegisterSignal(user, COMSIG_MOB_SAY, PROC_REF(make_music))
+
+/obj/item/skillchip/musical/on_deactivate(mob/living/carbon/user, silent)
+ . = ..()
+ UnregisterSignal(user, COMSIG_MOB_SAY)
+
+/obj/item/skillchip/musical/proc/make_music(mob/living/carbon/source, list/say_args)
+ SIGNAL_HANDLER
+
+ var/raw_message = say_args[SPEECH_MESSAGE]
+ var/list/words = splittext(raw_message, " ")
+ if(length(words) <= 1)
+ say_args[SPEECH_MODS][MODE_SING] = TRUE
+ return
+ var/last_word = words[length(words)]
+ var/num_chars = length_char(last_word)
+ var/last_vowel = ""
+ // find the last vowel present in the word
+ for(var/i in 1 to num_chars)
+ var/char = copytext_char(last_word, i, i + 1)
+ if(char in VOWELS)
+ last_vowel = char
+
+ // now we'll reshape the final word to make it sound like they're singing it
+ var/final_word = ""
+ var/has_ellipsis = copytext(last_word, -3) == "..."
+ for(var/i in 1 to num_chars)
+ var/char = copytext_char(last_word, i, i + 1)
+ // replacing any final periods with exclamation marks (so long as it's not an ellipsis)
+ if(char == "." && i == num_chars && !has_ellipsis)
+ final_word += "!"
+ // or if it's the vowel we found, we're gonna repeat it a few times (holding the note)
+ else if(char == last_vowel)
+ for(var/j in 1 to 4)
+ final_word += char
+ // if we dragged out the last character of the word, just period it
+ if(i == num_chars)
+ final_word += "."
+ // no special handing otherwise
+ else
+ final_word += char
+
+ if(!has_ellipsis)
+ // adding an extra exclamation mark at the end if there's no period
+ var/last_char = copytext_char(final_word, -1)
+ if(last_char != ".")
+ final_word += "!"
+
+ words[length(words)] = final_word
+ // now we siiiiiiing
+ say_args[SPEECH_MESSAGE] = jointext(words, " ")
+ say_args[SPEECH_MODS][MODE_SING] = TRUE
+
+/obj/item/skillchip/musical/examine(mob/user)
+ . = ..()
+ . += span_tinynoticeital("Huh, looks like it'd fit in a skillchip adapter.")
+
+/obj/item/skillchip/musical/examine_more(mob/user)
+ . = ..()
+ var/list/songs = list()
+ songs += "• \"The Ballad of Space Station 13\""
+ songs += "• \"The Captain's Call\""
+ songs += "• \"A Mime's Lament\""
+ songs += "• \"Banned from Cargo\""
+ songs += "• \"Botany Blues\""
+ songs += "• \"Clown Song\""
+ songs += "• \"Elegy to an Engineer\""
+ songs += "• \"Medical Malpractitioner\""
+ songs += "• \"Security Strike\""
+ songs += "• \"Send for the Shuttle\""
+ songs += "• And one song scratched out..."
+
+ . += span_notice("On the back of the chip, you see a list of songs:")
+ . += span_smallnotice("[jointext(songs, "
")]")
diff --git a/code/modules/library/skill_learning/generic_skillchips/point.dm b/code/modules/library/skill_learning/generic_skillchips/point.dm
index 761a482268952..ba6a2e3e236ec 100644
--- a/code/modules/library/skill_learning/generic_skillchips/point.dm
+++ b/code/modules/library/skill_learning/generic_skillchips/point.dm
@@ -11,25 +11,17 @@
activate_message = span_notice("From \"The Definitive Compendium of Body Language for the Aspiring Leader\", page 164, paragraph 3...")
deactivate_message = span_notice("So, uh, yeah, how do I point at things again?")
- ///The action for changing the pointer color
- var/datum/action/change_pointer_color/action
-
-/obj/item/skillchip/big_pointer/Destroy()
- action = null
- return ..()
+ actions_types = list(/datum/action/change_pointer_color)
/obj/item/skillchip/big_pointer/on_activate(mob/living/carbon/user, silent=FALSE)
. = ..()
RegisterSignal(user, COMSIG_MOVABLE_POINTED, PROC_REF(fancier_pointer))
- if(!action)
- action = new(src)
- action.Grant(user)
/obj/item/skillchip/big_pointer/on_deactivate(mob/living/carbon/user, silent=FALSE)
UnregisterSignal(user, COMSIG_MOVABLE_POINTED)
+ var/datum/action/change_pointer_color/action = locate() in actions
action?.arrow_color = null
action?.arrow_overlay = null
- action?.Remove(user)
return ..()
/obj/item/skillchip/big_pointer/proc/fancier_pointer(mob/living/user, atom/pointed, obj/effect/temp_visual/point/point)
@@ -37,6 +29,7 @@
if(HAS_TRAIT(user, TRAIT_UNKNOWN))
return
point.cut_overlays()
+ var/datum/action/change_pointer_color/action = locate() in actions
if(!action.arrow_color)
point.icon_state = "arrow_large"
return
diff --git a/code/modules/library/skill_learning/generic_skillchips/rod_suplex.dm b/code/modules/library/skill_learning/job_skillchips/research_director.dm
similarity index 100%
rename from code/modules/library/skill_learning/generic_skillchips/rod_suplex.dm
rename to code/modules/library/skill_learning/job_skillchips/research_director.dm
diff --git a/code/modules/library/skill_learning/skillchip.dm b/code/modules/library/skill_learning/skillchip.dm
index 10139585dd9a7..eddaf300e08a9 100644
--- a/code/modules/library/skill_learning/skillchip.dm
+++ b/code/modules/library/skill_learning/skillchip.dm
@@ -48,6 +48,10 @@
. = ..()
removable = is_removable
+///We don't grant actions outside of being activated when implanted
+/obj/item/skillchip/item_action_slot_check(slot, mob/user, datum/action/action)
+ return FALSE
+
/**
* Activates the skillchip, if possible.
*
@@ -150,6 +154,9 @@
active = TRUE
+ for(var/datum/action/action as anything in actions)
+ action.Grant(user)
+
COOLDOWN_START(src, chip_cooldown, cooldown)
/**
@@ -185,6 +192,9 @@
active = FALSE
+ for(var/datum/action/action as anything in actions)
+ action.Remove(user)
+
COOLDOWN_START(src, chip_cooldown, cooldown)
/**
@@ -369,236 +379,3 @@
removable = metadata["removable"]
return active_msg
-
-/obj/item/skillchip/basketweaving
- name = "Basketsoft 3000 skillchip"
- desc = "Underwater edition."
- auto_traits = list(TRAIT_UNDERWATER_BASKETWEAVING_KNOWLEDGE)
- skill_name = "Underwater Basketweaving"
- skill_description = "Master intricate art of using twine to create perfect baskets while submerged."
- skill_icon = "shopping-basket"
- activate_message = span_notice("You're one with the twine and the sea.")
- deactivate_message = span_notice("Higher mysteries of underwater basketweaving leave your mind.")
-
-/obj/item/skillchip/wine_taster
- name = "WINE skillchip"
- desc = "Wine.Is.Not.Equal version 5."
- auto_traits = list(TRAIT_WINE_TASTER)
- skill_name = "Wine Tasting"
- skill_description = "Recognize wine vintage from taste alone. Never again lack an opinion when presented with an unknown drink."
- skill_icon = "wine-bottle"
- activate_message = span_notice("You recall wine taste.")
- deactivate_message = span_notice("Your memories of wine evaporate.")
-
-/obj/item/skillchip/bonsai
- name = "Hedge 3 skillchip"
- auto_traits = list(TRAIT_BONSAI)
- skill_name = "Hedgetrimming"
- skill_description = "Trim hedges and potted plants into marvelous new shapes with any old knife. Not applicable to plastic plants."
- skill_icon = "spa"
- activate_message = span_notice("Your mind is filled with plant arrangments.")
- deactivate_message = span_notice("You can't remember what a hedge looks like anymore.")
-
-/obj/item/skillchip/useless_adapter
- name = "Skillchip adapter"
- skill_name = "Useless adapter"
- skill_description = "Allows you to insert another skillchip into this adapter after it has been inserted into your brain..."
- skill_icon = "plug"
- activate_message = span_notice("You can now activate another chip through this adapter, but you're not sure why you did this...")
- deactivate_message = span_notice("You no longer have the useless skillchip adapter.")
- skillchip_flags = SKILLCHIP_ALLOWS_MULTIPLE
- // Literally does nothing.
- complexity = 0
- slot_use = 0
-
-/obj/item/skillchip/light_remover
- name = "N16H7M4R3 skillchip"
- auto_traits = list(TRAIT_LIGHTBULB_REMOVER)
- skill_name = "Lightbulb Removing"
- skill_description = "Stop failing taking out lightbulbs today, no gloves needed!"
- skill_icon = "lightbulb"
- activate_message = span_notice("Your feel like your pain receptors are less sensitive to hot objects.")
- deactivate_message = span_notice("You feel like hot objects could stop you again...")
-
-/obj/item/skillchip/disk_verifier
- name = "K33P-TH4T-D15K skillchip"
- auto_traits = list(TRAIT_DISK_VERIFIER)
- skill_name = "Nuclear Disk Verification"
- skill_description = "Nuclear authentication disks have an extremely long serial number for verification. This skillchip stores that number, which allows the user to automatically spot forgeries."
- skill_icon = "save"
- activate_message = span_notice("You feel your mind automatically verifying long serial numbers on disk shaped objects.")
- deactivate_message = span_notice("The innate recognition of absurdly long disk-related serial numbers fades from your mind.")
-
-/obj/item/skillchip/entrails_reader
- name = "3NTR41LS skillchip"
- auto_traits = list(TRAIT_ENTRAILS_READER)
- skill_name = "Entrails Reader"
- skill_description = "Be able to learn about a person's life, by looking at their internal organs. Not to be confused with looking into the future."
- skill_icon = "lungs"
- activate_message = span_notice("You feel that you know a lot about interpreting organs.")
- deactivate_message = span_notice("Knowledge of liver damage, heart strain and lung scars fades from your mind.")
-
-/obj/item/skillchip/appraiser
- name = "GENUINE ID Appraisal Now! skillchip"
- auto_traits = list(TRAIT_ID_APPRAISER)
- skill_name = "ID Appraisal"
- skill_description = "Appraise an ID and see if it's issued from centcom, or just a cruddy station-printed one."
- skill_icon = "magnifying-glass"
- activate_message = span_notice("You feel that you can recognize special, minute details on ID cards.")
- deactivate_message = span_notice("Was there something special about certain IDs?")
-
-/obj/item/skillchip/sabrage
- name = "Le S48R4G3 skillchip"
- auto_traits = list(TRAIT_SABRAGE_PRO)
- skill_name = "Sabrage Proficiency"
- skill_description = "Grants the user knowledge of the intricate structure of a champagne bottle's structural weakness at the neck, \
- improving their proficiency at being a show-off at officer parties."
- skill_icon = "bottle-droplet"
- activate_message = span_notice("You feel a new understanding of champagne bottles and methods on how to remove their corks.")
- deactivate_message = span_notice("The knowledge of the subtle physics residing inside champagne bottles fades from your mind.")
-
-/obj/item/skillchip/brainwashing
- name = "suspicious skillchip"
- auto_traits = list(TRAIT_BRAINWASHING)
- skill_name = "Brainwashing"
- skill_description = "WARNING: The integrity of this chip is compromised. Please discard this skillchip."
- skill_icon = "soap"
- activate_message = span_notice("...But all at once it comes to you... something involving putting a brain in a washing machine?")
- deactivate_message = span_warning("All knowledge of the secret brainwashing technique is GONE.")
-
-/obj/item/skillchip/brainwashing/examine(mob/user)
- . = ..()
- . += span_warning("It seems to have been corroded over time, putting this in your head may not be the best idea...")
-
-/obj/item/skillchip/brainwashing/on_activate(mob/living/carbon/user, silent = FALSE)
- to_chat(user, span_danger("You get a pounding headache as the chip sends corrupt memories into your head!"))
- user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20)
- . = ..()
-
-/obj/item/skillchip/chefs_kiss
- name = "K1SS skillchip"
- auto_traits = list(TRAIT_CHEF_KISS)
- skill_name = "Chef's Kiss"
- skill_description = "Allows you to kiss food you've created to make them with love."
- skill_icon = "cookie"
- activate_message = span_notice("You recall learning from your grandmother how they baked their cookies with love.")
- deactivate_message = span_notice("You forget all memories imparted upon you by your grandmother. Were they even your real grandma?")
-
-/obj/item/skillchip/master_angler
- name = "Mast-Angl-Er skillchip"
- auto_traits = list(TRAIT_REVEAL_FISH, TRAIT_EXAMINE_FISHING_SPOT, TRAIT_EXAMINE_FISH, TRAIT_EXAMINE_DEEPER_FISH)
- skill_name = "Fisherman's Discernment"
- skill_description = "Lists fishes when examining a fishing spot, gives a hint of whatever thing's biting the hook and more."
- skill_icon = "fish"
- activate_message = span_notice("You feel the knowledge and passion of several sunbaked, seasoned fishermen burn within you.")
- deactivate_message = span_notice("You no longer feel like casting a fishing rod by the sunny riverside.")
-
-/obj/item/skillchip/intj
- name = "Integrated Intuitive Thinking and Judging skillchip"
- auto_traits = list(TRAIT_REMOTE_TASTING)
- skill_name = "Mental Flavour Calculus"
- skill_description = "When examining food, you can experience the flavours just as well as if you were eating it."
- skill_icon = FA_ICON_DRUMSTICK_BITE
- activate_message = span_notice("You think of your favourite food and realise that you can rotate its flavour in your mind.")
- deactivate_message = span_notice("You feel your food-based mind palace crumbling...")
-
-/obj/item/skillchip/drunken_brawler
- name = "F0RC3 4DD1CT10N skillchip"
- auto_traits = list(TRAIT_DRUNKEN_BRAWLER)
- skill_name = "Drunken Unarmed Proficiency"
- skill_description = "When intoxicated, you gain increased unarmed effectiveness."
- skill_icon = "wine-bottle"
- activate_message = span_notice("You honestly could do with a drink. Never know when someone might try and jump you around here.")
- deactivate_message = span_notice("You suddenly feel a lot safer going around the station sober... ")
-
-/obj/item/skillchip/musical
- name = "\improper Old Copy of \"Space Station 13: The Musical\""
- desc = "An old copy of \"Space Station 13: The Musical\", \
- ran on the station's 100th anniversary...Or maybe it was the 200th?"
- skill_name = "Memory of a Musical"
- skill_description = "Allows you to hit that high note, like those that came a century before us."
- skill_icon = FA_ICON_MUSIC
- activate_message = span_notice("You feel like you could \u2669 sing a soooong! \u266B")
- deactivate_message = span_notice("The musical fades from your mind, leaving you with a sense of nostalgia.")
- custom_premium_price = PAYCHECK_CREW * 4
-
-/obj/item/skillchip/musical/Initialize(mapload, is_removable)
- . = ..()
- name = replacetext(name, "Old", round(CURRENT_STATION_YEAR - pick(50, 100, 150, 200, 250), 5))
-
-/obj/item/skillchip/musical/on_activate(mob/living/carbon/user, silent = FALSE)
- . = ..()
- RegisterSignal(user, COMSIG_MOB_SAY, PROC_REF(make_music))
-
-/obj/item/skillchip/musical/on_deactivate(mob/living/carbon/user, silent)
- . = ..()
- UnregisterSignal(user, COMSIG_MOB_SAY)
-
-/obj/item/skillchip/musical/proc/make_music(mob/living/carbon/source, list/say_args)
- SIGNAL_HANDLER
-
- var/raw_message = say_args[SPEECH_MESSAGE]
- var/list/words = splittext(raw_message, " ")
- if(length(words) <= 1)
- say_args[SPEECH_MODS][MODE_SING] = TRUE
- return
- var/last_word = words[length(words)]
- var/num_chars = length_char(last_word)
- var/last_vowel = ""
- // find the last vowel present in the word
- for(var/i in 1 to num_chars)
- var/char = copytext_char(last_word, i, i + 1)
- if(char in VOWELS)
- last_vowel = char
-
- // now we'll reshape the final word to make it sound like they're singing it
- var/final_word = ""
- var/has_ellipsis = copytext(last_word, -3) == "..."
- for(var/i in 1 to num_chars)
- var/char = copytext_char(last_word, i, i + 1)
- // replacing any final periods with exclamation marks (so long as it's not an ellipsis)
- if(char == "." && i == num_chars && !has_ellipsis)
- final_word += "!"
- // or if it's the vowel we found, we're gonna repeat it a few times (holding the note)
- else if(char == last_vowel)
- for(var/j in 1 to 4)
- final_word += char
- // if we dragged out the last character of the word, just period it
- if(i == num_chars)
- final_word += "."
- // no special handing otherwise
- else
- final_word += char
-
- if(!has_ellipsis)
- // adding an extra exclamation mark at the end if there's no period
- var/last_char = copytext_char(final_word, -1)
- if(last_char != ".")
- final_word += "!"
-
- words[length(words)] = final_word
- // now we siiiiiiing
- say_args[SPEECH_MESSAGE] = jointext(words, " ")
- say_args[SPEECH_MODS][MODE_SING] = TRUE
-
-/obj/item/skillchip/musical/examine(mob/user)
- . = ..()
- . += span_tinynoticeital("Huh, looks like it'd fit in a skillchip adapter.")
-
-/obj/item/skillchip/musical/examine_more(mob/user)
- . = ..()
- var/list/songs = list()
- songs += "• \"The Ballad of Space Station 13\""
- songs += "• \"The Captain's Call\""
- songs += "• \"A Mime's Lament\""
- songs += "• \"Banned from Cargo\""
- songs += "• \"Botany Blues\""
- songs += "• \"Clown Song\""
- songs += "• \"Elegy to an Engineer\""
- songs += "• \"Medical Malpractitioner\""
- songs += "• \"Security Strike\""
- songs += "• \"Send for the Shuttle\""
- songs += "• And one song scratched out..."
-
- . += span_notice("On the back of the chip, you see a list of songs:")
- . += span_smallnotice("[jointext(songs, "
")]")
diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm
index e3f72da5bbffd..21676d1741e74 100644
--- a/code/modules/lighting/lighting_atom.dm
+++ b/code/modules/lighting/lighting_atom.dm
@@ -201,8 +201,8 @@
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
+ hand_back[1] = -hand_back[1] / ICON_SIZE_X
+ hand_back[2] = -hand_back[2] / ICON_SIZE_Y
else
hand_back = list(0, 0)
diff --git a/code/modules/loadout/categories/heads.dm b/code/modules/loadout/categories/heads.dm
index 6b939495684ba..ad23f0b2dfb7b 100644
--- a/code/modules/loadout/categories/heads.dm
+++ b/code/modules/loadout/categories/heads.dm
@@ -132,6 +132,26 @@
name = "Rose"
item_path = /obj/item/food/grown/rose
+/datum/loadout_item/head/sunflower
+ name = "Sunflower"
+ item_path = /obj/item/food/grown/sunflower
+
+/datum/loadout_item/head/poppy
+ name = "Poppy"
+ item_path = /obj/item/food/grown/poppy
+
+/datum/loadout_item/head/lily
+ name = "Lily"
+ item_path = /obj/item/food/grown/poppy/lily
+
+/datum/loadout_item/head/geranium
+ name = "Geranium"
+ item_path = /obj/item/food/grown/poppy/geranium
+
+/datum/loadout_item/head/harebell
+ name = "Harebell"
+ item_path = /obj/item/food/grown/harebell
+
/datum/loadout_item/head/wig
name = "Wig"
item_path = /obj/item/clothing/head/wig/natural
diff --git a/code/modules/manufactorio/_manufacturing.dm b/code/modules/manufactorio/_manufacturing.dm
new file mode 100644
index 0000000000000..db177ac5ae4d1
--- /dev/null
+++ b/code/modules/manufactorio/_manufacturing.dm
@@ -0,0 +1,130 @@
+#define MANUFACTURING_FAIL_FULL -1
+#define MANUFACTURING_FAIL 0
+#define MANUFACTURING_SUCCESS 1
+
+#define POCKET_INPUT "Input"
+#define POCKET_OUTPUT "Output"
+
+#define MANUFACTURING_TURF_LAG_LIMIT 10 // max items on a turf before we consider it full
+
+/obj/machinery/power/manufacturing
+ icon = 'icons/obj/machines/manufactorio.dmi'
+ name = "base manufacture receiving type"
+ desc = "this shouldnt exist"
+ density = TRUE
+ /// Do we add the simple_rotation component and a text that we are powered by cable? Also allows unwrenching
+ var/may_be_moved = TRUE
+ /// Allow taking in mobs from conveyors?
+ var/allow_mob_bump_intake = FALSE
+
+/obj/machinery/power/manufacturing/Initialize(mapload)
+ . = ..()
+ if(may_be_moved)
+ AddComponent(/datum/component/simple_rotation)
+ if(anchored)
+ connect_to_network()
+
+/obj/machinery/power/manufacturing/examine(mob/user)
+ . = ..()
+ if(may_be_moved)
+ . += "It receives power via cable, but certain buildings do not need power."
+ . += length(contents - circuit) ? "It contains:" : "Its empty."
+ for(var/atom/movable/thing as anything in contents - circuit)
+ var/text = thing.name
+ var/obj/item/stack/possible_stack = thing
+ if(istype(possible_stack))
+ text = "[possible_stack.amount] [text]"
+ . += text
+
+
+/obj/machinery/power/manufacturing/Bumped(atom/movable/bumped_atom) //attempt to put in whatever is pushed into us via conveyor
+ . = ..()
+ if((!allow_mob_bump_intake && ismob(bumped_atom)) || !anchored) //only uncomment if youre brave
+ return
+ var/conveyor = locate(/obj/machinery/conveyor) in bumped_atom.loc
+ if(isnull(conveyor))
+ return
+ receive_resource(bumped_atom, bumped_atom.loc, get_dir(src, bumped_atom))
+
+/obj/machinery/power/manufacturing/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(!may_be_moved)
+ return
+ default_unfasten_wrench(user, tool)
+ if(anchored)
+ connect_to_network()
+ else
+ disconnect_from_network()
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/power/manufacturing/screwdriver_act(mob/living/user, obj/item/tool)
+ if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool))
+ return ITEM_INTERACT_SUCCESS
+ return ITEM_INTERACT_BLOCKING
+
+/obj/machinery/power/manufacturing/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(default_deconstruction_crowbar(tool))
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/power/manufacturing/proc/generate_io_overlays(direction, color, offsets_override)
+ var/list/dir_offset
+ if(islist(offsets_override))
+ dir_offset = offsets_override
+ else
+ dir_offset = dir2offset(direction)
+ dir_offset[1] *= 32
+ dir_offset[2] *= 32
+ var/image/nonemissive = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_[direction]")
+ nonemissive.pixel_x = dir_offset[1]
+ nonemissive.pixel_y = dir_offset[2]
+ nonemissive.color = color
+ var/mutable_appearance/emissive = emissive_appearance(nonemissive.icon, nonemissive.icon_state, offset_spokesman = src, alpha = nonemissive.alpha)
+ emissive.pixel_y = nonemissive.pixel_y
+ emissive.pixel_x = nonemissive.pixel_x
+ return list(nonemissive, emissive)
+
+/// Returns whatever object it may output, or null if it cant do that
+/obj/machinery/power/manufacturing/proc/request_resource()
+
+
+/obj/machinery/power/manufacturing/proc/receive_resource(atom/movable/receiving, atom/from, receive_dir)
+ CRASH("Unimplemented!") //check can_receive_resource here
+
+//use dir please
+/obj/machinery/power/manufacturing/proc/send_resource(atom/movable/sending, atom/what_or_dir)
+ if(isobj(what_or_dir))
+ var/obj/machinery/power/manufacturing/target = what_or_dir
+ return target.receive_resource(sending, src, get_step(src, what_or_dir))
+ var/turf/next_turf = isturf(what_or_dir) ? what_or_dir : get_step(src, what_or_dir)
+ var/obj/machinery/power/manufacturing/manufactury = locate(/obj/machinery/power/manufacturing) in next_turf
+ if(!isnull(manufactury))
+ if(!manufactury.anchored)
+ return MANUFACTURING_FAIL
+ return manufactury.receive_resource(sending, src, isturf(what_or_dir) ? get_dir(src, what_or_dir) : what_or_dir)
+ if(next_turf.is_blocked_turf(exclude_mobs = TRUE, source_atom = sending))
+ return MANUFACTURING_FAIL
+ if(length(next_turf.contents) >= MANUFACTURING_TURF_LAG_LIMIT)
+ return MANUFACTURING_FAIL_FULL
+ if(isnull(sending))
+ return MANUFACTURING_SUCCESS // for the sake of being used as a check
+ if(isnull(sending.loc) || !sending.Move(next_turf, get_dir(src, next_turf)))
+ sending.forceMove(next_turf)
+ return MANUFACTURING_SUCCESS
+
+/// Checks if this stack (if not a stack does not do anything) can merge WITHOUT creating two stacks in contents
+/obj/machinery/power/manufacturing/proc/may_merge_in_contents(obj/item/stack/stack)
+ if(!istype(stack))
+ return
+ for(var/obj/item/stack/other in contents - circuit)
+ if(!stack.can_merge(other))
+ continue
+ if(other.amount + stack.amount <= other.max_amount)
+ return other
+
+/obj/machinery/power/manufacturing/proc/may_merge_in_contents_and_do_so(obj/item/stack/stack)
+ var/merging_into = may_merge_in_contents(stack)
+ if(isnull(merging_into))
+ return
+ return stack.merge(merging_into)
+
diff --git a/code/modules/manufactorio/machines/crafter.dm b/code/modules/manufactorio/machines/crafter.dm
new file mode 100644
index 0000000000000..302202838e53c
--- /dev/null
+++ b/code/modules/manufactorio/machines/crafter.dm
@@ -0,0 +1,145 @@
+/obj/machinery/power/manufacturing/crafter
+ name = "manufacturing assembling machine"
+ desc = "Assembles (crafts) the set recipe until it runs out of resources. Inputs irrelevant to the recipe are ignored, and it may only hold exactly what the recipe needs."
+ icon_state = "crafter"
+ circuit = /obj/item/circuitboard/machine/manucrafter
+ /// power used per process() spent crafting
+ var/power_cost = 5 KILO WATTS
+ /// our output, if the way out was blocked is held here
+ var/atom/movable/withheld
+ /// current recipe
+ var/datum/crafting_recipe/recipe
+ /// crafting component
+ var/datum/component/personal_crafting/machine/craftsman
+ /// current timer for our crafting
+ var/craft_timer
+ /// do we use cooking recipes instead
+ var/cooking = FALSE
+
+/obj/machinery/power/manufacturing/crafter/Initialize(mapload)
+ . = ..()
+ craftsman = AddComponent(/datum/component/personal_crafting/machine)
+ if(ispath(recipe))
+ recipe = locate(recipe) in (cooking ? GLOB.cooking_recipes : GLOB.crafting_recipes)
+
+/obj/machinery/power/manufacturing/crafter/examine(mob/user)
+ . = ..()
+ . += span_notice("It is currently manufacturing [isnull(recipe) ? "nothing. Use a multitool to set it" : recipe.name].")
+ if(isnull(recipe))
+ return
+ . += span_notice("It needs:")
+ for(var/valid_type in recipe.reqs)
+ // Check if they're datums, specifically reagents.
+ var/datum/reagent/reagent_ingredient = valid_type
+ if(istype(reagent_ingredient))
+ var/amount = recipe.reqs[reagent_ingredient]
+ . += "[amount] unit[amount > 1 ? "s" : ""] of [initial(reagent_ingredient.name)]"
+
+ var/atom/ingredient = valid_type
+ var/amount = recipe.reqs[ingredient]
+
+ . += "[amount > 1 ? ("[amount]" + " of") : "a"] [initial(ingredient.name)]"
+
+/obj/machinery/power/manufacturing/crafter/update_overlays()
+ . = ..()
+ . += generate_io_overlays(dir, COLOR_ORANGE)
+ for(var/target_dir in GLOB.cardinals - dir)
+ . += generate_io_overlays(target_dir, COLOR_MODERATE_BLUE)
+
+/obj/machinery/power/manufacturing/crafter/proc/valid_for_recipe(obj/item/checking)
+ . = FALSE
+ for(var/requirement_path in recipe.reqs)
+ if(!ispath(checking.type, requirement_path) || recipe.blacklist.Find(checking.type))
+ continue
+ var/amount = recipe.reqs[requirement_path]
+ if(count_path(requirement_path) >= amount)
+ continue
+ return TRUE
+
+/obj/machinery/power/manufacturing/crafter/proc/count_path(path)
+ . = 0
+ for(var/atom/content as anything in contents - circuit)
+ if(!ispath(path, content.type))
+ continue
+ .++
+
+/obj/machinery/power/manufacturing/crafter/receive_resource(obj/receiving, atom/from, receive_dir)
+ if(isnull(recipe) || !isitem(receiving) || surplus() < power_cost)
+ return MANUFACTURING_FAIL
+ if(receive_dir == dir || !valid_for_recipe(receiving))
+ return MANUFACTURING_FAIL
+ if(isstack(receiving) && count_path(receiving.type) && !may_merge_in_contents_and_do_so(receiving))
+ return MANUFACTURING_FAIL_FULL
+ receiving.Move(src, get_dir(receiving, src))
+ START_PROCESSING(SSmanufacturing, src)
+ return MANUFACTURING_SUCCESS
+
+/obj/machinery/power/manufacturing/crafter/multitool_act(mob/living/user, obj/item/tool)
+ . = NONE
+ var/list/unavailable = list()
+ for(var/datum/crafting_recipe/potential_recipe as anything in cooking ? GLOB.cooking_recipes : GLOB.crafting_recipes)
+ if(craftsman.is_recipe_available(potential_recipe, user))
+ continue
+ var/obj/result = initial(potential_recipe.result)
+ if(istype(result) && initial(result.anchored))
+ continue
+ unavailable += potential_recipe
+ var/result = tgui_input_list(usr, "Recipe", "Select Recipe", (cooking ? GLOB.cooking_recipes : GLOB.crafting_recipes) - unavailable)
+ if(isnull(result) || result == recipe || !user.can_perform_action(src))
+ return ITEM_INTERACT_FAILURE
+ var/dump_target = get_step(src, get_dir(src, user))
+ for(var/atom/movable/thing as anything in contents - circuit)
+ thing.Move(dump_target)
+ recipe = result
+ balloon_alert(user, "set")
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/power/manufacturing/crafter/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == withheld)
+ withheld = null
+
+/obj/machinery/power/manufacturing/crafter/atom_destruction(damage_flag)
+ . = ..()
+ withheld?.Move(drop_location(src))
+
+/obj/machinery/power/manufacturing/crafter/Destroy()
+ . = ..()
+ recipe = null
+ craftsman = null
+ QDEL_NULL(withheld)
+
+/obj/machinery/power/manufacturing/crafter/process(seconds_per_tick)
+ if(!isnull(withheld) && !send_resource(withheld, dir))
+ return
+ if(!isnull(craft_timer))
+ if(surplus() >= power_cost)
+ add_load()
+ else
+ deltimer(craft_timer)
+ craft_timer = null
+ say("Power failure!")
+ return
+ if(isnull(recipe) || !craftsman.check_contents(src, recipe, craftsman.get_surroundings(src)))
+ return
+ flick_overlay_view(mutable_appearance(icon, "crafter_printing"), recipe.time)
+ craft_timer = addtimer(CALLBACK(src, PROC_REF(craft), recipe), recipe.time, TIMER_STOPPABLE)
+
+/obj/machinery/power/manufacturing/crafter/proc/craft(datum/crafting_recipe/recipe)
+ if(QDELETED(src))
+ return
+ craft_timer = null
+ var/atom/movable/result = craftsman.construct_item(src, recipe)
+ if(istype(result))
+ if(isitem(result))
+ result.pixel_x += rand(-4, 4)
+ result.pixel_y += rand(-4, 4)
+ result.Move(src)
+ send_resource(result, dir)
+ else
+ say(result)
+
+/obj/machinery/power/manufacturing/crafter/cooker
+ name = "manufacturing cooking machine" // maybe this shouldnt be available dont wanna make chef useless, though otherwise it would need a sprite
+ desc = "Cooks the set recipe until it runs out of resources. Inputs irrelevant to the recipe are ignored."
+ cooking = TRUE
diff --git a/code/modules/manufactorio/machines/crusher.dm b/code/modules/manufactorio/machines/crusher.dm
new file mode 100644
index 0000000000000..272cfeee02ee3
--- /dev/null
+++ b/code/modules/manufactorio/machines/crusher.dm
@@ -0,0 +1,83 @@
+/obj/machinery/power/manufacturing/crusher //todo make it work for other stuff
+ name = "manufacturing crusher"
+ desc = "Crushes any item put into it, boulders and such. Materials below a sheet are stored in the machine."
+ icon_state = "crusher"
+ circuit = /obj/item/circuitboard/machine/manucrusher
+ /// power used to crush
+ var/crush_cost = 3 KILO WATTS
+ /// how much can we hold
+ var/capacity = 5
+ /// withheld output because output is either blocked or full
+ var/atom/movable/withholding
+ /// list of held mats
+ var/list/obj/item/stack/held_mats = list()
+
+/obj/machinery/power/manufacturing/crusher/update_overlays()
+ . = ..()
+ . += generate_io_overlays(dir, COLOR_ORANGE) // OUT - stuff in it
+ . += generate_io_overlays(REVERSE_DIR(dir), COLOR_MODERATE_BLUE) // IN - to crush
+
+/obj/machinery/power/manufacturing/crusher/Destroy()
+ . = ..()
+ QDEL_NULL(withholding)
+
+/obj/machinery/power/manufacturing/crusher/atom_destruction(damage_flag)
+ withholding?.Move(drop_location())
+ return ..()
+
+/obj/machinery/power/manufacturing/crusher/receive_resource(obj/receiving, atom/from, receive_dir)
+ if(istype(receiving, /obj/item/stack/ore) || receiving.resistance_flags & INDESTRUCTIBLE || !isitem(receiving) || surplus() < crush_cost || receive_dir != REVERSE_DIR(dir))
+ return MANUFACTURING_FAIL
+ if(length(contents - circuit) >= capacity && may_merge_in_contents_and_do_so(receiving))
+ return MANUFACTURING_FAIL_FULL
+ receiving.Move(src, get_dir(receiving, src))
+ START_PROCESSING(SSmanufacturing, src)
+ return MANUFACTURING_SUCCESS
+
+/obj/machinery/power/manufacturing/crusher/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == withholding)
+ withholding = null
+
+/obj/machinery/power/manufacturing/crusher/process(seconds_per_tick) //noot functional
+ if(!isnull(withholding) && !send_resource(withholding, dir))
+ return
+ for(var/material in held_mats)
+ if(held_mats[material] >= 1)
+ var/new_amount = floor(held_mats[material])
+ held_mats[material] -= new_amount
+ if(held_mats[material] <= 0)
+ held_mats -= material
+ withholding = new material(null, new_amount)
+ return
+ var/list/poor_saps = contents - circuit
+ if(!length(poor_saps))
+ return PROCESS_KILL
+ if(surplus() < crush_cost)
+ return
+ var/obj/victim = poor_saps[length(poor_saps)]
+ if(istype(victim)) //todo handling for other things
+ if(!length(victim.custom_materials))
+ add_load(crush_cost)
+ victim.atom_destruction()
+ for(var/obj/object in victim.contents+victim)
+ for(var/datum/material/possible_mat as anything in object.custom_materials)
+ var/quantity = object.custom_materials[possible_mat]
+ object.set_custom_materials(object.custom_materials.Copy() - possible_mat, 1)
+ var/type_to_use = istype(victim, /obj/item/boulder) ? possible_mat.ore_type : possible_mat.sheet_type
+ if(quantity < SHEET_MATERIAL_AMOUNT)
+ if(!(type_to_use in held_mats))
+ held_mats[type_to_use] = quantity / SHEET_MATERIAL_AMOUNT
+ continue
+ held_mats[type_to_use] += quantity / SHEET_MATERIAL_AMOUNT
+ continue
+ var/obj/item/stack/sheet/new_item = new type_to_use(src, quantity / SHEET_MATERIAL_AMOUNT)
+ if(!send_resource(new_item, dir))
+ withholding = new_item
+ return
+ else if(isliving(victim))
+ var/mob/living/poor_sap = victim
+ poor_sap.adjustBruteLoss(95, TRUE)
+ if(!send_resource(poor_sap, dir))
+ withholding = poor_sap
+ return
diff --git a/code/modules/manufactorio/machines/debug.dm b/code/modules/manufactorio/machines/debug.dm
new file mode 100644
index 0000000000000..7c21cf4e989a7
--- /dev/null
+++ b/code/modules/manufactorio/machines/debug.dm
@@ -0,0 +1,18 @@
+/obj/loop_spawner
+ name = "testing loop spawner"
+ icon = 'icons/obj/machines/mining_machines.dmi'
+ icon_state = "unloader"
+ anchored = TRUE
+ color = COLOR_PURPLE
+ /// directions we can output to right now
+ var/to_spawn = /obj/item/screwdriver
+ /// the subsystem to process us
+ var/subsystem_to_process_us = /datum/controller/subsystem/processing/obj
+
+/obj/loop_spawner/Initialize(mapload)
+ . = ..()
+ var/datum/controller/subsystem/processing/subsystem = locate(subsystem_to_process_us) in Master.subsystems
+ START_PROCESSING(subsystem, src)
+
+/obj/loop_spawner/process(seconds_per_tick)
+ new to_spawn(get_step(src, dir))
diff --git a/code/modules/manufactorio/machines/lathe.dm b/code/modules/manufactorio/machines/lathe.dm
new file mode 100644
index 0000000000000..2669e851b931f
--- /dev/null
+++ b/code/modules/manufactorio/machines/lathe.dm
@@ -0,0 +1,145 @@
+/obj/machinery/power/manufacturing/lathe // this is a heavily gutted autolathe
+ name = "manufacturing lathe"
+ desc = "Lathes the set recipe until it runs out of resources. Only accepts sheets or other kinds of material stacks."
+ icon_state = "lathe"
+ circuit = /obj/item/circuitboard/machine/manulathe
+ /// power cost for lathing
+ var/power_cost = 5 KILO WATTS
+ /// design id we print
+ var/design_id
+ ///The container to hold materials
+ var/datum/component/material_container/materials
+ //looping sound for printing items
+ var/datum/looping_sound/lathe_print/print_sound
+ ///Designs related to the autolathe
+ var/datum/techweb/autounlocking/stored_research
+ /// timer id of printing
+ var/busy = FALSE
+ /// our output, if the way out was blocked is held here
+ var/atom/movable/withheld
+
+/obj/machinery/power/manufacturing/lathe/Initialize(mapload)
+ . = ..()
+ print_sound = new(src, FALSE)
+ materials = AddComponent( \
+ /datum/component/material_container, \
+ SSmaterials.materials_by_category[MAT_CATEGORY_ITEM_MATERIAL], \
+ SHEET_MATERIAL_AMOUNT * MAX_STACK_SIZE * 2, \
+ MATCONTAINER_EXAMINE|MATCONTAINER_NO_INSERT, \
+ )
+ if(!GLOB.autounlock_techwebs[/datum/techweb/autounlocking/autolathe])
+ GLOB.autounlock_techwebs[/datum/techweb/autounlocking/autolathe] = new /datum/techweb/autounlocking/autolathe
+ stored_research = GLOB.autounlock_techwebs[/datum/techweb/autounlocking/autolathe]
+
+/obj/machinery/power/manufacturing/lathe/examine(mob/user)
+ . = ..()
+ var/datum/design/design
+ if(!isnull(design_id))
+ design = SSresearch.techweb_design_by_id(design_id)
+ . += span_notice("It is set to print [!isnull(design) ? design.name : "nothing, set with a multitool"].")
+ if(isnull(design))
+ return
+ . += span_notice("It needs:")
+ for(var/valid_type in design.materials)
+ var/atom/ingredient = valid_type
+ var/amount = design.materials[ingredient] / SHEET_MATERIAL_AMOUNT
+
+ . += "[amount] sheets of [initial(ingredient.name)]"
+
+/obj/machinery/power/manufacturing/lathe/update_overlays()
+ . = ..()
+ . += generate_io_overlays(dir, COLOR_ORANGE) // OUT - stuff in it
+ . += generate_io_overlays(REVERSE_DIR(dir), COLOR_MODERATE_BLUE) // IN - to crush
+
+/obj/machinery/power/manufacturing/lathe/Destroy()
+ . = ..()
+ stored_research = null
+ QDEL_NULL(print_sound)
+ materials = null
+ QDEL_NULL(withheld)
+
+/obj/machinery/power/manufacturing/lathe/atom_destruction(damage_flag)
+ withheld?.Move(drop_location())
+ return ..()
+
+/obj/machinery/power/manufacturing/lathe/receive_resource(atom/movable/receiving, atom/from, receive_dir)
+ if(!isstack(receiving) || receiving.resistance_flags & INDESTRUCTIBLE || receive_dir != REVERSE_DIR(dir))
+ return MANUFACTURING_FAIL
+ materials.insert_item(receiving)
+ return MANUFACTURING_SUCCESS
+
+/obj/machinery/power/manufacturing/lathe/multitool_act(mob/living/user, obj/item/tool)
+ . = ..()
+ var/list/name_to_id = list()
+ for(var/id in stored_research.researched_designs)
+ var/datum/design/design = SSresearch.techweb_design_by_id(id)
+ name_to_id[design.name] = id
+ var/result = tgui_input_list(user, "Select Design", "Select Design", sort_list(name_to_id))
+ if(isnull(result))
+ return ITEM_INTERACT_FAILURE
+ design_id = name_to_id[result]
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/power/manufacturing/lathe/process()
+ if(!isnull(withheld) && !send_resource(withheld, dir))
+ return
+
+ var/datum/design/design = SSresearch.techweb_design_by_id(design_id)
+ if(isnull(design) || !(design.build_type & AUTOLATHE))
+ return
+ if(surplus() < power_cost)
+ finalize_build()
+ return
+ //check for materials required. For custom material items decode their required materials
+ var/list/materials_needed = list()
+ for(var/material in design.materials)
+ var/amount_needed = design.materials[material]
+ if(istext(material)) // category
+ for(var/datum/material/valid_candidate as anything in SSmaterials.materials_by_category[material])
+ if(materials.get_material_amount(valid_candidate) < amount_needed)
+ continue
+ material = valid_candidate
+ break
+ if(isnull(material))
+ return
+ materials_needed[material] = amount_needed
+
+ if(!materials.has_materials(materials_needed))
+ return
+
+ var/craft_time = (design.construction_time * design.lathe_time_factor) ** 0.8
+ flick_overlay_view(mutable_appearance(icon, "crafter_printing"), craft_time)
+ print_sound.start()
+ add_load(power_cost)
+ busy = addtimer(CALLBACK(src, PROC_REF(do_make_item), design, materials_needed), craft_time, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_DELETE_ME)
+
+/obj/machinery/power/manufacturing/lathe/proc/do_make_item(datum/design/design, list/materials_needed)
+ finalize_build()
+ if(surplus() < power_cost)
+ return
+
+ var/is_stack = ispath(design.build_path, /obj/item/stack)
+ if(!materials.has_materials(materials_needed))
+ return
+ materials.use_materials(materials_needed)
+
+ var/atom/movable/created
+ if(is_stack)
+ var/obj/item/stack/stack_item = initial(design.build_path)
+ created = new stack_item(null, 1)
+ else
+ created = new design.build_path(null)
+ split_materials_uniformly(materials_needed, target_object = created)
+ if(isitem(created))
+ created.pixel_x = created.base_pixel_x + rand(-6, 6)
+ created.pixel_y = created.base_pixel_y + rand(-6, 6)
+ SSblackbox.record_feedback("nested tally", "lathe_printed_items", 1, list("[type]", "[created.type]"))
+
+ if(!send_resource(created, dir))
+ withheld = created
+
+
+/obj/machinery/power/manufacturing/lathe/proc/finalize_build()
+ print_sound.stop()
+ deltimer(busy)
+ busy = null
diff --git a/code/modules/manufactorio/machines/router.dm b/code/modules/manufactorio/machines/router.dm
new file mode 100644
index 0000000000000..8e1c20214339e
--- /dev/null
+++ b/code/modules/manufactorio/machines/router.dm
@@ -0,0 +1,60 @@
+/obj/machinery/power/manufacturing/router // Basically a splitter
+ name = "manufacturing router"
+ desc = "Distributes input to 3 output directions equally. Stacks are split, and you may toggle outputs with a multitool. May not receive from other routers."
+ allow_mob_bump_intake = TRUE
+ icon_state = "splitter"
+ circuit = /obj/item/circuitboard/machine/manurouter
+ /// outputs disabled with a multitool
+ var/list/disabled_dirs = list()
+ /// directions we can output to right now
+ var/list/directions
+
+/obj/machinery/power/manufacturing/router/Initialize(mapload)
+ . = ..()
+ directions = GLOB.cardinals.Copy()
+
+/obj/machinery/power/manufacturing/router/multitool_act(mob/living/user, obj/item/tool)
+ . = ..()
+ var/to_toggle = get_dir(src, user)
+ if(!(to_toggle in GLOB.cardinals))
+ balloon_alert(user, "stand inline!")
+ return ITEM_INTERACT_FAILURE
+ if(to_toggle in disabled_dirs)
+ disabled_dirs -= to_toggle
+ else
+ disabled_dirs += to_toggle
+ update_appearance(UPDATE_OVERLAYS)
+ balloon_alert(user, "toggled output")
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/power/manufacturing/router/update_overlays()
+ . = ..()
+ for(var/direction in GLOB.cardinals)
+ var/variant
+ if(disabled_dirs.Find(direction))
+ variant = "bl"
+ else
+ variant = (direction == dir) ? "in" : "out"
+ var/image/new_overlay = image(icon, "splitter_[variant]", layer = layer+0.001, dir = direction)
+ . += new_overlay
+
+/obj/machinery/power/manufacturing/router/receive_resource(obj/receiving, atom/from, receive_dir)
+ if(istype(from, /obj/machinery/power/manufacturing/router))
+ return MANUFACTURING_FAIL
+ var/list/filtered = directions - receive_dir - disabled_dirs
+ if(!length(filtered))
+ directions = GLOB.cardinals.Copy()
+ for(var/target in filtered)
+ directions -= target
+ if(isstack(receiving))
+ receiving = handle_stack(receiving, receive_dir)
+ if(send_resource(receiving, target))
+ dir = receive_dir
+ update_appearance(UPDATE_OVERLAYS) // im sorry
+ return MANUFACTURING_SUCCESS
+ return MANUFACTURING_FAIL_FULL
+
+/obj/machinery/power/manufacturing/router/proc/handle_stack(obj/item/stack/stack, direction)
+ if(stack.amount <= 1) // last implementation was just not good so lets cheap out
+ return stack
+ return stack.split_stack(amount = 1)
diff --git a/code/modules/manufactorio/machines/smelter.dm b/code/modules/manufactorio/machines/smelter.dm
new file mode 100644
index 0000000000000..597c9a7b43a50
--- /dev/null
+++ b/code/modules/manufactorio/machines/smelter.dm
@@ -0,0 +1,59 @@
+/obj/machinery/power/manufacturing/smelter
+ name = "manufacturing smelter"
+ desc = "Pretty much incinerates whatever is put into it. Refines ore (not boulders)."
+ icon_state = "smelter"
+ circuit = /obj/item/circuitboard/machine/manusmelter
+ /// power used to smelt
+ var/power_cost = 4 KILO WATTS
+ /// our output, if the way out was blocked is held here
+ var/atom/movable/withheld
+
+/obj/machinery/power/manufacturing/smelter/update_overlays()
+ . = ..()
+ . += generate_io_overlays(dir, COLOR_ORANGE) // OUT - stuff in it
+ . += generate_io_overlays(REVERSE_DIR(dir), COLOR_MODERATE_BLUE) // IN - to crush
+
+/obj/machinery/power/manufacturing/smelter/receive_resource(obj/receiving, atom/from, receive_dir)
+ if(!isitem(receiving) || surplus() < power_cost || receive_dir != REVERSE_DIR(dir))
+ return MANUFACTURING_FAIL
+ var/list/stacks = contents - circuit
+ if(length(stacks) >= 5 && !may_merge_in_contents_and_do_so(receiving))
+ return MANUFACTURING_FAIL_FULL
+ receiving.Move(src, get_dir(receiving, src))
+ START_PROCESSING(SSmanufacturing, src)
+ return MANUFACTURING_SUCCESS
+
+/obj/machinery/power/manufacturing/smelter/Destroy()
+ . = ..()
+ QDEL_NULL(withheld)
+
+/obj/machinery/power/manufacturing/smelter/atom_destruction(damage_flag)
+ withheld?.Move(drop_location())
+ return ..()
+
+/obj/machinery/power/manufacturing/smelter/process(seconds_per_tick)
+ var/list/stacks = contents - circuit
+ if(!length(stacks))
+ return
+
+ var/list/stacks_preprocess = contents - circuit
+ var/obj/item/stack/ore/ore = stacks_preprocess[length(stacks_preprocess)]
+ if(isnull(ore))
+ return
+ if(isnull(withheld) && surplus() >= power_cost)
+ icon_state="smelter_on"
+ add_load(power_cost)
+ if(istype(ore))
+ var/obj/item/stack/new_stack = new ore.refined_type(null, min(5, ore.amount), FALSE)
+ new_stack.moveToNullspace()
+ ore.use(min(5, ore.amount))
+ ore = new_stack
+ else
+ ore.fire_act(1400)
+ withheld = ore
+ else if(surplus() < power_cost)
+ icon_state = "smelter"
+ if(send_resource(withheld, dir))
+ withheld = null // nullspace thumbs down
+ if(!length(contents - circuit))
+ return PROCESS_KILL //we finished
diff --git a/code/modules/manufactorio/machines/sorter.dm b/code/modules/manufactorio/machines/sorter.dm
new file mode 100644
index 0000000000000..b749b14c6d893
--- /dev/null
+++ b/code/modules/manufactorio/machines/sorter.dm
@@ -0,0 +1,149 @@
+/obj/machinery/power/manufacturing/sorter
+ icon_state = "router"
+ name = "conveyor sort-router"
+ desc = "Pushes things on it to its sides following set criteria, set via multitool."
+ layer = BELOW_OPEN_DOOR_LAYER
+ density = FALSE
+ interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND
+ circuit = /obj/item/circuitboard/machine/manusorter
+ /// for mappers; filter path = list(direction, value), otherwise a list of initialized filters
+ var/list/sort_filters = list()
+ /// dir to push to if there is no criteria
+ var/dir_if_not_met
+ /// timer id of the thing that makes stuff move
+ var/delay_timerid
+ /// max filters
+ var/max_filters = 10
+
+/obj/machinery/power/manufacturing/sorter/Initialize(mapload)
+ . = ..()
+ if(isnull(dir_if_not_met))
+ dir_if_not_met = dir
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ for(var/i in 1 to length(sort_filters))
+ var/creating_type = sort_filters[i]
+ var/list/values = sort_filters[creating_type]
+ var/datum/sortrouter_filter/new_type = new creating_type(src)
+ new_type.dir_target = values[1]
+ new_type.value = values[2]
+ sort_filters[i] = new_type
+ START_PROCESSING(SSobj, src)
+
+/obj/machinery/power/manufacturing/sorter/Destroy()
+ . = ..()
+ QDEL_LIST(sort_filters)
+
+/obj/machinery/power/manufacturing/sorter/multitool_act(mob/living/user, obj/item/tool)
+ . = ..()
+ ui_interact(user)
+
+/obj/machinery/power/manufacturing/sorter/receive_resource(atom/movable/receiving, atom/from, receive_dir)
+ if(length(loc.contents) >= MANUFACTURING_TURF_LAG_LIMIT)
+ return MANUFACTURING_FAIL_FULL
+ receiving.Move(loc)
+ return MANUFACTURING_SUCCESS
+
+
+/obj/machinery/power/manufacturing/sorter/ui_data(mob/user)
+ . = list()
+ .["unmet_dir"] = dir_if_not_met
+ .["filters"] = list()
+ for(var/datum/sortrouter_filter/sorting as anything in sort_filters)
+ .["filters"] += list(list(
+ "name" = sorting.return_name(),
+ "ref" = REF(sorting),
+ "inverted" = sorting.inverted,
+ "dir" = sorting.dir_target,
+ ))
+
+/obj/machinery/power/manufacturing/sorter/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("del_filter")
+ var/datum/sortrouter_filter/filter = locate(params["ref"])
+ if(isnull(filter))
+ return
+ sort_filters -= filter
+ qdel(filter)
+ return TRUE
+ if("new_filter")
+ if(length(sort_filters) >= max_filters)
+ return
+ var/static/list/filter_by_name
+ if(!length(filter_by_name))
+ filter_by_name = list()
+ for(var/datum/sortrouter_filter/to_do as anything in subtypesof(/datum/sortrouter_filter))
+ filter_by_name[initial(to_do.name)] = to_do
+ filter_by_name = sort_list(filter_by_name)
+ var/target_type = tgui_input_list(usr, "Select a filter", "New Filter", filter_by_name)
+ if(isnull(target_type)|| !usr.can_perform_action(src, ALLOW_SILICON_REACH))
+ return
+ target_type = filter_by_name[target_type]
+ sort_filters += new target_type(src)
+ return TRUE
+ if("rotate")
+ var/datum/sortrouter_filter/filter = locate(params["ref"])
+ if(isnull(filter))
+ return
+ var/next_ind = GLOB.cardinals.Find(filter.dir_target) + 1
+ filter.dir_target = GLOB.cardinals[WRAP(next_ind, 1, 5)]
+ return TRUE
+ if("rotate_unmet")
+ var/next_ind = GLOB.cardinals.Find(dir_if_not_met) + 1
+ dir_if_not_met = GLOB.cardinals[WRAP(next_ind, 1, 5)]
+ return TRUE
+ if("edit")
+ var/datum/sortrouter_filter/filter = locate(params["ref"])
+ if(isnull(filter))
+ return
+ filter.edit(usr)
+ return TRUE
+ if("shift")
+ var/datum/sortrouter_filter/filter = locate(params["ref"])
+ if(isnull(filter))
+ return
+ var/next_ind = WRAP(sort_filters.Find(filter) + text2num(params["amount"]), 1, length(sort_filters)+1)
+ sort_filters -= filter
+ sort_filters.Insert(next_ind, filter)
+ return TRUE
+
+/obj/machinery/power/manufacturing/sorter/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ManufacturingSorter")
+ ui.open()
+
+/obj/machinery/power/manufacturing/sorter/proc/send_nomobs(atom/movable/moving, dir)
+ var/mutable_appearance/operate = mutable_appearance(icon, "router_operate")
+ operate.dir = dir
+ flick_overlay_view(operate, 1 SECONDS)
+ return ismob(moving) ? moving.Move(get_step(src,dir), dir) : send_resource(moving, dir)
+
+/obj/machinery/power/manufacturing/sorter/process()
+ if(delay_timerid || !length(loc?.contents - 1))
+ return
+ launch_everything()
+
+/obj/machinery/power/manufacturing/sorter/proc/on_entered(datum/source, atom/movable/mover)
+ SIGNAL_HANDLER
+ if(mover == src || !istype(mover) || mover.anchored || delay_timerid)
+ return
+ delay_timerid = addtimer(CALLBACK(src, PROC_REF(launch_everything)), 0.2 SECONDS)
+
+/obj/machinery/power/manufacturing/sorter/proc/launch_everything()
+ delay_timerid = null
+ var/turf/where_we_at = get_turf(src)
+ for(var/atom/movable/mover as anything in where_we_at.contents)
+ if(mover.anchored)
+ continue
+ for(var/datum/sortrouter_filter/sorting as anything in sort_filters)
+ if(sorting.meets_conditions(mover) == sorting.inverted)
+ continue
+ send_nomobs(mover, sorting.dir_target)
+ return
+ send_nomobs(mover, dir_if_not_met)
diff --git a/code/modules/manufactorio/machines/sorter_filters.dm b/code/modules/manufactorio/machines/sorter_filters.dm
new file mode 100644
index 0000000000000..cb7e31cc41ed4
--- /dev/null
+++ b/code/modules/manufactorio/machines/sorter_filters.dm
@@ -0,0 +1,120 @@
+/datum/sortrouter_filter
+ /// name of the filter shown in UI
+ var/name
+ /// if it meets criteria, item is pushed to this direction
+ var/dir_target = NORTH
+ /// value of our filter, checked by us
+ var/value = ""
+ /// is our output inverted? checked by sorter
+ var/inverted = FALSE
+ /// the sorter we belong to
+ var/obj/machinery/power/manufacturing/sorter/sorter
+
+/datum/sortrouter_filter/New(sorter)
+ . = ..()
+ if(isnull(sorter))
+ return
+ src.sorter = sorter
+
+/datum/sortrouter_filter/Destroy()
+ . = ..()
+ if(isnull(sorter))
+ return
+ sorter = null
+
+/datum/sortrouter_filter
+
+/datum/sortrouter_filter/proc/return_name()
+ return name
+
+/datum/sortrouter_filter/proc/edit(mob/user)
+ to_chat(user, "This filter is not editable.")
+
+/datum/sortrouter_filter/proc/meets_conditions(atom/checking)
+
+/datum/sortrouter_filter/is_stack
+ name = "input is stack"
+
+/datum/sortrouter_filter/is_stack/meets_conditions(atom/checking)
+ return isstack(checking)
+
+/datum/sortrouter_filter/is_ore
+ name = "input is ore"
+
+/datum/sortrouter_filter/is_ore/meets_conditions(atom/checking)
+ return istype(checking, /obj/item/stack/ore)
+
+/datum/sortrouter_filter/is_mail
+ name = "input is mail"
+
+/datum/sortrouter_filter/is_mail/meets_conditions(atom/checking)
+ return istype(checking, /obj/item/mail)
+
+/datum/sortrouter_filter/is_tagged
+ name = "input is tagged X"
+
+/datum/sortrouter_filter/is_tagged/edit(mob/user)
+ var/target = tgui_input_list(user, "Select a tag", "Tag", sort_list(GLOB.TAGGERLOCATIONS))
+ if(isnull(target) || !user.can_perform_action(sorter, ALLOW_SILICON_REACH))
+ return
+ value = GLOB.TAGGERLOCATIONS.Find(target)
+
+/datum/sortrouter_filter/is_tagged/return_name()
+ return "input is tagged [value ? GLOB.TAGGERLOCATIONS[value] : ""]"
+
+/datum/sortrouter_filter/is_tagged/meets_conditions(checking)
+ var/obj/item/delivery/mail_or_delivery = checking
+ var/sort_tag
+ if(istype(checking, /obj/item/delivery) || istype(checking, /obj/item/mail))
+ sort_tag = mail_or_delivery.sort_tag
+
+ return value == sort_tag
+
+/datum/sortrouter_filter/name_contains
+ name = "input's name contains"
+
+/datum/sortrouter_filter/name_contains/edit(mob/user)
+ var/target = tgui_input_text(user, "What should it contain?", "Name", value, 12)
+ if(isnull(target)|| !user.can_perform_action(sorter, ALLOW_SILICON_REACH))
+ return
+ value = target
+
+/datum/sortrouter_filter/name_contains/return_name()
+ return "input's name contains [value]"
+
+/datum/sortrouter_filter/name_contains/meets_conditions(atom/checking)
+ return findtext(LOWER_TEXT(checking.name), value)
+
+/datum/sortrouter_filter/is_path_specific
+ name = "input is specific item"
+ /// are we currently listening for an item to set as our filter?
+ var/currently_listening = FALSE
+
+/datum/sortrouter_filter/is_path_specific/edit(mob/user)
+ name = initial(name)
+ if(!currently_listening)
+ name = "awaiting item"
+ to_chat(user, "Hit the sorter with the item of choice to set the filter.")
+ sorter.balloon_alert(user, "awaiting item!")
+ currently_listening = TRUE
+ RegisterSignal(sorter, COMSIG_ATOM_ATTACKBY, PROC_REF(sorter_hit))
+ else
+ currently_listening = FALSE
+ UnregisterSignal(sorter, COMSIG_ATOM_ATTACKBY)
+
+/datum/sortrouter_filter/is_path_specific/proc/sorter_hit(datum/source, obj/item/attacking_item, user, params)
+ currently_listening = FALSE
+ value = attacking_item.type
+ name = attacking_item.name
+ sorter.balloon_alert(user, "filter set")
+ UnregisterSignal(sorter, COMSIG_ATOM_ATTACKBY)
+ return COMPONENT_NO_AFTERATTACK
+
+/datum/sortrouter_filter/is_path_specific/meets_conditions(atom/checking)
+ return checking.type == value
+
+/datum/sortrouter_filter/is_path_specific/subtypes
+ name = "input is specific kind of item"
+
+/datum/sortrouter_filter/is_path_specific/subtypes/meets_conditions(atom/checking)
+ return istype(checking.type, value)
diff --git a/code/modules/manufactorio/machines/storagebox.dm b/code/modules/manufactorio/machines/storagebox.dm
new file mode 100644
index 0000000000000..b8a6f5cccac39
--- /dev/null
+++ b/code/modules/manufactorio/machines/storagebox.dm
@@ -0,0 +1,46 @@
+/obj/machinery/power/manufacturing/storagebox
+ name = "manufacturing storage unit"
+ desc = "Its basically a box. Receives resources (if anchored). Needs a machine to take stuff out of without dumping everything out."
+ icon_state = "box"
+ /// how much can we hold
+ var/max_stuff = 16
+
+/obj/machinery/power/manufacturing/request_resource() //returns last inserted item
+ var/list/real_contents = contents - circuit
+ if(!length(real_contents))
+ return
+ return (real_contents)[length(real_contents)]
+
+/obj/machinery/power/manufacturing/storagebox/receive_resource(atom/movable/receiving, atom/from, receive_dir)
+ if(iscloset(receiving) && length(receiving.contents))
+ return MANUFACTURING_FAIL
+ if(length(contents - circuit) >= max_stuff && !may_merge_in_contents_and_do_so(receiving))
+ return MANUFACTURING_FAIL_FULL
+ receiving.Move(src,receive_dir)
+ return MANUFACTURING_SUCCESS
+
+/obj/machinery/power/manufacturing/storagebox/container_resist_act(mob/living/user)
+ . = ..()
+ user.Move(drop_location())
+
+/obj/machinery/power/manufacturing/storagebox/screwdriver_act(mob/living/user, obj/item/tool)
+ . = NONE
+ balloon_alert(user, "disassembling...")
+ if(!do_after(user, 5 SECONDS, src))
+ return ITEM_INTERACT_FAILURE
+ atom_destruction()
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/power/manufacturing/storagebox/atom_destruction(damage_flag)
+ new /obj/item/stack/sheet/iron(drop_location(), 10)
+ dump_inventory_contents()
+ return ..()
+
+/obj/machinery/power/manufacturing/storagebox/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(user.combat_mode)
+ return
+ balloon_alert(user, "dumping..")
+ if(!do_after(user, 1.25 SECONDS, src))
+ return
+ dump_inventory_contents()
diff --git a/code/modules/manufactorio/machines/unloader.dm b/code/modules/manufactorio/machines/unloader.dm
new file mode 100644
index 0000000000000..982c33582684e
--- /dev/null
+++ b/code/modules/manufactorio/machines/unloader.dm
@@ -0,0 +1,78 @@
+/obj/machinery/power/manufacturing/unloader
+ name = "manufacturing crate unloader"
+ desc = "Unloads crates (and ore boxes) passed into it, ejecting the empty crate to the side and its contents forwards. Use a multitool to flip the crate output."
+ icon = 'icons/obj/machines/mining_machines.dmi'
+ icon_state = "unloader-corner"
+ circuit = /obj/item/circuitboard/machine/manuunloader
+ /// power used per attempt to unload a crate
+ var/power_to_unload_crate = 2 KILO WATTS
+ /// whether the side we output unloaded crates is flipped
+ var/flip_side = FALSE
+
+/obj/machinery/power/manufacturing/unloader/update_overlays()
+ . = ..()
+ . += generate_io_overlays(dir, COLOR_ORANGE) // OUT - stuff in it
+ . += generate_io_overlays(REVERSE_DIR(dir), COLOR_MODERATE_BLUE) // IN - crate
+ . += generate_io_overlays(turn(dir, flip_side ? 90 : -90), COLOR_ORANGE) // OUT -- empty crate
+
+/obj/machinery/power/manufacturing/unloader/request_resource() //returns held crate if someone wants to do that for some reason
+ var/list/real_contents = contents - circuit
+ if(!length(real_contents))
+ return
+ return (real_contents)[1]
+
+/obj/machinery/power/manufacturing/unloader/multitool_act(mob/living/user, obj/item/tool)
+ . = ..()
+ balloon_alert(user, "flipped")
+ flip_side = !flip_side
+ update_appearance()
+
+/obj/machinery/power/manufacturing/unloader/receive_resource(obj/receiving, atom/from, receive_dir)
+ if(surplus() < power_to_unload_crate || receive_dir != REVERSE_DIR(dir))
+ return MANUFACTURING_FAIL
+ var/list/real_contents = contents - circuit
+ if(length(real_contents))
+ return MANUFACTURING_FAIL_FULL
+
+ var/obj/structure/closet/as_closet = receiving
+ var/obj/structure/ore_box/as_orebox = receiving
+ if(istype(as_closet))
+ if(!as_closet.can_open())
+ return MANUFACTURING_FAIL
+ else if(!istype(as_orebox))
+ return MANUFACTURING_FAIL
+ receiving.Move(src, get_dir(receiving, src))
+ START_PROCESSING(SSfastprocess, src)
+ return MANUFACTURING_SUCCESS
+
+/obj/machinery/power/manufacturing/unloader/process(seconds_per_tick)
+ var/list/real_contents = contents - circuit
+ if(!length(real_contents))
+ return PROCESS_KILL
+ if(surplus() < power_to_unload_crate)
+ return
+ add_load(power_to_unload_crate)
+ var/obj/structure/closet/closet = real_contents[1]
+ if(istype(closet))
+ return unload_crate(closet)
+ else
+ return unload_orebox(closet)
+
+/obj/machinery/power/manufacturing/unloader/proc/unload_crate(obj/structure/closet/closet)
+ if (!closet.contents_initialized)
+ closet.contents_initialized = TRUE
+ closet.PopulateContents()
+ SEND_SIGNAL(closet, COMSIG_CLOSET_CONTENTS_INITIALIZED)
+ for(var/atom/thing as anything in closet.contents)
+ if(ismob(thing))
+ continue
+ send_resource(thing, dir)
+ if(!length(closet.contents) && send_resource(closet, turn(dir, flip_side ? 90 : -90)))
+ closet.open(force = TRUE)
+ return PROCESS_KILL
+
+/obj/machinery/power/manufacturing/unloader/proc/unload_orebox(obj/structure/ore_box/box)
+ for(var/atom/thing as anything in box.contents)
+ send_resource(thing, dir)
+ if(!length(box.contents) && send_resource(box, turn(dir, flip_side ? 90 : -90)))
+ return PROCESS_KILL
diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/puzzle.dm b/code/modules/mapfluff/ruins/lavalandruin_code/puzzle.dm
index ef6dc902c9f08..8501c21cbccf7 100644
--- a/code/modules/mapfluff/ruins/lavalandruin_code/puzzle.dm
+++ b/code/modules/mapfluff/ruins/lavalandruin_code/puzzle.dm
@@ -160,10 +160,10 @@
var/y = width - round((id - 1) / width)
var/x = ((id - 1) % width) + 1
- var/x_start = 1 + (x - 1) * world.icon_size
- var/x_end = x_start + world.icon_size - 1
- var/y_start = 1 + ((y - 1) * world.icon_size)
- var/y_end = y_start + world.icon_size - 1
+ var/x_start = 1 + (x - 1) * ICON_SIZE_X
+ var/x_end = x_start + ICON_SIZE_X - 1
+ var/y_start = 1 + ((y - 1) * ICON_SIZE_Y)
+ var/y_end = y_start + ICON_SIZE_Y - 1
var/icon/T = new(base_icon)
T.Crop(x_start,y_start,x_end,y_end)
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/commsbuoy.dm b/code/modules/mapfluff/ruins/spaceruin_code/commsbuoy.dm
new file mode 100644
index 0000000000000..895200d487a1b
--- /dev/null
+++ b/code/modules/mapfluff/ruins/spaceruin_code/commsbuoy.dm
@@ -0,0 +1,267 @@
+/obj/structure/fluff/commsbuoy_receiver
+ name = "interstellar receiver"
+ desc = "A dish-shaped component of the Comms Buoy used to detect and record interstellar signals."
+ icon = 'icons/obj/machines/telecomms.dmi'
+ icon_state = "broadcast receiver"
+
+/obj/structure/fluff/commsbuoy_processor
+ name = "comms buoy processor unit"
+ desc = "This machine is used to process and unscramble interstellar transmissions, to then be relayed and broadcast."
+ icon = 'icons/obj/machines/telecomms.dmi'
+ icon_state = "processor"
+
+/obj/structure/fluff/commsbuoy_broadcaster
+ name = "interstellar broadcaster"
+ desc = "A dish-shaped component of the Comms Buoy used to broadcast processed interstellar signals."
+ icon = 'icons/obj/machines/telecomms.dmi'
+ icon_state = "broadcaster"
+
+/obj/structure/fluff/sat_dish
+ name = "satellite dish"
+ desc = "I wonder if they get any sports channels out here."
+ density = FALSE
+ deconstructible = TRUE
+ icon = 'icons/obj/fluff/general.dmi'
+ icon_state = "sat_dish"
+
+/obj/item/keycard/nt_commsbuoy
+ name = "Nanotrasen comms buoy keycard"
+ desc = "A keycard with the NT logo prominently displayed. The last user broke off the end; the card can still swipe, but this won't insert \
+ into any chip readers now. On the back, mostly obscured by dried blood, the text \"SPINWARD\" is printed, followed by an illegible ID string."
+ color = "#4c80b1"
+ puzzle_id = "nt_commsbuoy"
+
+/obj/machinery/door/puzzle/keycard/nt_commsbuoy
+ name = "secure airlock"
+ puzzle_id = "nt_commsbuoy"
+
+/area/ruin/space/nt_commsbuoy
+ name = "\improper Nanotrasen Comms Buoy"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
+ has_gravity = FALSE
+ ambientsounds = list(
+ 'sound/ambience/engineering/ambisin2.ogg',
+ 'sound/ambience/misc/signal.ogg',
+ 'sound/ambience/misc/signal.ogg',
+ 'sound/ambience/general/ambigen9.ogg',
+ 'sound/ambience/engineering/ambitech.ogg',
+ 'sound/ambience/engineering/ambitech2.ogg',
+ 'sound/ambience/engineering/ambitech3.ogg',
+ 'sound/ambience/misc/ambimystery.ogg',
+ ) //same ambience as tcommsat
+
+/obj/item/paper/fluff/ruins/nt_commsbuoy
+ color = COLOR_BLUE_GRAY
+
+/obj/item/paper/fluff/ruins/nt_commsbuoy/table_of_contents
+ name = "Table of Contents: NT-EBCB Model 7"
+ desc = "The Table of Contents page, text mostly faded. Rest of handbook not included."
+ default_raw_text = {"
+