Files
Bubberstation/code/game/objects/items/puzzle_pieces.dm
Y0SH1M4S73R 90b67b0541 A Small Circuit Expansion: Wallmounts, Undertiles, and Wire Bundles (#89122)
## About The Pull Request

This PR adds several circuit features, and changes several
assembly-related features to make them a bit more logical in how they
work. Included are the following changes/additions:

- Assemblies and wires have been refactored with vars that describe
their behavior:
- Assemblies have an `assembly_behavior` bitflag instead of a
`attachable` var. This var has 3 flags:
- `ASSEMBLY_INPUT`: The assembly is able to pulse the wire it is
attached to.
- `ASSEMBLY_TOGGLE_ARMED`: On activation, the assembly toggles whether
it can pulse the wire it is attached to.
- `ASSEMBLY_FUNCTIONAL_OUTPUT`: On activation, the assembly does
something other than just toggling whether it's armed.
  - Wires have a `wires_behavior` bitflag with 3 flags:
- `WIRES_INPUT`: The object the wires are attached to does something
when the wires are pulsed.
- `WIRES_TOGGLE_ARMED`: The object the wires are attached to can
activate assemblies attached to those wires, and is fine if all that
activating that assembly does is toggle whether it's armed.
- `WIRES_FUNCTIONAL_OUTPUT`: The object the wires are attached to
expects that assemblies attached to its wires do something other than
toggling themselves when activated.
- Buttons can only accept assemblies with `ASSEMBLY_FUNCTIONAL_OUTPUT`.
- Pressure plates can now accept any assembly with
`ASSEMBLY_FUNCTIONAL_OUTPUT`, not just signalers. Assembly shells
attached to pressure plates will draw power from the powernet if the
pressure plate is under a tile.
- Adds a new circuit component - the wire bundle.
- This component gives the circuit a number of wires corresponding to
the size of the shell.
- Each wire has a corresponding port on the component that can pulse,
and receive pulses from, that wire.
  - A circuit can only have one wire bundle component.
- Assembly shells cannot be attached to these wires, and no wires will
be created if the component is added to an assembly shell.
  - Available with roundstart tech.
- Adds two new shells.
- The wallmounted shell is a large shell that can be hung on walls, and
uses power from the area it's placed in.
    - Frame icon:

![image](https://github.com/user-attachments/assets/2fe9297d-44a9-4ceb-b803-a758e59b807d)
    - Constructed icon:

![image](https://github.com/user-attachments/assets/34c3ffcb-53cb-415f-952f-aec5de63cf17)
- The undertile shell is a small shell that only works when fit under a
floor tile, but uses power from the area it's placed in.

![image](https://github.com/user-attachments/assets/11ea150d-08be-4ce9-bf44-35a9d6e2d94f)
  - Both shells support usb cables.
- The above shells are available with the Advanced Shells techweb node.

## Why It's Good For The Game

The wire bundle component complements the functionality of the assembly
shell by allowing circuits to use assemblies directly in their logic.

The wallmounted and undertile shells provide ways of placing circuits
that don't necessarily take up space for machines. The undertile shell
is particularly useful for relaying usb component data over wirenets.

Pressure plates being able to accept assemblies other than signalers
expands their uses significantly.

## Changelog

🆑
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.
add: Undertile circuit shells. They only work when placed under floor
tiles, but support USB cables and use APC power instead of cell power.
add: Wallmounted circuit shells. Large shells that support USB cables
and use APC power instead of cell power.
add: 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.
/🆑
2025-01-19 18:40:23 +00:00

669 lines
22 KiB
Plaintext

//Every time you got lost looking for keycards, increment: 2
//**************
//*****Keys*****
//**************
/obj/item/keycard
name = "security keycard"
desc = "This feels like it belongs to a door."
icon = 'icons/obj/fluff/puzzle_small.dmi'
icon_state = "keycard"
force = 0
throwforce = 0
w_class = WEIGHT_CLASS_TINY
throw_speed = 1
throw_range = 7
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
var/puzzle_id = null
/obj/item/keycard/get_save_vars()
return ..() + NAMEOF(src, puzzle_id)
//Two test keys for use alongside the two test doors.
/obj/item/keycard/yellow
name = "yellow keycard"
desc = "A yellow keycard. How fantastic. Looks like it belongs to a high security door."
color = "#f0da12"
puzzle_id = "yellow"
/obj/item/keycard/blue
name = "blue keycard"
desc = "A blue keycard. How terrific. Looks like it belongs to a high security door."
color = "#3bbbdb"
puzzle_id = "blue"
//***************
//*****Doors*****
//***************
/obj/machinery/door/puzzle
name = "locked door"
desc = "This door only opens under certain conditions. It looks virtually indestructible."
icon = 'icons/obj/doors/puzzledoor/default.dmi'
icon_state = "door_closed"
explosion_block = 3
heat_proof = TRUE
max_integrity = 600
armor_type = /datum/armor/door_puzzle
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
move_resist = MOVE_FORCE_OVERPOWERING
damage_deflection = 70
can_open_with_hands = FALSE
/// Make sure that the puzzle has the same puzzle_id as the keycard door! (If this is null, queuelinks dont happen!)
var/puzzle_id = null
/// do we use queue_links?
var/uses_queuelinks = TRUE
/// Message that occurs when the door is opened
var/open_message = "The door beeps, and slides opens."
/obj/machinery/door/puzzle/get_save_vars()
return ..() + NAMEOF(src, puzzle_id)
//Standard Expressions to make keycard doors basically un-cheeseable
/datum/armor/door_puzzle
melee = 100
bullet = 100
laser = 100
energy = 100
bomb = 100
bio = 100
fire = 100
acid = 100
/obj/machinery/door/puzzle/Initialize(mapload)
. = ..()
if(!isnull(puzzle_id) && uses_queuelinks)
SSqueuelinks.add_to_queue(src, puzzle_id)
AddElement(/datum/element/empprotection, EMP_PROTECT_ALL)
/obj/machinery/door/puzzle/MatchedLinks(id, list/partners)
for(var/partner in partners)
RegisterSignal(partner, COMSIG_PUZZLE_COMPLETED, PROC_REF(try_signal))
/obj/machinery/door/puzzle/proc/try_signal(datum/source, try_id)
SIGNAL_HANDLER
puzzle_id = null //honestly these cant be closed anyway and im not fucking around with door code anymore
INVOKE_ASYNC(src, PROC_REF(try_puzzle_open), null)
/obj/machinery/door/puzzle/animation_length(animation)
switch(animation)
if(DOOR_OPENING_ANIMATION)
return 1.0 SECONDS
/obj/machinery/door/puzzle/animation_segment_delay(animation)
switch(animation)
if(DOOR_OPENING_PASSABLE)
return 0.8 SECONDS
if(DOOR_OPENING_FINISHED)
return 1.0 SECONDS
/obj/machinery/door/puzzle/Bumped(atom/movable/AM)
return !density && ..()
/obj/machinery/door/puzzle/ex_act(severity, target)
return FALSE
/obj/machinery/door/puzzle/try_to_activate_door(mob/user, access_bypass = FALSE)
add_fingerprint(user)
if(operating)
return
/obj/machinery/door/puzzle/proc/try_puzzle_open(try_id)
if(puzzle_id && puzzle_id != try_id)
return FALSE
if(!density)
visible_message(span_warning("The door can't seem to be closed."))
return TRUE
if(open_message)
visible_message(span_notice(open_message))
open()
return TRUE
/obj/machinery/door/puzzle/keycard
desc = "This door only opens when a keycard is swiped. It looks virtually indestructible."
uses_queuelinks = FALSE
/obj/machinery/door/puzzle/keycard/attackby(obj/item/attacking_item, mob/user, params)
. = ..()
if(!istype(attacking_item, /obj/item/keycard))
return
var/obj/item/keycard/key = attacking_item
if(!try_puzzle_open(key.puzzle_id))
to_chat(user, span_notice("[src] buzzes. This must not be the right key."))
//Test doors. Gives admins a few doors to use quickly should they so choose for events.
/obj/machinery/door/puzzle/keycard/yellow_required
name = "blue airlock"
desc = "It looks like it requires a yellow keycard."
puzzle_id = "yellow"
/obj/machinery/door/puzzle/keycard/blue_required
name = "blue airlock"
desc = "It looks like it requires a blue keycard."
puzzle_id = "blue"
/obj/machinery/door/puzzle/light
desc = "This door only opens when a linked mechanism is powered. It looks virtually indestructible."
//*************************
//***Box Pushing Puzzles***
//*************************
//We're working off a subtype of pressureplates, which should work just a BIT better now.
/obj/structure/holobox
name = "holobox"
desc = "A hard-light box, containing a secure decryption key."
icon = 'icons/obj/fluff/puzzle_small.dmi'
icon_state = "laserbox"
density = TRUE
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
//Uses the pressure_plate settings for a pretty basic custom pattern that waits for a specific item to trigger. Easy enough to retool for mapping purposes or subtypes.
/obj/item/pressure_plate/hologrid
name = "hologrid"
desc = "A high power, electronic input port for a holobox, which can unlock the hologrid's storage compartment. Safe to stand on."
icon = 'icons/obj/fluff/puzzle_small.dmi'
icon_state = "lasergrid"
anchored = TRUE
trigger_mob = FALSE
trigger_item = TRUE
specific_item = /obj/structure/holobox
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
protected = TRUE
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
undertile_pressureplate = FALSE
var/reward = /obj/item/food/cookie
var/claimed = FALSE
/obj/item/pressure_plate/hologrid/get_save_vars()
return ..() + NAMEOF(src, reward)
/obj/item/pressure_plate/hologrid/Initialize(mapload)
. = ..()
if(undertile_pressureplate)
AddElement(/datum/element/undertile, tile_overlay = tile_overlay, use_anchor = FALSE) //we remove use_anchor here, so it ALWAYS stays anchored
/obj/item/pressure_plate/hologrid/examine(mob/user)
. = ..()
if(claimed)
. += span_notice("This one appears to be spent already.")
/obj/item/pressure_plate/hologrid/trigger()
if(!claimed)
new reward(loc)
flick("lasergrid_a",src)
icon_state = "lasergrid_full"
claimed = TRUE
/obj/item/pressure_plate/hologrid/on_entered(datum/source, atom/movable/AM)
. = ..()
if(trigger_item && istype(AM, specific_item) && !claimed)
AM.set_anchored(TRUE)
flick("laserbox_burn", AM)
trigger()
QDEL_IN(AM, 15)
//Light puzzle
/obj/structure/light_puzzle
name = "light mechanism"
desc = "It's a mechanism that seems to power something when all the lights are lit up. It looks virtually indestructible."
icon = 'icons/obj/fluff/puzzle_small.dmi'
icon_state = "light_puzzle"
anchored = TRUE
explosion_block = 3
armor_type = /datum/armor/structure_light_puzzle
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
light_range = MINIMUM_USEFUL_LIGHT_RANGE
light_power = 3
light_color = LIGHT_COLOR_ORANGE
var/powered = FALSE
var/puzzle_id = null
var/list/light_list = list(
0, 0, 0,
0, 0, 0,
0, 0, 0
)
/// Banned combinations of the list in decimal
var/static/list/banned_combinations = list(-1, 47, 95, 203, 311, 325, 422, 473, 488, 500, 511)
/// queue size, must match count of objects this activates!
var/queue_size = 2
/obj/structure/light_puzzle/get_save_vars()
return ..() + list(NAMEOF(src, queue_size), NAMEOF(src, puzzle_id))
/datum/armor/structure_light_puzzle
melee = 100
bullet = 100
laser = 100
energy = 100
bomb = 100
bio = 100
fire = 100
acid = 100
/obj/structure/light_puzzle/Initialize(mapload)
AddElement(/datum/element/blocks_explosives)
. = ..()
var/generated_board = -1
while(generated_board in banned_combinations)
generated_board = rand(0, 510)
for(var/i in 0 to 8)
var/position = !!(generated_board & (1<<i))
light_list[i+1] = position
update_icon(UPDATE_OVERLAYS)
if(!isnull(puzzle_id))
SSqueuelinks.add_to_queue(src, puzzle_id, queue_size)
/obj/structure/light_puzzle/update_overlays()
. = ..()
for(var/i in 1 to 9)
if(!light_list[i])
continue
var/mutable_appearance/lit_image = mutable_appearance('icons/obj/fluff/puzzle_small.dmi', "light_lit")
var/mutable_appearance/emissive_image = emissive_appearance('icons/obj/fluff/puzzle_small.dmi', "light_lit", src)
lit_image.pixel_x = 8 * ((i % 3 || 3 ) - 1)
lit_image.pixel_y = -8 * (ROUND_UP(i / 3) - 1)
emissive_image.pixel_x = lit_image.pixel_x
emissive_image.pixel_y = lit_image.pixel_y
. += lit_image
. += emissive_image
/obj/structure/light_puzzle/attack_hand(mob/living/user, list/modifiers)
if(!modifiers || powered)
return ..()
var/light_clicked
var/x_clicked = text2num(modifiers[ICON_X])
var/y_clicked = text2num(modifiers[ICON_Y])
if(x_clicked <= 4 || x_clicked >= 29 || y_clicked <= 4 || y_clicked >= 29)
return ..()
x_clicked = ROUND_UP((x_clicked - 4) / 8)
y_clicked = (-(ROUND_UP((y_clicked - 4) / 8) - 4) - 1) * 3
light_clicked = x_clicked + y_clicked
switch_light(light_clicked)
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
/obj/structure/light_puzzle/proc/switch_light(light)
var/list/updating_lights = list()
updating_lights += light
if(light % 3 != 0)
updating_lights += light + 1
if(light % 3 != 1)
updating_lights += light - 1
if(light + 3 <= 9)
updating_lights += light + 3
if(light - 3 > 0)
updating_lights += light - 3
for(var/updating_light in updating_lights)
light_list[updating_light] = !light_list[updating_light]
update_icon(UPDATE_OVERLAYS)
for(var/checking_light in light_list)
if(!checking_light)
return
visible_message(span_boldnotice("[src] becomes fully charged!"))
powered = TRUE
SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED)
playsound(src, 'sound/machines/synth/synth_yes.ogg', 100, TRUE)
//
// literally just buttons
//
/obj/machinery/puzzle
name = "abstract puzzle gizmo"
icon = 'icons/obj/machines/wallmounts.dmi'
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
/// have we been pressed already?
var/used = FALSE
/// can we be pressed only once?
var/single_use = TRUE
/// puzzle id we send on press
var/id //null would literally open every puzzle door without an id
/// queue size, must match count of objects this activates!
var/queue_size = 2
/// should the puzzle machinery perform the final step of the queue link on LateInitialize? An alternative to queue size
var/late_initialize_pop = FALSE
/obj/machinery/puzzle/get_save_vars()
return ..() + list(NAMEOF(src, queue_size), NAMEOF(src, id))
/obj/machinery/puzzle/Initialize(mapload)
. = ..()
if(!isnull(id))
SSqueuelinks.add_to_queue(src, id, late_initialize_pop ? 0 : queue_size)
return late_initialize_pop ? INITIALIZE_HINT_LATELOAD : .
/obj/machinery/puzzle/post_machine_initialize()
. = ..()
if(late_initialize_pop && id && SSqueuelinks.queues[id])
SSqueuelinks.pop_link(id)
/obj/machinery/puzzle/proc/on_puzzle_complete() //incase someone wants to make this do something else for some reason
SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED)
/obj/machinery/puzzle/update_icon_state()
icon_state = "[base_icon_state][used]"
return ..()
/obj/machinery/puzzle/button
name = "control panel"
desc = "A panel that controls something nearby. I'm sure it being covered in hazard stripes is fine."
icon = 'icons/obj/machines/wallmounts.dmi'
icon_state = "lockdown0"
base_icon_state = "lockdown"
/obj/machinery/puzzle/button/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
if(used && single_use)
return
used = single_use
update_icon_state()
visible_message(span_notice("[user] presses a button on [src]."), span_notice("You press a button on [src]."))
playsound(src, 'sound/machines/terminal/terminal_button07.ogg', 45, TRUE)
on_puzzle_complete()
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/button, 32)
/obj/machinery/puzzle/keycardpad
name = "keycard panel"
desc = "A panel that controls something nearby. Accepts keycards."
icon_state = "keycardpad0"
base_icon_state = "keycardpad"
/obj/machinery/puzzle/keycardpad/attackby(obj/item/attacking_item, mob/user, params)
. = ..()
if(!istype(attacking_item, /obj/item/keycard) || used)
return
var/obj/item/keycard/key = attacking_item
var/correct_card = key.puzzle_id == id
balloon_alert_to_viewers("[correct_card ? "correct" : "incorrect"] card swiped[correct_card ? "" : "!"]")
playsound(src, 'sound/machines/card_slide.ogg', 45, TRUE)
if(!correct_card)
return
used = TRUE
update_icon_state()
playsound(src, 'sound/machines/beep/beep.ogg', 45, TRUE)
on_puzzle_complete()
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/keycardpad, 32)
/obj/machinery/puzzle/password
name = "password panel"
desc = "A panel that controls something nearby. This one requires a (case-sensitive) password, and it's not \"Swordfish\"."
icon_state = "passpad0"
base_icon_state = "passpad"
///The password to this door.
var/password = ""
///The text shown in the tgui input popup
var/tgui_text = "Please enter the password."
///The title of the tgui input popup
var/tgui_title = "What's the password?"
///Decides whether the max length of the input is MAX_NAME_LEN or the length of the password.
var/input_max_len_is_pass = FALSE
/obj/machinery/puzzle/password/get_save_vars()
return ..() + list(NAMEOF(src, password), NAMEOF(src, tgui_text), NAMEOF(src, tgui_title), NAMEOF(src, input_max_len_is_pass))
/obj/machinery/puzzle/password/interact(mob/user, list/modifiers)
if(used && single_use)
return
if(!user.can_perform_action(src, ALLOW_SILICON_REACH) || !user.can_interact_with(src))
return
var/pass_input = tgui_input_text(user, tgui_text, tgui_title, max_length = input_max_len_is_pass ? length(password) : MAX_NAME_LEN)
if(isnull(pass_input) || !user.can_perform_action(src, ALLOW_SILICON_REACH) || !user.can_interact_with(src))
return
var/correct = pass_input == password
balloon_alert_to_viewers("[correct ? "correct" : "wrong"] password[correct ? "" : "!"]")
if(!correct)
playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 45, TRUE)
return
used = single_use
update_icon_state()
playsound(src, 'sound/machines/terminal/terminal_button07.ogg', 45, TRUE)
on_puzzle_complete()
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/password, 32)
/obj/machinery/puzzle/password/pin
desc = "A panel that controls something nearby. This one requires a PIN password, so let's start by typing in 1234..."
tgui_text = "Please enter the PIN code."
tgui_title = "What's the PIN code?"
input_max_len_is_pass = TRUE
///The length of the PIN. Suggestion: something between 4 and 12.
var/pin_length = 6
///associate a color to each digit that may be found in the password.
var/list/digit_to_color = list()
/obj/machinery/puzzle/password/pin/get_save_vars()
return ..() + NAMEOF(src, pin_length)
/obj/machinery/puzzle/password/pin/Initialize(mapload)
. = ..()
for(var/iteration in 1 to pin_length)
password += "[rand(1, 9)]"
var/list/possible_colors = list(
"white",
"black",
"red",
"green",
"blue",
"yellow",
COLOR_ORANGE, // orange is also not valid
COLOR_BROWN, // brown is NOT a valid byond color
"gray",
"purple",
)
for(var/digit in 0 to 9)
digit_to_color["[digit]"] = pick_n_take(possible_colors)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/password/pin, 32)
//
// blockade
//
///blockades destroy themselves if they receive COMSIG_GLOB_PUZZLE_COMPLETED with their ID
/obj/structure/puzzle_blockade
name = "shield gate"
desc = "A wall of solid light, likely defending something important. Virtually indestructible, must be a way around, or to disable it."
icon = 'icons/effects/effects.dmi'
icon_state = "wave2"
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
move_resist = MOVE_FORCE_OVERPOWERING
opacity = FALSE
density = TRUE
anchored = TRUE
/// if we receive a puzzle signal with this id we get destroyed
var/id
/obj/structure/puzzle_blockade/get_save_vars()
return ..() + NAMEOF(src, id)
/obj/structure/puzzle_blockade/Initialize(mapload)
. = ..()
if(!isnull(id))
SSqueuelinks.add_to_queue(src, id)
/obj/structure/puzzle_blockade/MatchedLinks(id, list/partners)
for(var/partner in partners)
RegisterSignal(partner, COMSIG_PUZZLE_COMPLETED, PROC_REF(try_signal))
/obj/structure/puzzle_blockade/proc/try_signal(datum/source)
SIGNAL_HANDLER
playsound(src, SFX_SPARKS, 100, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
do_sparks(3, cardinal_only = FALSE, source = src)
qdel(src)
/obj/structure/puzzle_blockade/oneway
name = "one-way gate"
desc = "A wall of solid light, likely defending something important. Virtually indestructible."
icon = 'icons/obj/structures.dmi'
icon_state = "oneway"
base_icon_state = "oneway"
light_color = COLOR_BIOLUMINESCENCE_BLUE
light_range = 1
density = FALSE
/obj/structure/puzzle_blockade/oneway/update_icon_state()
icon_state = "[base_icon_state][density ? "" : "-off"]"
return ..()
/obj/structure/puzzle_blockade/oneway/CanAllowThrough(atom/movable/mover, border_dir)
return ..() && (REVERSE_DIR(border_dir) == dir || get_turf(mover) == get_turf(src))
/obj/structure/puzzle_blockade/oneway/CanAStarPass(border_dir, datum/can_pass_info/pass_info)
return REVERSE_DIR(border_dir) == dir
/obj/structure/puzzle_blockade/oneway/try_signal(datum/source)
density = FALSE
update_appearance(UPDATE_ICON)
/obj/effect/puzzle_poddoor_open
name = "puzzle-poddoor relay"
desc = "activates poddoors if activated with a puzzle signal."
icon = 'icons/effects/mapping_helpers.dmi'
icon_state = ""
anchored = TRUE
invisibility = INVISIBILITY_MAXIMUM
/// if we receive a puzzle signal with this we do our thing
var/queue_id
/// door id
var/id
/obj/effect/puzzle_poddoor_open/get_save_vars()
return ..() + list(NAMEOF(src, queue_id), NAMEOF(src, id))
/obj/effect/puzzle_poddoor_open/Initialize(mapload)
. = ..()
if(isnull(id) || isnull(queue_id))
log_mapping("[src] id:[id] has no id or door id and has been deleted")
return INITIALIZE_HINT_QDEL
SSqueuelinks.add_to_queue(src, queue_id)
/obj/effect/puzzle_poddoor_open/MatchedLinks(id, list/partners)
for(var/partner in partners)
RegisterSignal(partner, COMSIG_PUZZLE_COMPLETED, PROC_REF(try_signal))
/obj/effect/puzzle_poddoor_open/proc/try_signal(datum/source)
SIGNAL_HANDLER
var/openclose
for(var/obj/machinery/door/poddoor/door as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor))
if(door.id != id)
continue
if(isnull(openclose))
openclose = door.density
INVOKE_ASYNC(door, openclose ? TYPE_PROC_REF(/obj/machinery/door/poddoor, open) : TYPE_PROC_REF(/obj/machinery/door/poddoor, close))
#define MAX_PUZZLE_DOTS_PER_ROW 4
#define PUZZLE_DOTS_VERTICAL_OFFSET 7
#define PUZZLE_DOTS_HORIZONTAL_OFFSET 7
///A dotted board that can be used as clue for PIN puzzle machinery
/obj/effect/decal/puzzle_dots
name = "dotted board"
desc = "A board filled with colored dots. What could this mean?"
icon = 'icons/obj/fluff/puzzle_small.dmi'
icon_state = "puzzle_dots"
layer = ABOVE_NORMAL_TURF_LAYER
plane = GAME_PLANE //visible over walls
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | UNACIDABLE | LAVA_PROOF
flags_1 = UNPAINTABLE_1
///The id of the puzzle we're linked to.
var/id
/obj/effect/decal/puzzle_dots/get_save_vars()
return ..() + NAMEOF(src, id)
/obj/effect/decal/puzzle_dots/Initialize(mapload)
. = ..()
if(id)
SSqueuelinks.add_to_queue(src, id)
/obj/effect/decal/puzzle_dots/MatchedLinks(id, partners)
var/obj/machinery/puzzle/password/pin/pad = locate() in partners
var/list/pass_digits = splittext(pad.password, "")
var/pass_len = length(pass_digits)
var/extra_rows = CEILING((pass_len/MAX_PUZZLE_DOTS_PER_ROW)-1, 1)
if(extra_rows)
pixel_y += round(extra_rows*(PUZZLE_DOTS_VERTICAL_OFFSET*0.5))
for(var/i in 1 to extra_rows)
var/mutable_appearance/row = mutable_appearance(icon, icon_state)
row.pixel_y = -i*PUZZLE_DOTS_VERTICAL_OFFSET
add_overlay(row)
for(var/i in 1 to pass_len)
var/mutable_appearance/colored_dot = mutable_appearance(icon, "puzzle_dot_single")
colored_dot.color = pad.digit_to_color[pass_digits[i]]
colored_dot.pixel_x = PUZZLE_DOTS_HORIZONTAL_OFFSET * ((i-1)%MAX_PUZZLE_DOTS_PER_ROW)
colored_dot.pixel_y -= CEILING((i/MAX_PUZZLE_DOTS_PER_ROW)-1, 1)*PUZZLE_DOTS_VERTICAL_OFFSET
add_overlay(colored_dot)
#undef MAX_PUZZLE_DOTS_PER_ROW
#undef PUZZLE_DOTS_VERTICAL_OFFSET
#undef PUZZLE_DOTS_HORIZONTAL_OFFSET
/obj/effect/decal/cleanable/crayon/puzzle
name = "Password character"
icon_state = "0"
///The id of the puzzle we're linked to.
var/puzzle_id
/obj/effect/decal/cleanable/crayon/puzzle/get_save_vars()
return ..() + NAMEOF(src, puzzle_id)
/obj/effect/decal/cleanable/crayon/puzzle/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null)
. = ..()
name = "number"
if(puzzle_id)
SSqueuelinks.add_to_queue(src, puzzle_id)
/obj/effect/decal/cleanable/crayon/puzzle/MatchedLinks(id, partners)
var/obj/machinery/puzzle/password/pad = locate() in partners
var/list/pass_character = splittext(pad.password, "")
var/chosen_character = icon_state
if(!findtext(chosen_character, GLOB.is_alphanumeric))
qdel(src)
return FALSE
icon_state = pick(pass_character)
if(!text2num(icon_state))
name = "letter"
desc = "A letter vandalizing the station."
return TRUE
/obj/effect/decal/cleanable/crayon/puzzle/pin
name = "PIN number"
/obj/effect/decal/cleanable/crayon/puzzle/pin/MatchedLinks(id, partners)
. = ..()
var/obj/machinery/puzzle/password/pin/pad = locate() in partners
add_atom_colour(pad.digit_to_color[icon_state], FIXED_COLOUR_PRIORITY)
/obj/item/paper/fluff/scrambled_pass
name = "gibberish note"
icon_state = "scrap"
///The ID associated to the puzzle we're part of.
var/puzzle_id
/obj/item/paper/fluff/scrambled_pass/get_save_vars()
return ..() + NAMEOF(src, puzzle_id)
/obj/item/paper/fluff/scrambled_pass/Initialize(mapload)
. = ..()
if(mapload && puzzle_id)
SSqueuelinks.add_to_queue(src, puzzle_id)
/obj/item/paper/fluff/scrambled_pass/MatchedLinks(id, partners)
var/obj/machinery/puzzle/password/pad = locate() in partners
var/scrambled_text = ""
var/list/pass_characters = splittext(pad.password, "")
for(var/i in 1 to rand(200, 300))
scrambled_text += pick(pass_characters)
add_raw_text(scrambled_text)