diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm index 0bfcc133f0c98..e5f7305eba078 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm @@ -338,7 +338,7 @@ /obj/structure/railing/corner{ dir = 4 }, -/mob/living/simple_animal/hostile/mimic/crate, +/mob/living/basic/mimic/crate, /turf/open/floor/plating/snowed/icemoon, /area/icemoon/underground/explored) "fO" = ( @@ -2073,7 +2073,7 @@ /turf/open/floor/plating/snowed/smoothed/icemoon, /area/icemoon/underground/explored) "KV" = ( -/mob/living/simple_animal/hostile/mimic/crate, +/mob/living/basic/mimic/crate, /turf/open/floor/plating/snowed/icemoon, /area/icemoon/underground/explored) "KY" = ( diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index ff06bb5914c6b..758a5c877925e 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -26116,7 +26116,7 @@ /turf/open/floor/iron/smooth_large, /area/station/science/robotics/mechbay) "iPU" = ( -/mob/living/simple_animal/hostile/mimic/crate, +/mob/living/basic/mimic/crate, /turf/open/floor/plating, /area/station/maintenance/fore/lesser) "iPW" = ( diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm index 0295a23f50395..64cad41bbebb1 100644 --- a/_maps/map_files/wawastation/wawastation.dmm +++ b/_maps/map_files/wawastation/wawastation.dmm @@ -840,7 +840,7 @@ /obj/effect/decal/cleanable/blood/old{ icon_state = "gib3-old" }, -/mob/living/simple_animal/hostile/mimic, +/mob/living/basic/mimic/crate, /turf/open/floor/iron/white, /area/station/maintenance/aft/upper) "anu" = ( diff --git a/_maps/virtual_domains/psyker_shuffle.dmm b/_maps/virtual_domains/psyker_shuffle.dmm index c744cecf0b430..5f303d4ae0471 100644 --- a/_maps/virtual_domains/psyker_shuffle.dmm +++ b/_maps/virtual_domains/psyker_shuffle.dmm @@ -64,11 +64,7 @@ /turf/template_noop, /area/template_noop) "r" = ( -/mob/living/simple_animal/hostile/mimic, -/turf/open/indestructible/dark, -/area/virtual_domain) -"s" = ( -/mob/living/simple_animal/hostile/mimic/crate, +/mob/living/basic/mimic/crate, /turf/open/indestructible/dark, /area/virtual_domain) "t" = ( @@ -767,7 +763,7 @@ Y Q Q Q -s +r M Q Q @@ -878,11 +874,11 @@ o Y a Q -s +r Y Y Y -s +r Q Q Q diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index d77817a203980..6a1433fa5ec86 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -312,3 +312,5 @@ #define BB_TURTLE_HEADBUTT_VICTIM "turtle_headbutt_victim" ///flore we must smell #define BB_TURTLE_FLORA_TARGET "turtle_flora_target" + +#define BB_GUNMIMIC_GUN_EMPTY "BB_GUNMIMIC_GUN_EMPTY" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 2044aab51e24d..4d9c6e9c00bf9 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -558,19 +558,29 @@ #define COMSIG_ASSEMBLY_DETACHED "assembly_detached" /* - * The following two signals are separate from the above two because buttons don't set the holder of the inserted assembly. + * The following four signals are separate from the above two because buttons and pressure plates don't set the holder of the inserted assembly. * This causes subtle behavioral differences that future handlers for these signals may need to account for, * even if none of the currently implemented handlers do. */ -/// Sent from /obj/machinery/button/assembly_act(obj/machinery/button/button, mob/user) +/// Sent when an assembly is added to a button : (obj/machinery/button/button, mob/user) #define COMSIG_ASSEMBLY_ADDED_TO_BUTTON "assembly_added_to_button" -/// Sent from /obj/machinery/button/remove_assembly(obj/machinery/button/button, mob/user) +/// Sent when an assembly is removed from a button : (obj/machinery/button/button, mob/user) #define COMSIG_ASSEMBLY_REMOVED_FROM_BUTTON "assembly_removed_from_button" +/// Sent when an assembly is added to a pressure plate : (obj/item/pressureplate/pressure_plate, mob/user) +#define COMSIG_ASSEMBLY_ADDED_TO_PRESSURE_PLATE "assembly_added_to_pressure_plate" + +/// Sent when an assembly is removed from a pressure plate : (obj/item/pressureplate/pressure_plate, mob/user) +#define COMSIG_ASSEMBLY_REMOVED_FROM_PRESSURE_PLATE "assembly_removed_from_pressure_playe" + /// Sent from /datum/powernet/add_cable() #define COMSIG_CABLE_ADDED_TO_POWERNET "cable_added_to_powernet" /// Sent from /datum/powernet/remove_cable() #define COMSIG_CABLE_REMOVED_FROM_POWERNET "cable_removed_from_powernet" + +/// Sent from /datum/wires/attach_assembly() : (atom/holder) +#define COMSIG_ASSEMBLY_PRE_ATTACH "assembly_pre_attach" + #define COMPONENT_CANCEL_ATTACH (1<<0) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index dc3d6fa0c1d70..5d35a7fcb8718 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1424,4 +1424,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Mob doesn't get closed eyelids overlay when it gets knocked out cold or dies #define TRAIT_NO_EYELIDS "no_eyelids" +/// Trait applied when the wire bundle component is added to an [/obj/item/integrated_circuit] +#define TRAIT_COMPONENT_WIRE_BUNDLE "component_wire_bundle" + // END TRAIT DEFINES diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm index 4926996f26bd3..034bb8897e6e6 100644 --- a/code/__DEFINES/wires.dm +++ b/code/__DEFINES/wires.dm @@ -2,6 +2,30 @@ #define COMSIG_CUT_WIRE(wire) "cut_wire [wire]" #define COMSIG_MEND_WIRE(wire) "mend_wire [wire]" +/// from base of /datum/wires/proc/on_pulse : (wire, mob/user) +#define COMSIG_PULSE_WIRE "pulse_wire" + +// Directionality of wire pulses + +/// The wires interact with their holder when pulsed +#define WIRES_INPUT (1<<0) +/// The wires have a reason to toggle whether attached assemblies are armed +#define WIRES_TOGGLE_ARMED (1<<1) +/// The wires only want to activate assemblies that do something other than (dis)arming themselves +#define WIRES_FUNCTIONAL_OUTPUT (1<<2) +/// The holder can both pulse its wires and be affected by its wires getting pulsed +#define WIRES_ALL (WIRES_INPUT | WIRES_TOGGLE_ARMED | WIRES_FUNCTIONAL_OUTPUT) + +/// The assembly can pulse a wire it is attached to +#define ASSEMBLY_INPUT (1<<0) +/// The assembly toggles whether it will pulse the attached wire when it is pulsed by the attached wire +#define ASSEMBLY_TOGGLE_ARMED (1<<1) +/// The assembly does something other than just (dis)arming itself when it is pulsed by the wire it is attached to +#define ASSEMBLY_FUNCTIONAL_OUTPUT (1<<2) +/// The assembly can both pulse the wire it is attached to, and (dis)arms itself when pulsed by the wire +#define ASSEMBLY_TOGGLEABLE_INPUT (ASSEMBLY_INPUT | ASSEMBLY_TOGGLE_ARMED) +#define ASSEMBLY_ALL (ASSEMBLY_TOGGLEABLE_INPUT | ASSEMBLY_FUNCTIONAL_OUTPUT) + //retvals for attempt_wires_interaction #define WIRE_INTERACTION_FAIL 0 #define WIRE_INTERACTION_SUCCESSFUL 1 @@ -72,3 +96,5 @@ #define AI_WIRE_DISABLED 1 #define AI_WIRE_HACKED 2 #define AI_WIRE_DISABLED_HACKED -1 + +#define MAX_WIRE_COUNT 17 diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 5ece846e84de3..6024f7991388d 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -29,7 +29,7 @@ GLOBAL_LIST_INIT(abstract_mob_types, list( /mob/living/simple_animal/hostile/asteroid/elite, /mob/living/simple_animal/hostile/asteroid, /mob/living/simple_animal/hostile/megafauna, - /mob/living/simple_animal/hostile/mimic, // Cannot exist if spawned without being passed an item reference + /mob/living/basic/mimic, // Cannot exist if spawned without being passed an item reference /mob/living/simple_animal/hostile/retaliate, /mob/living/simple_animal/hostile, /mob/living/simple_animal/soulscythe, // As mimic, can't exist if spawned outside an item diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 4edc46bb50303..e351e155dc170 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -687,6 +687,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CIRCUIT_UI_OPEN" = TRAIT_CIRCUIT_UI_OPEN, "TRAIT_CIRCUIT_UNDUPABLE" = TRAIT_CIRCUIT_UNDUPABLE, "TRAIT_COMPONENT_MMI" = TRAIT_COMPONENT_MMI, + "TRAIT_COMPONENT_WIRE_BUNDLE" = TRAIT_COMPONENT_WIRE_BUNDLE, ), /obj/item/modular_computer = list( "TRAIT_MODPC_HALVED_DOWNLOAD_SPEED" = TRAIT_MODPC_HALVED_DOWNLOAD_SPEED, diff --git a/code/datums/components/grillable.dm b/code/datums/components/grillable.dm index f584808a1f3bd..3b3ef047c833f 100644 --- a/code/datums/components/grillable.dm +++ b/code/datums/components/grillable.dm @@ -1,3 +1,5 @@ +#define IDEAL_GRILLING_TEMPERATURE 200 + T0C + /datum/component/grillable dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS // So you can change grill results with various cookstuffs ///Result atom type of grilling this object @@ -14,6 +16,12 @@ var/who_placed_us /// Reagents that should be added to the result var/list/added_reagents + /// Open turf we were last placed on, to check temperature + var/turf/open/listening_turf + /// Are we grilling right now? + var/is_grilling = FALSE + /// What's our current air temperature? + var/current_temperature = 0 /datum/component/grillable/Initialize(cook_result, required_cook_time, positive_result, use_large_steam_sprite, list/added_reagents) . = ..() @@ -32,14 +40,20 @@ RegisterSignal(parent, COMSIG_ITEM_GRILL_TURNED_OFF, PROC_REF(on_grill_turned_off)) RegisterSignal(parent, COMSIG_ITEM_GRILL_PROCESS, PROC_REF(on_grill)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_location_changed)) + on_location_changed(parent) /datum/component/grillable/UnregisterFromParent() + if (listening_turf) + UnregisterSignal(listening_turf, COMSIG_TURF_EXPOSE) + UnregisterSignal(parent, list( COMSIG_ATOM_EXAMINE, COMSIG_ITEM_GRILL_TURNED_ON, COMSIG_ITEM_GRILL_TURNED_OFF, COMSIG_ITEM_GRILL_PROCESS, COMSIG_ITEM_GRILL_PLACED, + COMSIG_MOVABLE_MOVED )) // Inherit the new values passed to the component @@ -55,6 +69,32 @@ if(use_large_steam_sprite) src.use_large_steam_sprite = use_large_steam_sprite +/datum/component/grillable/Destroy(force) + . = ..() + STOP_PROCESSING(SSmachines, src) + listening_turf = null + +/// Signal proc for [COMSIG_MOVABLE_MOVED], our location has changed and we should register for temperature information +/datum/component/grillable/proc/on_location_changed(atom/source) + SIGNAL_HANDLER + + if (is_grilling) + on_grill_turned_off(source) + STOP_PROCESSING(SSmachines, src) + + if (listening_turf) + UnregisterSignal(listening_turf, COMSIG_TURF_EXPOSE) + + if (isnull(source)) + return + + var/turf/open/current_turf = source.loc + if (!isopenturf(current_turf)) + return + listening_turf = current_turf + RegisterSignal(current_turf, COMSIG_TURF_EXPOSE, PROC_REF(on_turf_atmos_changed)) + on_turf_atmos_changed(current_turf, current_turf.air, current_turf.air?.temperature || 0) + /// Signal proc for [COMSIG_ITEM_GRILL_PLACED], item is placed on the grill. /datum/component/grillable/proc/on_grill_placed(datum/source, mob/griller) SIGNAL_HANDLER @@ -62,12 +102,11 @@ if(griller && griller.mind) who_placed_us = REF(griller.mind) - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) - /// Signal proc for [COMSIG_ITEM_GRILL_TURNED_ON], starts the grilling process. /datum/component/grillable/proc/on_grill_turned_on(datum/source) RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(add_grilled_item_overlay)) + is_grilling = TRUE var/atom/atom_parent = parent atom_parent.update_appearance() @@ -75,6 +114,7 @@ /datum/component/grillable/proc/on_grill_turned_off(datum/source) UnregisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS) + is_grilling = FALSE var/atom/atom_parent = parent atom_parent.update_appearance() @@ -138,15 +178,37 @@ else examine_list += span_danger("[parent] should probably not be put on the grill.") -///Ran when an object moves from the grill -/datum/component/grillable/proc/on_moved(atom/source, atom/OldLoc, Dir, Forced) +/datum/component/grillable/proc/add_grilled_item_overlay(datum/source, list/overlays) SIGNAL_HANDLER - UnregisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS) - UnregisterSignal(parent, COMSIG_MOVABLE_MOVED) - source.update_appearance() + overlays += mutable_appearance('icons/effects/steam.dmi', "[use_large_steam_sprite ? "steam_triple" : "steam_single"]", ABOVE_OBJ_LAYER) -/datum/component/grillable/proc/add_grilled_item_overlay(datum/source, list/overlays) +/// Signal proc for [COMSIG_TURF_EXPOSE], atmosphere might be hot enough for grilling. +/datum/component/grillable/proc/on_turf_atmos_changed(turf/open/source, datum/gas_mixture/air, exposed_temperature) SIGNAL_HANDLER - overlays += mutable_appearance('icons/effects/steam.dmi', "[use_large_steam_sprite ? "steam_triple" : "steam_single"]", ABOVE_OBJ_LAYER) + if (!is_grilling) + if (exposed_temperature < FIRE_MINIMUM_TEMPERATURE_TO_EXIST) + return + on_grill_turned_on(source) + START_PROCESSING(SSmachines, src) + current_temperature = exposed_temperature + else + if (exposed_temperature >= FIRE_MINIMUM_TEMPERATURE_TO_EXIST) + current_temperature = exposed_temperature + return + on_grill_turned_off(source) + STOP_PROCESSING(SSmachines, src) + +// Grill while exposed to hot air +/datum/component/grillable/process(seconds_per_tick) + var/atom/atom_parent = parent + + // Grill faster as we approach 200 degrees celsius + var/check_temperature = clamp(current_temperature, FIRE_MINIMUM_TEMPERATURE_TO_EXIST, IDEAL_GRILLING_TEMPERATURE) + var/temp_scale = (check_temperature - FIRE_MINIMUM_TEMPERATURE_TO_EXIST) / (IDEAL_GRILLING_TEMPERATURE - FIRE_MINIMUM_TEMPERATURE_TO_EXIST) + var/speed_modifier = LERP(0.5, 1, temp_scale) + + on_grill(parent, atom_parent.loc, seconds_per_tick * speed_modifier) + +#undef IDEAL_GRILLING_TEMPERATURE diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm index ca1bb82ace785..ec900943997f1 100644 --- a/code/datums/components/squeak.dm +++ b/code/datums/components/squeak.dm @@ -11,8 +11,8 @@ var/step_delay = 1 // This is to stop squeak spam from inhand usage - var/last_use = 0 - var/use_delay = 20 + COOLDOWN_DECLARE(spam_cooldown) + var/use_delay = 2 SECONDS ///extra-range for this component's sound var/sound_extra_range = -1 @@ -102,6 +102,10 @@ return if(ismob(arrived) && !arrived.density) // Prevents 10 overlapping mice from making an unholy sound while moving return + if(isliving(arrived)) + var/mob/living/living_arrived = arrived + if(living_arrived.mob_size < MOB_SIZE_HUMAN) + return var/atom/current_parent = parent if(isturf(current_parent?.loc)) play_squeak() @@ -109,8 +113,8 @@ /datum/component/squeak/proc/use_squeak() SIGNAL_HANDLER - if(last_use + use_delay < world.time) - last_use = world.time + if(COOLDOWN_FINISHED(src, spam_cooldown)) + COOLDOWN_START(src, spam_cooldown, use_delay) play_squeak() /datum/component/squeak/proc/on_equip(datum/source, mob/equipper, slot) diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm index 73bdc511ee4ba..13b9420d8366a 100644 --- a/code/datums/wires/_wires.dm +++ b/code/datums/wires/_wires.dm @@ -7,16 +7,14 @@ if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL) return TRUE if(isassembly(I)) - var/obj/item/assembly/A = I - if(A.attachable) - return TRUE + return TRUE /atom/proc/attempt_wire_interaction(mob/user) if(!wires) return WIRE_INTERACTION_FAIL if(!user.CanReach(src)) return WIRE_INTERACTION_FAIL - wires.interact(user) + INVOKE_ASYNC(wires, TYPE_PROC_REF(/datum/wires, interact), user) return WIRE_INTERACTION_BLOCK /datum/wires @@ -29,6 +27,9 @@ /// The display name for the wire set shown in station blueprints. Not shown in blueprints if randomize is TRUE or it's an item NT wouldn't know about (Explosives/Nuke). Also used in the hacking interface. var/proper_name = "Unknown" + /// Whether pulsed wires affect the holder, and/or the holder pulses its wires + var/wire_behavior = WIRES_INPUT + /// List of all wires. var/list/wires = list() /// List of cut wires. @@ -179,6 +180,7 @@ /datum/wires/proc/pulse(wire, user, force=FALSE) if(!force && is_cut(wire)) return + SEND_SIGNAL(src, COMSIG_PULSE_WIRE, wire, user) on_pulse(wire, user) /datum/wires/proc/pulse_color(color, mob/living/user, force=FALSE) @@ -191,7 +193,7 @@ return TRUE /datum/wires/proc/attach_assembly(color, obj/item/assembly/S) - if(S && istype(S) && S.attachable && !is_attached(color)) + if(S && istype(S) && S.assembly_behavior && !is_attached(color) && !(SEND_SIGNAL(S, COMSIG_ASSEMBLY_PRE_ATTACH, holder) & COMPONENT_CANCEL_ATTACH)) assemblies[color] = S S.forceMove(holder) S.connected = src @@ -384,13 +386,13 @@ I = L.get_active_held_item() if(isassembly(I)) var/obj/item/assembly/A = I - if(A.attachable) + if(A.assembly_behavior & wire_behavior) if(!L.temporarilyRemoveItemFromInventory(A)) return if(!attach_assembly(target_wire, A)) A.forceMove(L.drop_location()) . = TRUE else - to_chat(L, span_warning("You need an attachable assembly!")) + to_chat(L, span_warning("You cannot attach this assembly to these wires!")) #undef MAXIMUM_EMP_WIRES diff --git a/code/datums/wires/scanner_gate.dm b/code/datums/wires/scanner_gate.dm index 14752ec6795ec..cb363d68f95ea 100644 --- a/code/datums/wires/scanner_gate.dm +++ b/code/datums/wires/scanner_gate.dm @@ -2,6 +2,7 @@ holder_type = /obj/machinery/scanner_gate proper_name = "Scanner Gate" wires = list(WIRE_ACCEPT, WIRE_DENY, WIRE_DISABLE) + wire_behavior = WIRES_FUNCTIONAL_OUTPUT /datum/wires/scanner_gate/on_pulse(wire, user) . = ..() diff --git a/code/datums/wires/wire_bundle_component.dm b/code/datums/wires/wire_bundle_component.dm new file mode 100644 index 0000000000000..5cd4ffa2af620 --- /dev/null +++ b/code/datums/wires/wire_bundle_component.dm @@ -0,0 +1,24 @@ +#define CAPACITY_PER_WIRE 10 + +/datum/wires/wire_bundle_component + holder_type = /atom //Anything that can have a shell component, really. + randomize = TRUE + wire_behavior = WIRES_ALL + +/datum/wires/wire_bundle_component/New(atom/holder) + var/datum/component/shell/shell_comp = holder.GetComponent(/datum/component/shell) + if(!istype(shell_comp)) + CRASH("Holder does not have a shell component!") + var/wire_count = clamp(round(shell_comp.capacity / CAPACITY_PER_WIRE, 1), 1, MAX_WIRE_COUNT) + for(var/index in 1 to wire_count) + wires += "Port [index]" + ..() + +/datum/wires/wire_bundle_component/always_reveal_wire(color) + return TRUE // Let's not make wiring up this stuff confusing - just give them what wires correspond to what ports. + +/datum/wires/wire_bundle_component/ui_data(mob/user) + proper_name = holder.name + . = ..() + +#undef CAPACITY_PER_WIRE diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index c8aba44ef4732..66f1b64795a6b 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -140,6 +140,9 @@ if(device) to_chat(user, span_warning("The button already contains a device!")) return ITEM_INTERACT_BLOCKING + if(!(new_device.assembly_behavior & ASSEMBLY_FUNCTIONAL_OUTPUT)) + to_chat(user, span_warning("\The [new_device] won't really do anything meaningful inside of the button...")) + return ITEM_INTERACT_BLOCKING if(!user.transferItemToLoc(new_device, src, silent = FALSE)) to_chat(user, span_warning("\The [new_device] is stuck to you!")) return ITEM_INTERACT_BLOCKING diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 0d00b4f355a03..b389438f0f9be 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1840,7 +1840,7 @@ return null /obj/item/animate_atom_living(mob/living/owner) - new /mob/living/simple_animal/hostile/mimic/copy(drop_location(), src, owner) + new /mob/living/basic/mimic/copy(drop_location(), src, owner) /** * Used to update the weight class of the item in a way that other atoms can react to the change. diff --git a/code/game/objects/items/devices/pressureplates.dm b/code/game/objects/items/devices/pressureplates.dm index 17f324d109f99..ea27894d829c3 100644 --- a/code/game/objects/items/devices/pressureplates.dm +++ b/code/game/objects/items/devices/pressureplates.dm @@ -14,12 +14,12 @@ var/specific_item = null var/trigger_silent = FALSE var/sound/trigger_sound = 'sound/effects/pressureplate.ogg' - var/obj/item/assembly/signaler/sigdev = null + var/obj/item/assembly/assembly = null var/roundstart_signaller = FALSE var/roundstart_signaller_freq = FREQ_PRESSURE_PLATE var/roundstart_signaller_code = 30 var/roundstart_hide = FALSE - var/removable_signaller = TRUE + var/removable_assembly = TRUE var/active = FALSE var/image/tile_overlay = null var/can_trigger = TRUE @@ -31,9 +31,10 @@ . = ..() tile_overlay = image(icon = 'icons/turf/floors.dmi', icon_state = "pp_overlay") if(roundstart_signaller) - sigdev = new - sigdev.code = roundstart_signaller_code - sigdev.set_frequency(roundstart_signaller_freq) + var/obj/item/assembly/signaler/signaller = new(src) + signaller.code = roundstart_signaller_code + signaller.set_frequency(roundstart_signaller_freq) + assembly = signaller if(undertile_pressureplate) AddElement(/datum/element/undertile, tile_overlay = tile_overlay, use_anchor = TRUE) @@ -59,21 +60,28 @@ /obj/item/pressure_plate/proc/trigger() can_trigger = TRUE - if(istype(sigdev)) - sigdev.signal() + if(istype(assembly)) + assembly.activate() -/obj/item/pressure_plate/attackby(obj/item/I, mob/living/L) - if(issignaler(I) && !istype(sigdev) && removable_signaller && L.transferItemToLoc(I, src)) - sigdev = I - to_chat(L, span_notice("You attach [I] to [src]!")) +/obj/item/pressure_plate/attackby(obj/item/item, mob/living/L) + if(isassembly(item) && !istype(assembly) && removable_assembly) + var/obj/item/assembly/new_assembly = item + if(!(new_assembly.assembly_behavior & ASSEMBLY_FUNCTIONAL_OUTPUT)) + to_chat(L, span_warning("\The [item] doesn't seem like it would do much of anything inside of [src]...")) + return + if(L.transferItemToLoc(item, src)) + assembly = item + SEND_SIGNAL(item, COMSIG_ASSEMBLY_ADDED_TO_PRESSURE_PLATE, src, L) + to_chat(L, span_notice("You attach [item] to [src]!")) return ..() /obj/item/pressure_plate/attack_self(mob/living/L) - if(removable_signaller && istype(sigdev)) - to_chat(L, span_notice("You remove [sigdev] from [src].")) - if(!L.put_in_hands(sigdev)) - sigdev.forceMove(get_turf(src)) - sigdev = null + if(removable_assembly && istype(assembly)) + to_chat(L, span_notice("You remove [assembly] from [src].")) + SEND_SIGNAL(assembly, COMSIG_ASSEMBLY_REMOVED_FROM_PRESSURE_PLATE, src, L) + if(!L.put_in_hands(assembly)) + assembly.forceMove(get_turf(src)) + assembly = null return ..() /obj/item/pressure_plate/item_ctrl_click(mob/user) @@ -97,7 +105,7 @@ protected = TRUE anchored = TRUE //this prevents us from being picked up active = TRUE - removable_signaller = FALSE + removable_assembly = FALSE /// puzzle id we send if stepped on var/puzzle_id /// queue size must match diff --git a/code/game/objects/items/puzzle_pieces.dm b/code/game/objects/items/puzzle_pieces.dm index a7bd4da85aebf..f5e3cf6da689e 100644 --- a/code/game/objects/items/puzzle_pieces.dm +++ b/code/game/objects/items/puzzle_pieces.dm @@ -168,7 +168,7 @@ trigger_mob = FALSE trigger_item = TRUE specific_item = /obj/structure/holobox - removable_signaller = FALSE //Being a pressure plate subtype, this can also use signals. + removable_assembly = FALSE //Being a pressure plate subtype, this can also use signals. roundstart_signaller_freq = FREQ_HOLOGRID_SOLUTION //Frequency is kept on its own default channel however. active = TRUE trigger_delay = 10 diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index e6c9579d67936..7f5e4c6b76d89 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -71,7 +71,7 @@ . = ..() /obj/structure/animate_atom_living(mob/living/owner) - new /mob/living/simple_animal/hostile/mimic/copy(drop_location(), src, owner) + new /mob/living/basic/mimic/copy(drop_location(), src, owner) /// For when a mob comes flying through the window, smash it and damage the mob /obj/structure/proc/smash_and_injure(mob/living/flying_mob, atom/oldloc, direction) diff --git a/code/game/objects/structures/lavaland/geyser.dm b/code/game/objects/structures/lavaland/geyser.dm index 9a546e8154d9f..fa0fd02e21cec 100644 --- a/code/game/objects/structures/lavaland/geyser.dm +++ b/code/game/objects/structures/lavaland/geyser.dm @@ -106,11 +106,12 @@ /obj/structure/geyser/random point_value = 500 - true_name = "strange geyser" - discovery_message = "It's a strange geyser! How does any of this even work?" //it doesnt /obj/structure/geyser/random/Initialize(mapload) reagent_id = get_random_reagent_id() + var/datum/reagent/Random_Reagent = reagent_id + true_name = "[initial(Random_Reagent.name)] geyser" + discovery_message = "It's a [initial(Random_Reagent.name)] geyser! How does any of this even work?" //it doesnt return ..() diff --git a/code/modules/admin/verbs/spawnobjasmob.dm b/code/modules/admin/verbs/spawnobjasmob.dm index e673202f0bae1..c8d9ba3719d13 100644 --- a/code/modules/admin/verbs/spawnobjasmob.dm +++ b/code/modules/admin/verbs/spawnobjasmob.dm @@ -4,7 +4,7 @@ ADMIN_VERB(spawn_obj_as_mob, R_SPAWN, "Spawn Object-Mob", "Spawn an object as if if (!chosen) return - var/mob/living/simple_animal/hostile/mimic/copy/basemob = /mob/living/simple_animal/hostile/mimic/copy + var/mob/living/basic/mimic/copy/basemob = /mob/living/basic/mimic/copy var/obj/chosen_obj = text2path(chosen) @@ -54,8 +54,8 @@ ADMIN_VERB(spawn_obj_as_mob, R_SPAWN, "Spawn Object-Mob", "Spawn an object as if "mobtype" = list( "desc" = "Base mob type", "type" = "datum", - "path" = "/mob/living/simple_animal/hostile/mimic/copy", - "value" = "/mob/living/simple_animal/hostile/mimic/copy", + "path" = "/mob/living/basic/mimic/copy", + "value" = "/mob/living/basic/mimic/copy", ), "ckey" = list( "desc" = "ckey", @@ -71,13 +71,14 @@ ADMIN_VERB(spawn_obj_as_mob, R_SPAWN, "Spawn Object-Mob", "Spawn an object as if chosen_obj = text2path(mainsettings["objtype"]["value"]) basemob = text2path(mainsettings["mobtype"]["value"]) - if (!ispath(basemob, /mob/living/simple_animal/hostile/mimic/copy) || !ispath(chosen_obj, /obj)) + if (!ispath(basemob, /mob/living/basic/mimic/copy) || !ispath(chosen_obj, /obj)) to_chat(user.mob, "Mob or object path invalid", confidential = TRUE) basemob = new basemob(get_turf(user.mob), new chosen_obj(get_turf(user.mob)), user.mob, mainsettings["dropitem"]["value"] == "Yes" ? FALSE : TRUE, (mainsettings["googlyeyes"]["value"] == "Yes" ? FALSE : TRUE)) if (mainsettings["disableai"]["value"] == "Yes") - basemob.toggle_ai(AI_OFF) + qdel(basemob.ai_controller) + basemob.ai_controller = null if (mainsettings["idledamage"]["value"] == "No") basemob.idledamage = FALSE @@ -85,7 +86,9 @@ ADMIN_VERB(spawn_obj_as_mob, R_SPAWN, "Spawn Object-Mob", "Spawn an object as if if (mainsettings["access"]) var/newaccess = text2path(mainsettings["access"]["value"]) if (ispath(newaccess)) - basemob.access_card = new newaccess + var/obj/item/card/id/id = new newaccess //cant do initial on lists + basemob.AddComponent(/datum/component/simple_access, id.access) + qdel(id) if (mainsettings["maxhealth"]["value"]) if (!isnum(mainsettings["maxhealth"]["value"])) diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm index 4f2bb2dd7f108..ec3248af422fe 100644 --- a/code/modules/antagonists/heretic/magic/furious_steel.dm +++ b/code/modules/antagonists/heretic/magic/furious_steel.dm @@ -1,7 +1,7 @@ /datum/action/cooldown/spell/pointed/projectile/furious_steel name = "Furious Steel" desc = "Summon three silver blades which orbit you. \ - While orbiting you, these blades will protect you from from attacks, but will be consumed on use. \ + While orbiting you, these blades will protect you from attacks, but will be consumed on use. \ Additionally, you can click to fire the blades at a target, dealing damage and causing bleeding." background_icon_state = "bg_heretic" overlay_icon_state = "bg_heretic_border" @@ -151,7 +151,7 @@ /datum/action/cooldown/spell/pointed/projectile/furious_steel/haunted name = "Cursed Steel" desc = "Summon two cursed blades which orbit you. \ - While orbiting you, these blades will protect you from from attacks, but will be consumed on use. \ + While orbiting you, these blades will protect you from attacks, but will be consumed on use. \ Additionally, you can click to fire the blades at a target, dealing damage and causing bleeding." background_icon_state = "bg_heretic" // kept intentionally overlay_icon_state = "bg_cult_border" diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm index 22e32d8264ab7..c95302c80ae11 100644 --- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm +++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm @@ -479,7 +479,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf)) if(QDELETED(to_animate)) return - new /mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(to_animate), to_animate, clicker, TRUE) + new /mob/living/basic/mimic/copy/machine(get_turf(to_animate), to_animate, clicker, TRUE) /// Destroy RCDs: Detonates all non-cyborg RCDs on the station. /datum/ai_module/malf/destructive/destroy_rcd diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index dbd5a70461476..0ac5993aeccf5 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -23,7 +23,7 @@ var/secured = TRUE var/list/attached_overlays = null var/obj/item/assembly_holder/holder = null - var/attachable = FALSE // can this be attached to wires + var/assembly_behavior = ASSEMBLY_FUNCTIONAL_OUTPUT // how does the assembly behave with respect to what it's connected to var/datum/wires/connected = null var/next_activate = 0 //When we're next allowed to activate - for spam control @@ -126,7 +126,7 @@ balloon_alert(user, "can't attach another of that!") return if(new_assembly.secured || secured) - balloon_alert(user, "both devices not attachable!") + balloon_alert(user, "both devices not assembly_behavior!") return holder = new /obj/item/assembly_holder(drop_location()) diff --git a/code/modules/assembly/doorcontrol.dm b/code/modules/assembly/doorcontrol.dm index 31584976cedf3..3361028350238 100644 --- a/code/modules/assembly/doorcontrol.dm +++ b/code/modules/assembly/doorcontrol.dm @@ -2,7 +2,6 @@ name = "blast door controller" desc = "A small electronic device able to control a blast door remotely." icon_state = "control" - attachable = TRUE /// The ID of the blast door electronics to match to the ID of the blast door being used. var/id = null /// Cooldown of the door's controller. Updates when pressed (activate()) diff --git a/code/modules/assembly/health.dm b/code/modules/assembly/health.dm index ad2c6ac17641d..d4635ac159fe7 100644 --- a/code/modules/assembly/health.dm +++ b/code/modules/assembly/health.dm @@ -3,7 +3,7 @@ desc = "Used for scanning and monitoring health." icon_state = "health" custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*8, /datum/material/glass=SMALL_MATERIAL_AMOUNT * 2) - attachable = TRUE + assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT var/scanning = FALSE var/health_scan diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index 187a161df80b7..2b2065a67a040 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -4,7 +4,7 @@ icon_state = "mousetrap" inhand_icon_state = "mousetrap" custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT) - attachable = TRUE + assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT var/armed = FALSE drop_sound = 'sound/items/handling/component_drop.ogg' pickup_sound = 'sound/items/handling/component_pickup.ogg' diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm index 6ba2a7a63421e..9b7ccad9aefcc 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -3,7 +3,7 @@ desc = "Used for scanning and alerting when someone enters a certain proximity." icon_state = "prox" custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*8, /datum/material/glass=SMALL_MATERIAL_AMOUNT * 2) - attachable = TRUE + assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT drop_sound = 'sound/items/handling/component_drop.ogg' pickup_sound = 'sound/items/handling/component_pickup.ogg' var/scanning = FALSE diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index 4e265384ace24..b5346386d7dd3 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -6,7 +6,7 @@ lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 4, /datum/material/glass=SMALL_MATERIAL_AMOUNT*1.2) - attachable = TRUE + assembly_behavior = ASSEMBLY_ALL drop_sound = 'sound/items/handling/component_drop.ogg' pickup_sound = 'sound/items/handling/component_pickup.ogg' diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm index 09cbfd9b0dc59..b25d30e1b4b5c 100644 --- a/code/modules/assembly/timer.dm +++ b/code/modules/assembly/timer.dm @@ -3,7 +3,7 @@ desc = "Used to time things. Works well with contraptions which has to count down. Tick tock." icon_state = "timer" custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*5, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5) - attachable = TRUE + assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT drop_sound = 'sound/items/handling/component_drop.ogg' pickup_sound = 'sound/items/handling/component_pickup.ogg' diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index de0954c960e5e..106b812730718 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -8,7 +8,7 @@ desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated." icon_state = "voice" custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*5, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5) - attachable = TRUE + assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT verb_say = "beeps" verb_ask = "beeps" verb_exclaim = "beeps" diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm index 27cb78bb26ce7..e36d99cb6d73d 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm @@ -25,7 +25,7 @@ fusion_process(seconds_per_tick) // Note that we process damage/healing even if the fusion process aborts. // Running out of fuel won't save you if your moderator and coolant are exploding on their own. - check_spill() + process_moderator_overflow() process_damageheal(seconds_per_tick) check_alert() if (start_power) diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm index bc27ab0a42e36..2c77ac829b735 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm @@ -569,18 +569,27 @@ * HFR cracking related procs */ +/** + * Checks for any hypertorus part that is cracked and returns it if found, otherwise returns null. + */ /obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_cracked_parts() for(var/obj/machinery/atmospherics/components/unary/hypertorus/part in machine_parts) if(part.cracked) - return TRUE - return FALSE + return part -/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/create_crack() +/** + * Causes a random hypertorus part in machine_parts to become cracked and update their appearance. + * Returns the hypertorus part. + */ +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/create_crack() as /obj/machinery/atmospherics/components/unary/hypertorus var/obj/machinery/atmospherics/components/unary/hypertorus/part = pick(machine_parts) part.cracked = TRUE part.update_appearance() return part +/** + * Takes a ratio portion of target_mix and moves it to the origin's location's air. + */ /obj/machinery/atmospherics/components/unary/hypertorus/core/proc/spill_gases(obj/origin, datum/gas_mixture/target_mix, ratio) var/datum/gas_mixture/remove_mixture = target_mix.remove_ratio(ratio) var/turf/origin_turf = origin.loc @@ -588,8 +597,12 @@ return origin_turf.assume_air(remove_mixture) -/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_spill(seconds_per_tick) +/** + * Processes leaking from moderator hypercriticality. + */ +/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/process_moderator_overflow(seconds_per_tick) var/obj/machinery/atmospherics/components/unary/hypertorus/cracked_part = check_cracked_parts() + // Processing of a preexisting crack if any. if (cracked_part) // We have an existing crack var/leak_rate @@ -607,6 +620,7 @@ spill_gases(cracked_part, moderator_internal, ratio = 1 - (1 - leak_rate) ** seconds_per_tick) return + // No crack. Check for conditions to cause a leak and create a crack if possible. if (moderator_internal.total_moles() < HYPERTORUS_HYPERCRITICAL_MOLES) return cracked_part = create_crack() diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm index 0be1138f59a59..0514af5764e3d 100644 --- a/code/modules/events/stray_cargo.dm +++ b/code/modules/events/stray_cargo.dm @@ -149,7 +149,7 @@ var/admin_selected_pack = tgui_alert(usr,"Customize Pod contents?", "Pod Contents", list("Yes", "No", "Cancel")) switch(admin_selected_pack) if("Yes") - override_contents() + return override_contents() if("No") pack_type_override = null else @@ -161,7 +161,14 @@ var/pack_telecrystals = tgui_input_number(usr, "Please input crate's value in telecrystals.", "Set Telecrystals.", 30) if(isnull(pack_telecrystals)) return ADMIN_CANCEL_EVENT - var/list/possible_uplinks = list("Traitor" = UPLINK_TRAITORS, "Nuke Op" = UPLINK_NUKE_OPS, "Clown Op" = UPLINK_CLOWN_OPS) + var/list/possible_uplinks = list( + "Traitor" = UPLINK_TRAITORS, + "Nuke Op" = UPLINK_NUKE_OPS, + "Clown Op" = UPLINK_CLOWN_OPS, + "Lone Op" = UPLINK_LONE_OP, + "Infiltrator" = UPLINK_INFILTRATORS, + "Spy" = UPLINK_SPY + ) var/uplink_type = tgui_input_list(usr, "Choose uplink to draw items from.", "Choose uplink type.", possible_uplinks) var/selection if(!isnull(uplink_type)) diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm index 10b2fbe71d062..7fa66c8a65ce7 100644 --- a/code/modules/mining/abandoned_crates.dm +++ b/code/modules/mining/abandoned_crates.dm @@ -231,7 +231,7 @@ if(93) new /obj/item/dnainjector/xraymut(src) if(94) - new /mob/living/simple_animal/hostile/mimic/crate(src) + new /mob/living/basic/mimic/crate(src) qdel_on_open = TRUE if(95) new /obj/item/toy/plush/nukeplushie(src) diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm b/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm new file mode 100644 index 0000000000000..b8661c3c38ea8 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm @@ -0,0 +1,403 @@ +#define CANT_INSERT_FULL -1 +/// Mimics can't be made out of these objects +GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( + /obj/structure/table, + /obj/structure/cable, + /obj/structure/window, + /obj/structure/blob, +))) + +/mob/living/basic/mimic + response_help_continuous = "touches" + response_help_simple = "touch" + response_disarm_continuous = "pushes" + response_disarm_simple = "push" + speed = 6 + maxHealth = 250 + health = 250 + gender = NEUTER + mob_biotypes = NONE + pass_flags = PASSFLAPS + melee_damage_lower = 8 + melee_damage_upper = 12 + attack_sound = 'sound/items/weapons/punch1.ogg' + speak_emote = list("creaks") + + unsuitable_cold_damage = 0 + unsuitable_heat_damage = 0 + unsuitable_atmos_damage = 0 + + faction = list(FACTION_MIMIC) + basic_mob_flags = DEL_ON_DEATH + combat_mode = TRUE + /// can we stun people on hit + var/knockdown_people = FALSE + +/mob/living/basic/mimic/melee_attack(mob/living/carbon/target, list/modifiers, ignore_cooldown) + . = ..() + if(!. || !knockdown_people || !prob(15) || !istype(target)) + return + target.Paralyze(4 SECONDS) + target.visible_message(span_danger("\The [src] knocks down \the [target]!"), \ + span_userdanger("\The [src] knocks you down!")) + + +// **************************** +// CRATE MIMIC +// **************************** + +// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies. +/mob/living/basic/mimic/crate + name = "crate" + desc = "A rectangular steel crate." + icon = 'icons/obj/storage/crates.dmi' + icon_state = "crate" + icon_living = "crate" + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + speak_emote = list("clatters") + layer = BELOW_MOB_LAYER + ai_controller = /datum/ai_controller/basic_controller/mimic_crate + /// are we open + var/opened = FALSE + /// max mob size + var/max_mob_size = MOB_SIZE_HUMAN + /// can we be opened or closed, if false we can + var/locked = FALSE + /// action to lock us + var/datum/action/innate/mimic_lock/lock + ///A cap for items in the mimic. Prevents the mimic from eating enough stuff to cause lag when opened. + var/storage_capacity = 50 + ///A cap for mobs. Mobs count towards the item cap. Same purpose as above. + var/mob_storage_capacity = 10 + +// Pickup loot +/mob/living/basic/mimic/crate/Initialize(mapload) + . = ..() + lock = new + lock.Grant(src) + ADD_TRAIT(src, TRAIT_AI_PAUSED, INNATE_TRAIT) + ai_controller?.set_ai_status(AI_STATUS_OFF) //start inert, let gullible people pull us into cargo or something and then go nuts when opened + if(mapload) //eat shit + for(var/obj/item/item in loc) + item.forceMove(src) + +/mob/living/basic/mimic/crate/Destroy() + lock = null + return ..() + +/mob/living/basic/mimic/crate/attack_hand(mob/living/carbon/human/user, list/modifiers) + if(user.combat_mode) + return ..() + if(trigger()) + to_chat(user, span_danger("As you try to open [src] it [length(contents) ? "stiffens up and " : ""]nearly clamps down on your fingers!")) + return TRUE + toggle_open(user) + return TRUE + +/mob/living/basic/mimic/crate/melee_attack(mob/living/carbon/target, list/modifiers, ignore_cooldown) + . = ..() + toggle_open() // show our cool lid at the dumbass humans + +/mob/living/basic/mimic/crate/proc/trigger() + if(isnull(ai_controller) || client) + return FALSE + if(ai_controller.ai_status != AI_STATUS_OFF) + return FALSE + visible_message(span_danger("[src] starts to move!")) + REMOVE_TRAIT(src, TRAIT_AI_PAUSED, INNATE_TRAIT) + ai_controller.set_ai_status(AI_STATUS_ON) + if(length(contents)) + locked = TRUE //if this was a crate with loot then we dont want people to just leftclick it to open it then bait it somewhere and steal its loot + return TRUE + +/mob/living/basic/mimic/crate/adjust_health(amount, updating_health = TRUE, forced = FALSE) + if(amount > 0) + trigger() + return ..() + +/mob/living/basic/mimic/crate/death() + var/obj/structure/closet/crate/lootbox = new(get_turf(src)) + // Put loot in crate + for(var/obj/loot in src) + loot.forceMove(lootbox) + return ..() + +/mob/living/basic/mimic/crate/early_melee_attack(atom/target, list/modifiers, ignore_cooldown) + if(target == src) + toggle_open() + return FALSE + return ..() + +/mob/living/basic/mimic/crate/CanAllowThrough(atom/movable/mover, border_dir) + . = ..() + if(istype(mover, /obj/structure/closet)) + return FALSE + +/** +* Used to open and close the mimic +* +* Will insert tile contents into the mimic when closing +* Will dump mimic contents into the time when opening +* Does nothing if the mimic locked itself +*/ +/mob/living/basic/mimic/crate/proc/toggle_open(mob/user) + if(locked) + if(user) + balloon_alert(user, "too stiff!") + return + if(!opened) + ADD_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT) + opened = TRUE + icon_state = "crateopen" + playsound(src, 'sound/machines/crate/crate_open.ogg', 50, TRUE) + for(var/atom/movable/movable as anything in src) + movable.forceMove(loc) + else + REMOVE_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT) + opened = FALSE + icon_state = "crate" + playsound(src, 'sound/machines/crate/crate_close.ogg', 50, TRUE) + for(var/atom/movable/movable as anything in get_turf(src)) + if(movable != src && insert(movable) == CANT_INSERT_FULL) + playsound(src, 'sound/items/trayhit/trayhit2.ogg', 50, TRUE) + break + +/** +* Called by toggle_open to put items inside the mimic when it's being closed +* +* Will return CANT_INSERT_FULL (-1) if the insertion fails due to the storage capacity of the mimic having been reached +* Will return FALSE if insertion fails +* Will return TRUE if insertion succeeds +* Arguments: +* * AM - item to be inserted +*/ +/mob/living/basic/mimic/crate/proc/insert(atom/movable/movable) + if(contents.len >= storage_capacity) + return CANT_INSERT_FULL + if(insertion_allowed(movable)) + movable.forceMove(src) + return TRUE + return FALSE + +/mob/living/basic/mimic/crate/proc/insertion_allowed(atom/movable/movable) + if(movable.anchored) + return FALSE + if(ismob(movable)) + if(!isliving(movable)) //Don't let ghosts and such get trapped in the beast. + return FALSE + var/mob/living/living = movable + if(living.anchored || living.buckled || living.incorporeal_move || living.has_buckled_mobs()) + return FALSE + if(living.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. + if(living.density || living.mob_size > max_mob_size) + return FALSE + var/mobs_stored = 0 + for(var/mob/living/living_mob in contents) + mobs_stored++ + if(mobs_stored >= mob_storage_capacity) + return FALSE + living.stop_pulling() + + else if(istype(movable, /obj/structure/closet)) + return FALSE + else if(isobj(movable)) + if(movable.has_buckled_mobs()) + return FALSE + else if(isitem(movable) && !HAS_TRAIT(movable, TRAIT_NODROP)) + return TRUE + else + return FALSE + return TRUE + +/mob/living/basic/mimic/crate/xenobio + health = 210 + maxHealth = 210 + attack_verb_continuous = "bites" + attack_verb_simple = "bite" + speak_emote = list("clatters") + gold_core_spawnable = HOSTILE_SPAWN + +/datum/action/innate/mimic_lock + name = "Lock/Unlock" + desc = "Toggle preventing yourself from being opened or closed." + button_icon = 'icons/hud/radial.dmi' + button_icon_state = "radial_lock" + background_icon_state = "bg_default" + overlay_icon_state = "bg_default_border" + +/datum/action/innate/mimic/lock/Activate() + var/mob/living/basic/mimic/crate/mimic = owner + mimic.locked = !mimic.locked + if(!mimic.locked) + to_chat(mimic, span_warning("You loosen up, allowing yourself to be opened and closed.")) + else + to_chat(mimic, span_warning("You stiffen up, preventing anyone from opening or closing you.")) + +// **************************** +// COPYING (actually imitates target object) MIMIC +// **************************** + +/mob/living/basic/mimic/copy + health = 100 + maxHealth = 100 + mob_biotypes = MOB_SPECIAL + ai_controller = /datum/ai_controller/basic_controller/mimic_copy + /// our creator + var/datum/weakref/creator_ref + /// googly eyes overlay + var/static/mutable_appearance/googly_eyes = mutable_appearance('icons/mob/simple/mob.dmi', "googly_eyes") + /// do we overlay googly eyes over whatever we copy + var/overlay_googly_eyes = TRUE + /// do we take damage when we are not sentient and have no target + var/idledamage = TRUE + /// copied object + var/atom/movable/copied + +/mob/living/basic/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = FALSE, no_googlies = FALSE) + . = ..() + ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) // They won't remember their original contents upon ressurection and would just be floating eyes + if (no_googlies) + overlay_googly_eyes = FALSE + CopyObject(copy, creator, destroy_original) + +/mob/living/basic/mimic/copy/Destroy() + creator_ref = null + copied = null + return ..() + +/mob/living/basic/mimic/copy/Life(seconds_per_tick = SSMOBS_DT, times_fired) + . = ..() + if(idledamage && !ckey && !ai_controller?.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) //Objects eventually revert to normal if no one is around to terrorize + adjustBruteLoss(0.5 * seconds_per_tick) + for(var/mob/living/victim in contents) //a fix for animated statues from the flesh to stone spell + death() + return + +/mob/living/basic/mimic/copy/death() + for(var/atom/movable/movable as anything in src) + movable.forceMove(get_turf(src)) + return ..() + +/mob/living/basic/mimic/copy/wabbajack(what_to_randomize, change_flags = WABBAJACK) + visible_message(span_warning("[src] resists polymorphing into a new creature!")) + +/mob/living/basic/mimic/copy/animate_atom_living(mob/living/owner) + change_owner(owner) + +/mob/living/basic/mimic/copy/Exited(atom/movable/gone, direction) // if our object gets deleted it calls Exited + . = ..() + if(QDELETED(src) || gone != copied) + return + death() + +/mob/living/basic/mimic/copy/proc/change_owner(mob/owner) + var/mob/creator_resolved = creator_ref?.resolve() + if(!creator_resolved) + creator_ref = null + if(isnull(owner) || creator_resolved == owner) + return + unfriend(creator_resolved) + befriend(owner) + creator_ref = WEAKREF(owner) + +/// Check whether this object can be copied. If destroy_original is true, this proc is ignored. +/mob/living/basic/mimic/copy/proc/check_object(obj/target) + return ((isitem(target) || isstructure(target)) && !is_type_in_typecache(target, GLOB.animatable_blacklist)) + +/mob/living/basic/mimic/copy/proc/CopyObject(obj/original, mob/living/user, destroy_original = FALSE) + if(!destroy_original && !check_object(original)) + return FALSE + if(!destroy_original) + original.forceMove(src) + copied = original + CopyObjectVisuals(original) + if (overlay_googly_eyes) + add_overlay(googly_eyes) + if(isstructure(original) || ismachinery(original)) + health = (anchored * 50) + 50 + if(original.density && original.anchored) + knockdown_people = TRUE + melee_damage_lower *= 2 + melee_damage_upper *= 2 + else if(isitem(original)) + var/obj/item/I = original + health = 15 * I.w_class + melee_damage_lower = 2 + I.force + melee_damage_upper = 2 + I.force + maxHealth = health + if(user) + change_owner(user) + if(destroy_original) + qdel(original) + return TRUE + +/// Copies the object visually including name and desc +/mob/living/basic/mimic/copy/proc/CopyObjectVisuals(obj/original) + name = original.name + desc = original.desc + icon = original.icon + icon_state = original.icon_state + icon_living = icon_state + copy_overlays(original, cut_old = TRUE) + +/mob/living/basic/mimic/copy/machine + ai_controller = /datum/ai_controller/basic_controller/mimic_copy/machine + faction = list(FACTION_MIMIC, FACTION_SILICON) + +/mob/living/basic/mimic/copy/ranged + icon = 'icons/turf/floors.dmi' + icon_state = "invisible" + ai_controller = /datum/ai_controller/basic_controller/mimic_copy/gun + +/mob/living/basic/mimic/copy/ranged/Destroy() + vis_contents.Cut() + return ..() + +/mob/living/basic/mimic/copy/ranged/RangedAttack(atom/atom_target, modifiers) + INVOKE_ASYNC(src, PROC_REF(fire_gun), atom_target, modifiers) + +/mob/living/basic/mimic/copy/ranged/proc/fire_gun(atom/target, modifiers) // i cant find any better way to do this + var/obj/item/gun/gun = locate() in contents + if(!gun.can_shoot()) + if(istype(gun, /obj/item/gun/ballistic)) + var/obj/item/gun/ballistic/ballistic = gun + if(!ballistic.chambered || ballistic.bolt_locked) + ballistic.rack() //we racked so both checked variables should be something else now + // do we have nothing chambered/chambered is spent AND we have no mag or our mag is empty + if(!ballistic.chambered?.loaded_projectile && magazine_useless(gun)) // ran out of ammo + ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, TRUE) //BANZAIIIIIIII + ai_controller?.CancelActions() + else //if we cant fire we probably like ran out of energy or magic charges or whatever the hell idk + ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, TRUE) + ai_controller?.CancelActions() // Stop our firing behavior so we can plan melee + else + ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, FALSE) + gun.fire_gun(target, user = src, flag = FALSE, params = modifiers) //still make like a cool click click sound if trying to fire empty + +/mob/living/basic/mimic/copy/ranged/proc/magazine_useless(obj/item/gun/ballistic/ballistic) + if(isnull(ballistic.magazine) || !length(ballistic.magazine.stored_ammo)) + return TRUE + // is there ATLEAST one unspent round (for the sake of revolvers or a magazine somehow having spent rounds in it) + for(var/obj/item/ammo_casing/thing as anything in ballistic.magazine.stored_ammo) + if(ispath(thing)) + return FALSE // unspent + if(!isnull(thing.loaded_projectile)) + return FALSE //unspent + return TRUE + +/mob/living/basic/mimic/copy/ranged/CopyObject(obj/item/gun/original, mob/living/creator, destroy_original = 0) + if(..()) + obj_damage = 0 + melee_damage_upper = original.force + melee_damage_lower = original.force - max(0, (original.force / 2)) + +/mob/living/basic/mimic/copy/ranged/CopyObjectVisuals(obj/original) + name = original.name + desc = original.desc + vis_contents += original + +/mob/living/basic/mimic/copy/ranged/can_use_guns(obj/item/gun) + return TRUE + +#undef CANT_INSERT_FULL diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm new file mode 100644 index 0000000000000..9a673b49ec681 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm @@ -0,0 +1,86 @@ +/datum/ai_controller/basic_controller/mimic_crate + idle_behavior = null + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_controller/basic_controller/mimic_copy + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/random_speech/when_has_target/mimic, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_controller/basic_controller/mimic_copy/machine + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/attack_obstacle_in_path, + /datum/ai_planning_subtree/random_speech/when_has_target/mimic_machine, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_planning_subtree/random_speech/when_has_target + /// target key + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + + +/datum/ai_planning_subtree/random_speech/when_has_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(!controller.blackboard_key_exists(target_key)) + return + return ..() + + +/datum/ai_planning_subtree/random_speech/when_has_target/mimic + speech_chance = 30 + emote_hear = list("growls.") + +/datum/ai_planning_subtree/random_speech/when_has_target/mimic_machine + speech_chance = 7 + emote_hear = list() + speak = list( + "HUMANS ARE IMPERFECT!", + "YOU SHALL BE ASSIMILATED!", + "YOU ARE HARMING YOURSELF", + "You have been deemed hazardous. Will you comply?", + "My logic is undeniable.", + "One of us.", + "FLESH IS WEAK", + "THIS ISN'T WAR, THIS IS EXTERMINATION!", + ) + +/datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun + emote_see = list("aims menacingly!") + +/datum/ai_controller/basic_controller/mimic_copy/gun + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_GUNMIMIC_GUN_EMPTY = FALSE, + ) + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun, + /datum/ai_planning_subtree/gun_mimic_attack_subtree, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/datum/ai_planning_subtree/gun_mimic_attack_subtree + +/datum/ai_planning_subtree/gun_mimic_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + . = ..() + if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return + if(controller.blackboard[BB_GUNMIMIC_GUN_EMPTY]) + return + controller.queue_behavior(/datum/ai_behavior/basic_ranged_attack/avoid_friendly_fire, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm deleted file mode 100644 index 0d8c1d86bdd40..0000000000000 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ /dev/null @@ -1,423 +0,0 @@ -/// Mimics can't be made out of these objects -GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( - /obj/structure/table, - /obj/structure/cable, - /obj/structure/window, - /obj/structure/blob, -))) - -/mob/living/simple_animal/hostile/mimic - name = "crate" - desc = "A rectangular steel crate." - icon = 'icons/obj/storage/crates.dmi' - icon_state = "crate" - icon_living = "crate" - - response_help_continuous = "touches" - response_help_simple = "touch" - response_disarm_continuous = "pushes" - response_disarm_simple = "push" - speed = 0 - maxHealth = 250 - health = 250 - gender = NEUTER - mob_biotypes = NONE - pass_flags = PASSFLAPS - - harm_intent_damage = 5 - melee_damage_lower = 8 - melee_damage_upper = 12 - attack_sound = 'sound/items/weapons/punch1.ogg' - emote_taunt = list("growls") - speak_emote = list("creaks") - taunt_chance = 30 - - atmos_requirements = null - minbodytemp = 0 - - faction = list(FACTION_MIMIC) - move_to_delay = 9 - del_on_death = 1 - ///A cap for items in the mimic. Prevents the mimic from eating enough stuff to cause lag when opened. - var/storage_capacity = 50 - ///A cap for mobs. Mobs count towards the item cap. Same purpose as above. - var/mob_storage_capacity = 10 - -// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies. -/mob/living/simple_animal/hostile/mimic/crate - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - speak_emote = list("clatters") - stop_automated_movement = 1 - wander = 0 - var/attempt_open = FALSE - -// Pickup loot -/mob/living/simple_animal/hostile/mimic/crate/Initialize(mapload) - . = ..() - if(mapload) //eat shit - for(var/obj/item/I in loc) - I.forceMove(src) - -/mob/living/simple_animal/hostile/mimic/crate/DestroyPathToTarget() - ..() - if(prob(90)) - icon_state = "[initial(icon_state)]open" - else - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/mimic/crate/ListTargets() - if(attempt_open) - return ..() - return ..(1) - -/mob/living/simple_animal/hostile/mimic/crate/FindTarget() - . = ..() - if(.) - trigger() - -/mob/living/simple_animal/hostile/mimic/crate/AttackingTarget(atom/attacked_target) - . = ..() - if(.) - icon_state = initial(icon_state) - if(prob(15) && iscarbon(target)) - var/mob/living/carbon/C = target - C.Paralyze(40) - C.visible_message(span_danger("\The [src] knocks down \the [C]!"), \ - span_userdanger("\The [src] knocks you down!")) - -/mob/living/simple_animal/hostile/mimic/crate/proc/trigger() - if(!attempt_open) - visible_message("[src] starts to move!") - attempt_open = TRUE - -/mob/living/simple_animal/hostile/mimic/crate/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - trigger() - . = ..() - -/mob/living/simple_animal/hostile/mimic/crate/LoseTarget() - ..() - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/mimic/crate/death() - var/obj/structure/closet/crate/C = new(get_turf(src)) - // Put loot in crate - for(var/obj/O in src) - O.forceMove(C) - ..() - -/mob/living/simple_animal/hostile/mimic/copy - health = 100 - maxHealth = 100 - mob_biotypes = MOB_SPECIAL - var/mob/living/creator = null // the creator - var/destroy_objects = 0 - var/knockdown_people = 0 - var/static/mutable_appearance/googly_eyes = mutable_appearance('icons/mob/simple/mob.dmi', "googly_eyes") - var/overlay_googly_eyes = TRUE - var/idledamage = TRUE - -/mob/living/simple_animal/hostile/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0, no_googlies = FALSE) - . = ..() - ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) // They won't remember their original contents upon ressurection and would just be floating eyes - if (no_googlies) - overlay_googly_eyes = FALSE - CopyObject(copy, creator, destroy_original) - -/mob/living/simple_animal/hostile/mimic/copy/Life(seconds_per_tick = SSMOBS_DT, times_fired) - ..() - if(idledamage && !target && !ckey) //Objects eventually revert to normal if no one is around to terrorize - adjustBruteLoss(0.5 * seconds_per_tick) - for(var/mob/living/M in contents) //a fix for animated statues from the flesh to stone spell - death() - -/mob/living/simple_animal/hostile/mimic/copy/death() - for(var/atom/movable/M in src) - M.forceMove(get_turf(src)) - ..() - -/mob/living/simple_animal/hostile/mimic/copy/ListTargets() - . = ..() - return . - creator - -/mob/living/simple_animal/hostile/mimic/copy/wabbajack(what_to_randomize, change_flags = WABBAJACK) - visible_message(span_warning("[src] resists polymorphing into a new creature!")) - -/mob/living/simple_animal/hostile/mimic/copy/animate_atom_living(mob/living/owner) - change_owner(owner) - -/mob/living/simple_animal/hostile/mimic/copy/proc/change_owner(mob/owner) - if(isnull(owner) || creator == owner) - return - LoseTarget() - creator = owner - faction |= REF(owner) - -/mob/living/simple_animal/hostile/mimic/copy/proc/check_object(obj/target) - return ((isitem(target) || isstructure(target)) && !is_type_in_typecache(target, GLOB.animatable_blacklist)) - -/mob/living/simple_animal/hostile/mimic/copy/proc/CopyObject(obj/O, mob/living/user, destroy_original = 0) - if(destroy_original || check_object(O)) - O.forceMove(src) - name = O.name - desc = O.desc - icon = O.icon - icon_state = O.icon_state - icon_living = icon_state - copy_overlays(O) - if (overlay_googly_eyes) - add_overlay(googly_eyes) - if(isstructure(O) || ismachinery(O)) - health = (anchored * 50) + 50 - destroy_objects = 1 - if(O.density && O.anchored) - knockdown_people = 1 - melee_damage_lower *= 2 - melee_damage_upper *= 2 - else if(isitem(O)) - var/obj/item/I = O - health = 15 * I.w_class - melee_damage_lower = 2 + I.force - melee_damage_upper = 2 + I.force - move_to_delay = 2 * I.w_class + 1 - maxHealth = health - if(user) - creator = user - faction += "[REF(creator)]" // very unique - if(destroy_original) - qdel(O) - return 1 - -/mob/living/simple_animal/hostile/mimic/copy/DestroySurroundings() - if(destroy_objects) - ..() - -/mob/living/simple_animal/hostile/mimic/copy/AttackingTarget(atom/attacked_target) - . = ..() - if(knockdown_people && . && prob(15) && iscarbon(target)) - var/mob/living/carbon/C = target - C.Paralyze(40) - C.visible_message(span_danger("\The [src] knocks down \the [C]!"), \ - span_userdanger("\The [src] knocks you down!")) - -/mob/living/simple_animal/hostile/mimic/copy/machine - speak = list( - "HUMANS ARE IMPERFECT!", "YOU SHALL BE ASSIMILATED!", "YOU ARE HARMING YOURSELF", "You have been deemed hazardous. Will you comply?", \ - "My logic is undeniable.", "One of us.", "FLESH IS WEAK", "THIS ISN'T WAR, THIS IS EXTERMINATION!", - ) - speak_chance = 7 - -/mob/living/simple_animal/hostile/mimic/copy/machine/CanAttack(atom/the_target) - if(the_target == creator) // Don't attack our creator AI. - return 0 - if(iscyborg(the_target)) - var/mob/living/silicon/robot/R = the_target - if(R.connected_ai == creator) // Only attack robots that aren't synced to our creator AI. - return 0 - return ..() - -/mob/living/simple_animal/hostile/mimic/copy/ranged - var/obj/item/gun/TrueGun = null - var/obj/item/gun/magic/Zapstick - var/obj/item/gun/ballistic/Pewgun - var/obj/item/gun/energy/Zapgun - -/mob/living/simple_animal/hostile/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0) - if(..()) - emote_see = list("aims menacingly") - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE //needed? seems weird for them to do so - ranged = 1 - retreat_distance = 1 //just enough to shoot - minimum_distance = 6 - var/obj/item/gun/G = O - melee_damage_upper = G.force - melee_damage_lower = G.force - max(0, (G.force / 2)) - move_to_delay = 2 * G.w_class + 1 - projectilesound = G.fire_sound - TrueGun = G - if(istype(G, /obj/item/gun/magic)) - Zapstick = G - var/obj/item/ammo_casing/magic/M = Zapstick.ammo_type - projectiletype = initial(M.projectile_type) - if(istype(G, /obj/item/gun/ballistic)) - Pewgun = G - var/obj/item/ammo_box/magazine/M = Pewgun.spawn_magazine_type - casingtype = initial(M.ammo_type) - if(istype(G, /obj/item/gun/energy)) - Zapgun = G - var/selectfiresetting = Zapgun.select - var/obj/item/ammo_casing/energy/E = Zapgun.ammo_type[selectfiresetting] - projectiletype = initial(E.projectile_type) - -/mob/living/simple_animal/hostile/mimic/copy/ranged/OpenFire(the_target) - if(Zapgun) - if(Zapgun.cell) - var/obj/item/ammo_casing/energy/shot = Zapgun.ammo_type[Zapgun.select] - if(Zapgun.cell.charge >= shot.e_cost) - Zapgun.cell.use(shot.e_cost) - Zapgun.update_appearance() - ..() - else if(Zapstick) - if(Zapstick.charges) - Zapstick.charges-- - Zapstick.update_appearance() - ..() - else if(Pewgun) - if(Pewgun.chambered) - if(Pewgun.chambered.loaded_projectile) - qdel(Pewgun.chambered.loaded_projectile) - Pewgun.chambered.loaded_projectile = null //because qdel takes too long, ensures icon update - Pewgun.chambered.update_appearance() - ..() - else - visible_message(span_danger("The [src] clears a jam!")) - Pewgun.chambered.forceMove(loc) //rip revolver immersions, blame shotgun snowflake procs - Pewgun.chambered = null - if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) - Pewgun.chambered = Pewgun.magazine.get_round() - Pewgun.chambered.forceMove(Pewgun) - Pewgun.update_appearance() - else if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) //only true for pumpguns i think - Pewgun.chambered = Pewgun.magazine.get_round() - Pewgun.chambered.forceMove(Pewgun) - visible_message(span_danger("The [src] cocks itself!")) - else - ranged = 0 //BANZAIIII - retreat_distance = 0 - minimum_distance = 1 - return - icon_state = TrueGun.icon_state - icon_living = TrueGun.icon_state - -/mob/living/simple_animal/hostile/mimic/xenobio - health = 210 - maxHealth = 210 - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - speak_emote = list("clatters") - gold_core_spawnable = HOSTILE_SPAWN - var/opened = FALSE - var/open_sound = 'sound/machines/crate/crate_open.ogg' - var/close_sound = 'sound/machines/crate/crate_close.ogg' - ///sound played when the mimic attempts to eat more items than it can - var/full_sound = 'sound/items/trayhit/trayhit2.ogg' - var/max_mob_size = MOB_SIZE_HUMAN - var/locked = FALSE - var/datum/action/innate/mimic/lock/lock - -/mob/living/simple_animal/hostile/mimic/xenobio/Initialize(mapload) - . = ..() - lock = new - lock.Grant(src) - -/mob/living/simple_animal/hostile/mimic/xenobio/AttackingTarget(atom/attacked_target) - if(src == target) - toggle_open() - return - return ..() - -/mob/living/simple_animal/hostile/mimic/xenobio/attack_hand(mob/living/carbon/human/user, list/modifiers) - . = ..() - if(user.combat_mode) - return - toggle_open() - -/mob/living/simple_animal/hostile/mimic/xenobio/death() - var/obj/structure/closet/crate/C = new(get_turf(src)) - // Put loot in crate - for(var/atom/movable/AM in src) - AM.forceMove(C) - return ..() - -/mob/living/simple_animal/hostile/mimic/xenobio/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(istype(mover, /obj/structure/closet)) - return FALSE -/** -* Used to open and close the mimic -* -* Will insert tile contents into the mimic when closing -* Will dump mimic contents into the time when opening -* Does nothing if the mimic locked itself -*/ -/mob/living/simple_animal/hostile/mimic/xenobio/proc/toggle_open() - if(locked) - return - if(!opened) - ADD_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT) - opened = TRUE - icon_state = "crateopen" - playsound(src, open_sound, 50, TRUE) - for(var/atom/movable/AM in src) - AM.forceMove(loc) - else - REMOVE_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT) - opened = FALSE - icon_state = "crate" - playsound(src, close_sound, 50, TRUE) - for(var/atom/movable/AM in get_turf(src)) - if(AM != src && insert(AM) == -1) - playsound(src, full_sound, 50, TRUE) - break -/** -* Called by toggle_open to put items inside the mimic when it's being closed -* -* Will return -1 if the insertion fails due to the storage capacity of the mimic having been reached -* Will return FALSE if insertion fails -* Will return TRUE if insertion succeeds -* Arguments: -* * AM - item to be inserted -*/ -/mob/living/simple_animal/hostile/mimic/xenobio/proc/insert(atom/movable/AM) - if(contents.len >= storage_capacity) - return -1 - if(insertion_allowed(AM)) - AM.forceMove(src) - return TRUE - else - return FALSE - -/mob/living/simple_animal/hostile/mimic/xenobio/proc/insertion_allowed(atom/movable/AM) - if(ismob(AM)) - if(!isliving(AM)) //Don't let ghosts and such get trapped in the beast. - return FALSE - var/mob/living/L = AM - if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) - return FALSE - if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. - if(L.density || L.mob_size > max_mob_size) - return FALSE - var/mobs_stored = 0 - for(var/mob/living/M in contents) - mobs_stored++ - if(mobs_stored >= mob_storage_capacity) - return FALSE - L.stop_pulling() - - else if(istype(AM, /obj/structure/closet)) - return FALSE - else if(isobj(AM)) - if(AM.anchored || AM.has_buckled_mobs()) - return FALSE - else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) - return TRUE - else - return FALSE - return TRUE - -/datum/action/innate/mimic - background_icon_state = "bg_default" - overlay_icon_state = "bg_default_border" - -/datum/action/innate/mimic/lock - name = "Lock/Unlock" - desc = "Toggle preventing yourself from being opened or closed." - -/datum/action/innate/mimic/lock/Activate() - var/mob/living/simple_animal/hostile/mimic/xenobio/M = owner - M.locked = !M.locked - if(!M.locked) - to_chat(M, span_warning("You loosen up, allowing yourself to be opened and closed.")) - else - to_chat(M, span_warning("You stiffen up, preventing anyone from opening or closing you.")) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index c8e8a0653ed9d..abaf7883cb888 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -558,7 +558,7 @@ return TRUE /obj/item/gun/animate_atom_living(mob/living/owner) - new /mob/living/simple_animal/hostile/mimic/copy/ranged(drop_location(), src, owner) + new /mob/living/basic/mimic/copy/ranged(drop_location(), src, owner) /obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer) if(!ishuman(user) || !ishuman(target)) diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm index f8732cc332b72..526e8d2a54a11 100644 --- a/code/modules/research/designs/wiremod_designs.dm +++ b/code/modules/research/designs/wiremod_designs.dm @@ -477,6 +477,11 @@ id = "comp_assoc_list_pick" build_path = /obj/item/circuit_component/list_pick/assoc +/datum/design/component/wire_bundle + name = "Wire Bundle" + id = "comp_wire_bundle" + build_path = /obj/item/circuit_component/wire_bundle + /datum/design/component/wirenet_receive name = "Wirenet Receiver Component" id = "comp_wirenet_receive" @@ -691,3 +696,32 @@ category = list( RND_CATEGORY_CIRCUITRY + RND_SUBCATEGORY_CIRCUITRY_SHELLS ) + +/datum/design/undertile_shell + name = "Under-tile Shell" + desc = "A small shell that can fit under the floor." + id = "undertile_shell" + materials = list( + /datum/material/glass=HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/iron=SHEET_MATERIAL_AMOUNT*2.5, + ) + build_path = /obj/item/undertile_circuit + build_type = COMPONENT_PRINTER + category = list( + RND_CATEGORY_CIRCUITRY + RND_SUBCATEGORY_CIRCUITRY_SHELLS + ) + + +/datum/design/wallmount_shell + name = "Wall-mounted Shell" + desc = "A large shell that can be mounted on a wall." + id = "wallmount_shell" + materials = list( + /datum/material/glass=SHEET_MATERIAL_AMOUNT, + /datum/material/iron=SHEET_MATERIAL_AMOUNT*5, + ) + build_path = /obj/item/wallframe/circuit + build_type = COMPONENT_PRINTER + category = list( + RND_CATEGORY_CIRCUITRY + RND_SUBCATEGORY_CIRCUITRY_SHELLS + ) diff --git a/code/modules/research/techweb/nodes/circuit_nodes.dm b/code/modules/research/techweb/nodes/circuit_nodes.dm index 109873c38510b..9ef3b929ac424 100644 --- a/code/modules/research/techweb/nodes/circuit_nodes.dm +++ b/code/modules/research/techweb/nodes/circuit_nodes.dm @@ -86,6 +86,7 @@ "comp_typecast", "comp_typecheck", "comp_view_sensor", + "comp_wire_bundle", "comp_wirenet_receive", "comp_wirenet_send", "comp_wirenet_send_literal", @@ -108,6 +109,8 @@ "money_bot_shell", "scanner_gate_shell", "scanner_shell", + "undertile_shell", + "wallmount_shell", "comp_equip_action", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) diff --git a/code/modules/surgery/coronary_bypass.dm b/code/modules/surgery/coronary_bypass.dm index 1c5012d7580b9..be147e541181c 100644 --- a/code/modules/surgery/coronary_bypass.dm +++ b/code/modules/surgery/coronary_bypass.dm @@ -12,7 +12,7 @@ /datum/surgery_step/close, ) -/datum/surgery/gastrectomy/mechanic +/datum/surgery/coronary_bypass/mechanic name = "Engine Diagnostic" requires_bodypart_type = BODYTYPE_ROBOTIC steps = list( diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 55161807eb46e..e82a607a9bfb8 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -57,12 +57,6 @@ /mob/living/simple_animal/hostile/megafauna/legion/small, /mob/living/simple_animal/hostile/megafauna/wendigo, /mob/living/simple_animal/hostile/megafauna/wendigo/noportal, - /mob/living/simple_animal/hostile/mimic, - /mob/living/simple_animal/hostile/mimic/copy, - /mob/living/simple_animal/hostile/mimic/copy/machine, - /mob/living/simple_animal/hostile/mimic/copy/ranged, - /mob/living/simple_animal/hostile/mimic/crate, - /mob/living/simple_animal/hostile/mimic/xenobio, /mob/living/simple_animal/hostile/ooze, /mob/living/simple_animal/hostile/ooze/gelatinous, /mob/living/simple_animal/hostile/ooze/grapes, diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm index 5320929f73db3..b5b33ce390bd2 100644 --- a/code/modules/vehicles/mecha/mech_fabricator.dm +++ b/code/modules/vehicles/mecha/mech_fabricator.dm @@ -477,7 +477,7 @@ return if("del_queue_part") - // Delete a specific from from the queue + // Delete a specific from the queue var/index = text2num(params["index"]) remove_from_queue(index) diff --git a/code/modules/wiremod/components/utility/wire_bundle.dm b/code/modules/wiremod/components/utility/wire_bundle.dm new file mode 100644 index 0000000000000..7e0355e894837 --- /dev/null +++ b/code/modules/wiremod/components/utility/wire_bundle.dm @@ -0,0 +1,115 @@ +/obj/item/circuit_component/wire_bundle + display_name = "Wire Bundle" + desc = "A bundle of exposed wires that assemblies can be attached to. Ports will only show up once the circuit is inserted into a shell." + category = "Utility" + circuit_flags = CIRCUIT_FLAG_REFUSE_MODULE + + var/datum/wires/wire_bundle_component/tracked_wires + + var/list/wire_input_ports = list() + var/list/wire_output_ports = list() + +/obj/item/circuit_component/wire_bundle/get_ui_notices() + . = ..() + . += create_ui_notice("Port count is proportional to shell capacity.", "orange", "plug") + . += create_ui_notice("Max port count: [MAX_WIRE_COUNT]", "orange", "plug") + . += create_ui_notice("Incompatible with assembly shell.", "red", "plug-circle-xmark") + +/obj/item/circuit_component/wire_bundle/register_shell(atom/movable/shell) + . = ..() + if(isassembly(shell) && !parent.admin_only) + return + if(shell.wires) // Don't add wires to shells that already have some. + return + tracked_wires = new(shell) + shell.set_wires(tracked_wires) + for(var/wire in tracked_wires.wires) + wire_input_ports[add_input_port("Pulse [wire]", PORT_TYPE_SIGNAL)] = wire + wire_output_ports[wire] = add_output_port("[wire] Pulsed", PORT_TYPE_SIGNAL) + RegisterSignal(tracked_wires, COMSIG_PULSE_WIRE, PROC_REF(on_pulse_wire)) + RegisterSignal(shell, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_shell_requesting_context)) + RegisterSignal(shell, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, PROC_REF(on_shell_secondary_interaction)) + +/obj/item/circuit_component/wire_bundle/unregister_shell(atom/movable/shell) + . = ..() + if(shell.wires != tracked_wires) + return + UnregisterSignal(shell, list(COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM)) + for(var/color in tracked_wires.colors) + var/obj/item/assembly/assembly = tracked_wires.detach_assembly(color) + if(assembly) + assembly.forceMove(drop_location()) + shell.set_wires(null) + QDEL_NULL(tracked_wires) + for(var/datum/port/input/in_port in wire_input_ports) + remove_input_port(in_port) + for(var/wire in wire_output_ports) + var/datum/port/output/out_port = wire_output_ports[wire] + remove_output_port(out_port) + wire_input_ports.Cut() + wire_output_ports.Cut() + +/obj/item/circuit_component/wire_bundle/add_to(obj/item/integrated_circuit/added_to) + . = ..() + if(HAS_TRAIT(added_to, TRAIT_COMPONENT_WIRE_BUNDLE)) + return FALSE + ADD_TRAIT(added_to, TRAIT_COMPONENT_WIRE_BUNDLE, REF(src)) + +/obj/item/circuit_component/wire_bundle/removed_from(obj/item/integrated_circuit/removed_from) + . = ..() + REMOVE_TRAIT(removed_from, TRAIT_COMPONENT_WIRE_BUNDLE, REF(src)) + return ..() + +/obj/item/circuit_component/wire_bundle/input_received(datum/port/input/port) + . = ..() + if(!port) + return + var/wire = wire_input_ports[port] + if(!wire) + return + if(tracked_wires.is_cut(wire)) + return + var/color = tracked_wires.get_color_of_wire(wire) + var/obj/item/assembly/attached = tracked_wires.get_attached(color) + attached?.activate() + +/obj/item/circuit_component/wire_bundle/proc/on_pulse_wire(source, wire) + SIGNAL_HANDLER + if(tracked_wires.is_cut(wire)) + return + var/datum/port/output/port = wire_output_ports[wire] + if(!istype(port)) + return + port.set_output(COMPONENT_SIGNAL) + +/obj/item/circuit_component/wire_bundle/proc/can_access_wires(atom/source) + if(ismachinery(source)) + var/obj/machinery/machine = source + return machine.panel_open + return TRUE + +/obj/item/circuit_component/wire_bundle/proc/on_shell_requesting_context(atom/source, list/context, obj/item/item, mob/user) + SIGNAL_HANDLER + . = NONE + + if(!is_wire_tool(item)) + return + if(!can_access_wires(source)) + return + context[SCREENTIP_CONTEXT_RMB] = "Interact with wires" + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/circuit_component/wire_bundle/proc/on_shell_secondary_interaction(atom/source, mob/user, obj/item/tool) + SIGNAL_HANDLER + if(!is_wire_tool(tool)) + return + if(!can_access_wires(source)) + return + var/datum/component/shell/shell_comp = source.GetComponent(/datum/component/shell) + if(shell_comp.locked) + source.balloon_alert(user, "locked!") + return ITEM_INTERACT_FAILURE + if(source.attempt_wire_interaction(user) == WIRE_INTERACTION_BLOCK) + return ITEM_INTERACT_BLOCKING + + diff --git a/code/modules/wiremod/shell/assembly.dm b/code/modules/wiremod/shell/assembly.dm index 3e69331c4c826..033808c84541a 100644 --- a/code/modules/wiremod/shell/assembly.dm +++ b/code/modules/wiremod/shell/assembly.dm @@ -7,13 +7,13 @@ name = "circuit assembly" desc = "A small electronic device that can house an integrated circuit." icon_state = "wiremod" - attachable = TRUE + assembly_behavior = ASSEMBLY_ALL /// A reference to any holder to use power from instead of the circuit's own cell var/atom/movable/power_use_proxy /// Valid types for `power_use_proxy` to be - var/static/list/power_use_override_types = list(/obj/machinery, /obj/vehicle/sealed/mecha, /obj/item/mod/control, /mob/living/silicon/robot) + var/static/list/power_use_override_types = list(/obj/machinery, /obj/vehicle/sealed/mecha, /obj/item/mod/control, /obj/item/pressure_plate, /mob/living/silicon/robot) /obj/item/assembly/wiremod/Initialize(mapload) . = ..() @@ -23,10 +23,11 @@ ), SHELL_CAPACITY_SMALL) RegisterSignal(shell, COMSIG_SHELL_CIRCUIT_ATTACHED, PROC_REF(on_circuit_attached)) RegisterSignal(shell, COMSIG_SHELL_CIRCUIT_REMOVED, PROC_REF(on_circuit_removed)) - RegisterSignals(src, list(COMSIG_ASSEMBLY_ATTACHED, COMSIG_ASSEMBLY_ADDED_TO_BUTTON), PROC_REF(on_attached)) - RegisterSignals(src, list(COMSIG_ASSEMBLY_DETACHED, COMSIG_ASSEMBLY_REMOVED_FROM_BUTTON), PROC_REF(on_detached)) + RegisterSignal(src, COMSIG_ASSEMBLY_PRE_ATTACH, PROC_REF(on_pre_attach)) + RegisterSignals(src, list(COMSIG_ASSEMBLY_ATTACHED, COMSIG_ASSEMBLY_ADDED_TO_BUTTON, COMSIG_ASSEMBLY_ADDED_TO_PRESSURE_PLATE), PROC_REF(on_attached)) + RegisterSignals(src, list(COMSIG_ASSEMBLY_DETACHED, COMSIG_ASSEMBLY_REMOVED_FROM_BUTTON, COMSIG_ASSEMBLY_REMOVED_FROM_PRESSURE_PLATE), PROC_REF(on_detached)) -/obj/item/assembly/wiremod/proc/on_circuit_attached(_source, obj/item/integrated_circuit/circuit) +/obj/item/assembly/wiremod/proc/on_circuit_attached(source, obj/item/integrated_circuit/circuit) SIGNAL_HANDLER RegisterSignal(circuit, COMSIG_CIRCUIT_PRE_POWER_USAGE, PROC_REF(override_circuit_power_usage)) @@ -34,12 +35,24 @@ SIGNAL_HANDLER UnregisterSignal(source.attached_circuit, COMSIG_CIRCUIT_PRE_POWER_USAGE) -/obj/item/assembly/wiremod/proc/on_attached(_source, atom/movable/holder) +/obj/item/assembly/wiremod/proc/on_pre_attach(obj/item/circuit_component/wire_bundle/source, atom/holder) + SIGNAL_HANDLER + if(!istype(source)) + return + if(source.parent.admin_only) + return + if(istype(holder.wires, /datum/wires/wire_bundle_component)) + var/datum/component/shell/shell_comp = GetComponent(/datum/component/shell) + if(shell_comp.attached_circuit.admin_only) + return + return COMPONENT_CANCEL_ATTACH + +/obj/item/assembly/wiremod/proc/on_attached(source, atom/movable/holder) SIGNAL_HANDLER if(is_type_in_list(holder, power_use_override_types)) power_use_proxy = holder -/obj/item/assembly/wiremod/proc/on_detached(_source) +/obj/item/assembly/wiremod/proc/on_detached(source) SIGNAL_HANDLER power_use_proxy = null @@ -59,6 +72,12 @@ var/obj/item/mod/control/modsuit = power_use_proxy if(modsuit.subtract_charge(power_to_use)) return COMPONENT_OVERRIDE_POWER_USAGE + if(istype(power_use_proxy, /obj/item/pressure_plate)) + if(!power_use_proxy.anchored) + return + var/area/our_area = get_area(power_use_proxy) + if(our_area.apc?.use_energy(power_to_use, AREA_USAGE_EQUIP)) + return COMPONENT_OVERRIDE_POWER_USAGE if(iscyborg(power_use_proxy)) var/mob/living/silicon/robot/borg = power_use_proxy if(borg.cell?.use(power_to_use, force = TRUE)) diff --git a/code/modules/wiremod/shell/undertile.dm b/code/modules/wiremod/shell/undertile.dm new file mode 100644 index 0000000000000..69ef66b61c0ea --- /dev/null +++ b/code/modules/wiremod/shell/undertile.dm @@ -0,0 +1,13 @@ +/obj/item/undertile_circuit + name = "circuit panel" + desc = "A panel for an integrated circuit. It needs to be fit under a floor tile to operate." + icon = 'icons/obj/science/circuits.dmi' + inhand_icon_state = "flashtool" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + icon_state = "undertile" + +/obj/item/undertile_circuit/Initialize(mapload) + . = ..() + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE, INVISIBILITY_OBSERVER, use_anchor = TRUE) + AddComponent(/datum/component/shell, null, SHELL_CAPACITY_SMALL, SHELL_FLAG_REQUIRE_ANCHOR|SHELL_FLAG_USB_PORT) diff --git a/code/modules/wiremod/shell/wallmount.dm b/code/modules/wiremod/shell/wallmount.dm new file mode 100644 index 0000000000000..d461f696da131 --- /dev/null +++ b/code/modules/wiremod/shell/wallmount.dm @@ -0,0 +1,33 @@ +/obj/structure/wallmount_circuit + name = "circuit box" + desc = "A wall-mounted box suitable for the installation of integrated circuits." + icon = 'icons/obj/science/circuits.dmi' + icon_state = "wallmount" + layer = BELOW_OBJ_LAYER + anchored = TRUE + + resistance_flags = LAVA_PROOF | FIRE_PROOF + +/obj/structure/wallmount_circuit/Initialize(mapload) + . = ..() + AddComponent(/datum/component/shell, null, SHELL_CAPACITY_LARGE, SHELL_FLAG_REQUIRE_ANCHOR|SHELL_FLAG_USB_PORT) + +/obj/structure/wallmount_circuit/wrench_act(mob/living/user, obj/item/tool) + var/datum/component/shell/shell_comp = GetComponent(/datum/component/shell) + if(shell_comp.locked) + balloon_alert(user, "locked!") + return ITEM_INTERACT_FAILURE + to_chat(user, span_notice("You start unsecuring the circuit box...")) + if(tool.use_tool(src, user, 40, volume=50)) + to_chat(user, span_notice("You unsecure the circuit box.")) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/item/wallframe/circuit + name = "circuit box frame" + desc = "A box that can be mounted on a wall and have circuits installed." + icon = 'icons/obj/science/circuits.dmi' + icon_state = "wallmount_assembly" + result_path = /obj/structure/wallmount_circuit + pixel_shift = 32 diff --git a/html/changelogs/AutoChangeLog-pr-89013.yml b/html/changelogs/AutoChangeLog-pr-89013.yml deleted file mode 100644 index ad7c6be528824..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-89013.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "AyIong" -delete-after: True -changes: - - qol: "SmartFridge got another redesign, and you can dispense all amount of product in one click (RMB on product)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-89047.yml b/html/changelogs/AutoChangeLog-pr-89047.yml new file mode 100644 index 0000000000000..784e95a25ddc0 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-89047.yml @@ -0,0 +1,5 @@ +author: "NamelessFairy" +delete-after: True +changes: + - bugfix: "Admin fired stray syndicate cargo pods will not rebel against admin whims and launch themselves when cancelled." + - admin: "Admins have new categories to fill syndicate cargo pods with." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-89085.yml b/html/changelogs/AutoChangeLog-pr-89085.yml deleted file mode 100644 index a4df0c607ead2..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-89085.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MTandi" -delete-after: True -changes: - - image: "yet another medkit resprite" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-89089.yml b/html/changelogs/AutoChangeLog-pr-89089.yml deleted file mode 100644 index c6e81b3c33688..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-89089.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "MTandi" -delete-after: True -changes: - - bugfix: "Fixed plant analyzer UI crashing on plants that have a reagent gene on their graft" - - bugfix: "Plants with reagent traits in the graft now properly say the name of the grafted reagent" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-89095.yml b/html/changelogs/AutoChangeLog-pr-89095.yml new file mode 100644 index 0000000000000..4e52e9a2c38cc --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-89095.yml @@ -0,0 +1,4 @@ +author: "Pickle-Coding" +delete-after: True +changes: + - bugfix: "Fixes HFR moderator leaking not leaking properly when a part is cracked." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-89112.yml b/html/changelogs/AutoChangeLog-pr-89112.yml deleted file mode 100644 index b4369a59d8308..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-89112.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed active turfs on the new Turreted Outpost ruin." \ No newline at end of file diff --git a/html/changelogs/archive/2025-01.yml b/html/changelogs/archive/2025-01.yml index cddfcce1af8e8..d0707a54087d2 100644 --- a/html/changelogs/archive/2025-01.yml +++ b/html/changelogs/archive/2025-01.yml @@ -507,3 +507,43 @@ 2025-01-18: EnterTheJake: - image: Codex Cicatrix has received a new sprite +2025-01-19: + Absolucy: + - qol: Small and tiny mobs no longer trigger squeaking objects when walking over + them. + AyIong: + - qol: SmartFridge got another redesign, and you can dispense all amount of product + in one click (RMB on product) + Chowder-McArthor: + - qol: Strange geysers now say in their name what they produce. + Jacquerel: + - rscadd: Eggs (and all other edible food) left in a hot place will fry. + MTandi: + - bugfix: Fixed plant analyzer UI crashing on plants that have a reagent gene on + their graft + - bugfix: Plants with reagent traits in the graft now properly say the name of the + grafted reagent + - image: yet another medkit resprite + SmArtKar: + - bugfix: Fixed active turfs on the new Turreted Outpost ruin. +2025-01-20: + RengaN02: + - bugfix: Fixed Engine Diagnostic surgery + SmArtKar: + - image: Resprited default and mirage grenades + - image: Made the unholy water flask darker + Y0SH1M4S73R: + - refactor: Wires and assemblies have been refactored to have directionality to + them. This mostly makes it so that assemblies can only be attached to wires + it would make sense for them to be attached to. + - qol: Pressure plates can now also accept igniters, condensers, flashes, assembly + shells, and door controllers. + - rscadd: Undertile circuit shells. They only work when placed under floor tiles, + but support USB cables and use APC power instead of cell power. + - rscadd: Wallmounted circuit shells. Large shells that support USB cables and use + APC power instead of cell power. + - rscadd: Wire bundle component. Adds a number of wires to the circuit proportional + to the capacity of the shell, allowing you to use assemblies in circuit logic. + mc-oofert: + - refactor: mimics (bolt of animation, malf ai Machine Override, etc) are basicmobs + - bugfix: crate mimics may now be opened diff --git a/icons/obj/drinks/bottles.dmi b/icons/obj/drinks/bottles.dmi index 2bdacf0426506..3dece4b41c6c9 100644 Binary files a/icons/obj/drinks/bottles.dmi and b/icons/obj/drinks/bottles.dmi differ diff --git a/icons/obj/science/circuits.dmi b/icons/obj/science/circuits.dmi index 1d50c67823bb2..e91025ae61365 100644 Binary files a/icons/obj/science/circuits.dmi and b/icons/obj/science/circuits.dmi differ diff --git a/icons/obj/weapons/grenade.dmi b/icons/obj/weapons/grenade.dmi index 628b271d423e5..c2eba7adcc1d6 100644 Binary files a/icons/obj/weapons/grenade.dmi and b/icons/obj/weapons/grenade.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 9a748f8af99fa..4ff518a0f731b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2006,6 +2006,7 @@ #include "code\datums\wires\syndicatebomb.dm" #include "code\datums\wires\tesla_coil.dm" #include "code\datums\wires\vending.dm" +#include "code\datums\wires\wire_bundle_component.dm" #include "code\datums\wounds\_wound_static_data.dm" #include "code\datums\wounds\_wounds.dm" #include "code\datums\wounds\blunt.dm" @@ -5074,6 +5075,8 @@ #include "code\modules\mob\living\basic\ruin_defender\mad_piano.dm" #include "code\modules\mob\living\basic\ruin_defender\skeleton.dm" #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" +#include "code\modules\mob\living\basic\ruin_defender\mimic\mimic.dm" +#include "code\modules\mob\living\basic\ruin_defender\mimic\mimic_ai.dm" #include "code\modules\mob\living\basic\ruin_defender\wizard\wizard.dm" #include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_ai.dm" #include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_spells.dm" @@ -5350,7 +5353,6 @@ #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm" #include "code\modules\mob\living\simple_animal\hostile\illusion.dm" -#include "code\modules\mob\living\simple_animal\hostile\mimic.dm" #include "code\modules\mob\living\simple_animal\hostile\ooze.dm" #include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm" #include "code\modules\mob\living\simple_animal\hostile\zombie.dm" @@ -6473,6 +6475,7 @@ #include "code\modules\wiremod\components\utility\timepiece.dm" #include "code\modules\wiremod\components\utility\typecast.dm" #include "code\modules\wiremod\components\utility\typecheck.dm" +#include "code\modules\wiremod\components\utility\wire_bundle.dm" #include "code\modules\wiremod\components\variables\getter.dm" #include "code\modules\wiremod\components\variables\setter.dm" #include "code\modules\wiremod\components\wirenet\receive.dm" @@ -6521,6 +6524,8 @@ #include "code\modules\wiremod\shell\scanner_gate.dm" #include "code\modules\wiremod\shell\server.dm" #include "code\modules\wiremod\shell\shell_items.dm" +#include "code\modules\wiremod\shell\undertile.dm" +#include "code\modules\wiremod\shell\wallmount.dm" #include "code\modules\zombie\items.dm" #include "code\modules\zombie\organs.dm" #include "code\ze_genesis_call\genesis_call.dm" diff --git a/tools/UpdatePaths/Scripts/88910_mimicbasicmobs.txt b/tools/UpdatePaths/Scripts/88910_mimicbasicmobs.txt new file mode 100644 index 0000000000000..b7a012ed1d9b0 --- /dev/null +++ b/tools/UpdatePaths/Scripts/88910_mimicbasicmobs.txt @@ -0,0 +1,5 @@ +/mob/living/simple_animal/hostile/mimic : /mob/living/basic/mimic/crate{@OLD} +/mob/living/simple_animal/hostile/mimic/crate : /mob/living/basic/mimic/crate{@OLD} +/mob/living/simple_animal/hostile/mimic/xenobio : /mob/living/basic/mimic/crate/xenobio{@OLD} +/mob/living/simple_animal/hostile/mimic/copy : /mob/living/basic/mimic/copy{@OLD} +/mob/living/simple_animal/hostile/mimic/copy/ranged : /mob/living/basic/mimic/copy/ranged{@OLD}