diff --git a/code/__DEFINES/assemblies.dm b/code/__DEFINES/assemblies.dm
new file mode 100644
index 00000000000..53409ac7254
--- /dev/null
+++ b/code/__DEFINES/assemblies.dm
@@ -0,0 +1,8 @@
+/// When combined in a holder, blacklists duplicate assemblies
+#define ASSEMBLY_NO_DUPLICATES (1<<0)
+
+/// How loud do assemblies beep at
+#define ASSEMBLY_BEEP_VOLUME 5
+
+/// The max amount of assemblies attachable on an assembly holder
+#define HOLDER_MAX_ASSEMBLIES 12
diff --git a/code/datums/components/trapdoor.dm b/code/datums/components/trapdoor.dm
index 9bde925e409..154538b244b 100644
--- a/code/datums/components/trapdoor.dm
+++ b/code/datums/components/trapdoor.dm
@@ -224,7 +224,7 @@
var/list/stored_decals = list()
-/obj/item/assembly/trapdoor/pulsed(radio, mob/pulser)
+/obj/item/assembly/trapdoor/pulsed(mob/pulser)
. = ..()
if(linked)
return
@@ -307,7 +307,7 @@
return TRUE
if(!internals.linked)
- internals.pulsed(pulser = user)
+ internals.pulsed(user)
// The pulse linked successfully
if(internals.linked)
user.balloon_alert(user, "linked")
@@ -325,7 +325,7 @@
icon_state = "trapdoor_pressed"
addtimer(VARSET_CALLBACK(src, icon_state, initial(icon_state)), trapdoor_cooldown_time)
COOLDOWN_START(src, trapdoor_cooldown, trapdoor_cooldown_time)
- internals.pulsed(pulser = user)
+ internals.pulsed(user)
return TRUE
#undef TRAPDOOR_LINKING_SEARCH_RANGE
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index 0b9bbab374b..81e2f292d44 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -182,7 +182,7 @@
icon_state = "[skin]1"
if(device)
- device.pulsed(pulser = user)
+ device.pulsed(user)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_BUTTON_PRESSED,src)
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/, update_appearance)), 15)
diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm
index a5830103416..4f1dac572eb 100644
--- a/code/modules/assembly/assembly.dm
+++ b/code/modules/assembly/assembly.dm
@@ -1,9 +1,3 @@
-#define WIRE_RECEIVE (1<<0)
-#define WIRE_PULSE (1<<1)
-#define WIRE_PULSE_SPECIAL (1<<2)
-#define WIRE_RADIO_RECEIVE (1<<3)
-#define WIRE_RADIO_PULSE (1<<4)
-#define ASSEMBLY_BEEP_VOLUME 5
/obj/item/assembly
name = "assembly"
@@ -24,10 +18,11 @@
* This will prevent things such as visible lasers from facing the incorrect direction when transformed by assembly_holder's update_appearance()
*/
var/is_position_sensitive = FALSE
+ /// Flags related to this assembly. See [assemblies.dm]
+ var/assembly_flags = NONE
var/secured = TRUE
var/list/attached_overlays = null
var/obj/item/assembly_holder/holder = null
- var/wire_type = WIRE_RECEIVE | WIRE_PULSE
var/attachable = FALSE // can this be attached to wires
var/datum/wires/connected = null
var/next_activate = 0 //When we're next allowed to activate - for spam control
@@ -75,24 +70,26 @@
return FALSE
return TRUE
-///Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
-/obj/item/assembly/proc/pulsed(radio = FALSE, mob/pulser)
- if(wire_type & WIRE_RECEIVE)
- INVOKE_ASYNC(src, PROC_REF(activate), pulser)
- if(radio && (wire_type & WIRE_RADIO_RECEIVE))
- INVOKE_ASYNC(src, PROC_REF(activate), pulser)
+/**
+ * Pulsed: This device was pulsed by another device
+ *
+ * * pulser: Who triggered the pulse
+ */
+/obj/item/assembly/proc/pulsed(mob/pulser)
+ INVOKE_ASYNC(src, PROC_REF(activate), pulser)
SEND_SIGNAL(src, COMSIG_ASSEMBLY_PULSED)
return TRUE
-///Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct
-/obj/item/assembly/proc/pulse(radio = FALSE)
- if(connected && wire_type)
+/**
+ * Pulse: This device is emitting a pulse to act on another device
+ */
+/obj/item/assembly/proc/pulse()
+ // if we have connected wires and are a pulsing assembly, pulse it
+ if(connected)
connected.pulse_assembly(src)
- return TRUE
- if(holder && (wire_type & WIRE_PULSE))
- holder.process_activation(src, 1, 0)
- if(holder && (wire_type & WIRE_PULSE_SPECIAL))
- holder.process_activation(src, 0, 1)
+ // otherwise if we're attached to a holder, process the activation of it with our flags
+ else if(holder)
+ holder.process_activation(src)
return TRUE
/// What the device does when turned on
@@ -116,21 +113,29 @@
return
. = ..()
-/obj/item/assembly/attackby(obj/item/W, mob/user, params)
- if(isassembly(W))
- var/obj/item/assembly/A = W
- if((!A.secured) && (!secured))
- holder = new/obj/item/assembly_holder(get_turf(src))
- holder.assemble(src,A,user)
- to_chat(user, span_notice("You attach and secure \the [A] to \the [src]!"))
- else
- to_chat(user, span_warning("Both devices must be in attachable mode to be attached together."))
+/obj/item/assembly/attackby(obj/item/attacking_item, mob/user, params)
+ if(isassembly(attacking_item))
+ var/obj/item/assembly/new_assembly = attacking_item
+ // Check both our's and their's assembly flags to see if either should not duplicate
+ // If so, and we match types, don't create a holder - block it
+ if(((new_assembly.assembly_flags|assembly_flags) & ASSEMBLY_NO_DUPLICATES) && istype(new_assembly, type))
+ balloon_alert(user, "can't attach another of that!")
+ return
+ if(new_assembly.secured || secured)
+ balloon_alert(user, "both devices not attachable!")
+ return
+
+ holder = new /obj/item/assembly_holder(drop_location())
+ holder.assemble(src, new_assembly, user)
+ holder.balloon_alert(user, "parts combined")
return
- if(istype(W, /obj/item/assembly_holder))
- if(!secured)
- var/obj/item/assembly_holder/added_to_holder = W
- added_to_holder.add_assembly(src, user)
- ..()
+
+ if(istype(attacking_item, /obj/item/assembly_holder))
+ var/obj/item/assembly_holder/added_to_holder = attacking_item
+ added_to_holder.try_add_assembly(src, user)
+ return
+
+ return ..()
/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I)
if(..())
diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm
index 004315f7c21..1c5ff4f448f 100644
--- a/code/modules/assembly/holder.dm
+++ b/code/modules/assembly/holder.dm
@@ -46,6 +46,24 @@
for(var/obj/item/assembly/assembly in assemblies)
assembly.on_attach()
+/obj/item/assembly_holder/proc/try_add_assembly(obj/item/assembly/attached_assembly, mob/user)
+ if(attached_assembly.secured)
+ balloon_alert(attached_assembly, "not attachable!")
+ return FALSE
+
+ if(LAZYLEN(assemblies) >= HOLDER_MAX_ASSEMBLIES)
+ balloon_alert(user, "too many assemblies!")
+ return FALSE
+
+ if(attached_assembly.assembly_flags & ASSEMBLY_NO_DUPLICATES)
+ if(locate(attached_assembly.type) in assemblies)
+ balloon_alert(user, "can't attach another of that!")
+ return FALSE
+
+ add_assembly(attached_assembly, user)
+ balloon_alert(user, "part attached")
+ return TRUE
+
/**
* Adds an assembly to the assembly holder
*
@@ -118,12 +136,11 @@
/obj/item/assembly_holder/attackby(obj/item/weapon, mob/user, params)
if(isassembly(weapon))
- var/obj/item/assembly/attached_assembly = weapon
- if(!attached_assembly.secured)
- add_assembly(attached_assembly, user)
- balloon_alert(user, "part added")
+ try_add_assembly(weapon, user)
return
+
return ..()
+
/obj/item/assembly_holder/AltClick(mob/user)
return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation
@@ -153,16 +170,14 @@
* send a pulse to the assembly holder, which then calls this proc that actually activates the assemblies
* Arguments:
* * /obj/device - the device we sent the pulse from which called this proc
- * * normal - if this is a normal activation
- * * special - if this is a special activation
*/
-/obj/item/assembly_holder/proc/process_activation(obj/device, normal = TRUE, special = TRUE)
+/obj/item/assembly_holder/proc/process_activation(obj/device)
if(!device)
return FALSE
- if(normal && LAZYLEN(assemblies) >= 2)
+ if(LAZYLEN(assemblies) >= 2)
for(var/obj/item/assembly/assembly as anything in assemblies)
if(assembly != device)
- assembly.pulsed(FALSE)
+ assembly.pulsed()
if(master)
master.receive_signal()
return TRUE
diff --git a/code/modules/assembly/igniter.dm b/code/modules/assembly/igniter.dm
index 3b27bb59e7d..783f67c35d5 100644
--- a/code/modules/assembly/igniter.dm
+++ b/code/modules/assembly/igniter.dm
@@ -12,6 +12,7 @@
heat = 1000
drop_sound = 'sound/items/handling/component_drop.ogg'
pickup_sound = 'sound/items/handling/component_pickup.ogg'
+ assembly_flags = ASSEMBLY_NO_DUPLICATES
/obj/item/assembly/igniter/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
@@ -43,13 +44,6 @@
sparks.start()
return TRUE
-/obj/item/assembly/igniter/attackby(obj/item/potential_igniter, mob/user, params)
- if(isigniter(potential_igniter))
- to_chat(user, "You briefly consider attaching [potential_igniter] to [src], and then conclude that's a very silly thing to do and move on.")
- return
- return ..()
-
-
/obj/item/assembly/igniter/attack_self(mob/user)
activate()
add_fingerprint(user)
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index 9cce8adc370..75ffdfe2413 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -149,7 +149,7 @@
switchListener(location)
if(!secured || !on || next_activate > world.time)
return FALSE
- pulse(FALSE)
+ pulse()
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/mob/hearing_mob in get_hearers_in_view(hearing_range, src))
hearing_mob.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm
index a118c1bc48a..4ab9cb93820 100644
--- a/code/modules/assembly/mousetrap.dm
+++ b/code/modules/assembly/mousetrap.dm
@@ -87,7 +87,7 @@
var/mob/living/carbon/human/user = usr
if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
to_chat(user, span_warning("Your hand slips, setting off the trigger!"))
- pulse(FALSE)
+ pulse()
update_appearance()
playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3)
@@ -117,7 +117,7 @@
var/mob/living/carbon/human/victim = target
if(HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE))
playsound(src, 'sound/effects/snap.ogg', 50, TRUE)
- pulse(FALSE)
+ pulse()
return FALSE
switch(type)
if("feet")
@@ -148,7 +148,7 @@
visible_message(span_boldannounce("Skreeeee!")) //He's simply too large to be affected by a tiny mouse trap.
playsound(src, 'sound/effects/snap.ogg', 50, TRUE)
- pulse(FALSE)
+ pulse()
/**
* clumsy_check: Sets off the mousetrap if handled by a clown (with some probability)
diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm
index 859d7c1d08d..56fdc8629bf 100644
--- a/code/modules/assembly/proximity.dm
+++ b/code/modules/assembly/proximity.dm
@@ -90,7 +90,7 @@
/obj/item/assembly/prox_sensor/proc/sense()
if(!scanning || !secured || next_activate > world.time)
return FALSE
- pulse(FALSE)
+ pulse()
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/mob/hearing_mob in get_hearers_in_view(hearing_range, src))
hearing_mob.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index 381c02de759..9dca6ece811 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -6,7 +6,6 @@
lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
custom_materials = list(/datum/material/iron=400, /datum/material/glass=120)
- wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE
attachable = TRUE
drop_sound = 'sound/items/handling/component_drop.ogg'
pickup_sound = 'sound/items/handling/component_pickup.ogg'
@@ -156,8 +155,6 @@
return
if(signal.data["code"] != code)
return
- if(!(src.wires & WIRE_RADIO_RECEIVE))
- return
if(suicider)
manual_suicide(suicider)
return
@@ -165,7 +162,7 @@
// If the holder is a TTV, we want to store the last received signal to incorporate it into TTV logging, else wipe it.
last_receive_signal_log = istype(holder, /obj/item/transfer_valve) ? signal.logging_data : null
- pulse(TRUE)
+ pulse()
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/mob/hearing_mob in get_hearers_in_view(hearing_range, src))
hearing_mob.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
@@ -177,34 +174,6 @@
radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER)
return
-// Embedded signaller used in grenade construction.
-// It's necessary because the signaler doens't have an off state.
-// Generated during grenade construction. -Sayu
-/obj/item/assembly/signaler/receiver
- var/on = FALSE
-
-/obj/item/assembly/signaler/receiver/proc/toggle_safety()
- on = !on
-
-/obj/item/assembly/signaler/receiver/activate()
- toggle_safety()
- return TRUE
-
-/obj/item/assembly/signaler/receiver/examine(mob/user)
- . = ..()
- . += span_notice("The radio receiver is [on?"on":"off"].")
-
-/obj/item/assembly/signaler/receiver/receive_signal(datum/signal/signal)
- if(!on)
- return
- return ..(signal)
-
-/obj/item/assembly/signaler/anomaly/attack_self()
- return
-
-/obj/item/assembly/signaler/crystal_anomaly/attack_self()
- return
-
/obj/item/assembly/signaler/cyborg
/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params)
@@ -228,4 +197,3 @@
if(ispAI(user))
return TRUE
. = ..()
-
diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm
index a9f43ac380c..db85d25dc3b 100644
--- a/code/modules/assembly/timer.dm
+++ b/code/modules/assembly/timer.dm
@@ -55,7 +55,7 @@
/obj/item/assembly/timer/proc/timer_end()
if(secured && next_activate <= world.time)
- pulse(FALSE)
+ pulse()
audible_message(span_infoplain("[icon2html(src, hearers(src))] *beep* *beep* *beep*"), null, hearing_range)
for(var/mob/hearing_mob in get_hearers_in_view(hearing_range, src))
hearing_mob.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm
index 87d59b59d89..ef5270075be 100644
--- a/code/modules/research/anomaly/anomaly_core.dm
+++ b/code/modules/research/anomaly/anomaly_core.dm
@@ -25,6 +25,9 @@
user.set_suicide(TRUE)
user.gib()
+/obj/item/assembly/signaler/anomaly/attack_self()
+ return
+
/obj/item/assembly/signaler/anomaly/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_ANALYZER)
to_chat(user, span_notice("Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(frequency)], code [code]."))
diff --git a/code/modules/wiremod/shell/assembly.dm b/code/modules/wiremod/shell/assembly.dm
index ed060f4b8a7..ea278fb4a64 100644
--- a/code/modules/wiremod/shell/assembly.dm
+++ b/code/modules/wiremod/shell/assembly.dm
@@ -64,4 +64,4 @@
return ..()
/obj/item/circuit_component/assembly_output/input_received(datum/port/input/port, list/return_values)
- attached_assembly.pulse(FALSE)
+ attached_assembly.pulse()
diff --git a/tgstation.dme b/tgstation.dme
index 6a2bb04776e..6e53ed4fdbb 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -39,6 +39,7 @@
#include "code\__DEFINES\apc_defines.dm"
#include "code\__DEFINES\aquarium.dm"
#include "code\__DEFINES\art.dm"
+#include "code\__DEFINES\assemblies.dm"
#include "code\__DEFINES\atom_hud.dm"
#include "code\__DEFINES\basic_mobs.dm"
#include "code\__DEFINES\blackmarket.dm"