tgui backend

This commit is contained in:
LetterN
2021-10-28 12:51:31 +08:00
parent 853ff1d8ad
commit 49940c373e
110 changed files with 7353 additions and 259 deletions
@@ -0,0 +1,68 @@
/**
* # Light Component
*
* Emits a light of a specific brightness and colour. Requires a shell.
*/
/obj/item/circuit_component/light
display_name = "Light"
desc = "A component that emits a light of a specific brightness and colour. Requires a shell."
/// The colours of the light
var/datum/port/input/red
var/datum/port/input/green
var/datum/port/input/blue
/// The brightness
var/datum/port/input/brightness
/// Whether the light is on or not
var/datum/port/input/on
var/max_power = 5
var/min_lightness = 0.4
var/shell_light_color
/obj/item/circuit_component/light/get_ui_notices()
. = ..()
. += create_ui_notice("Maximum Brightness: [max_power]", "orange", "lightbulb")
/obj/item/circuit_component/light/Initialize()
. = ..()
red = add_input_port("Red", PORT_TYPE_NUMBER)
green = add_input_port("Green", PORT_TYPE_NUMBER)
blue = add_input_port("Blue", PORT_TYPE_NUMBER)
brightness = add_input_port("Brightness", PORT_TYPE_NUMBER)
on = add_input_port("On", PORT_TYPE_NUMBER)
/obj/item/circuit_component/light/register_shell(atom/movable/shell)
. = ..()
TRIGGER_CIRCUIT_COMPONENT(src, null)
/obj/item/circuit_component/light/unregister_shell(atom/movable/shell)
shell.set_light_on(FALSE)
return ..()
/obj/item/circuit_component/light/input_received(datum/port/input/port)
. = ..()
brightness.set_value(clamp(brightness.value || 0, 0, max_power))
red.set_value(clamp(red.value, 0, 255))
blue.set_value(clamp(blue.value, 0, 255))
green.set_value(clamp(green.value, 0, 255))
var/list/hsl = rgb2hsl(red.value || 0, green.value || 0, blue.value || 0)
var/list/light_col = hsl2rgb(hsl[1], hsl[2], max(min_lightness, hsl[3]))
shell_light_color = rgb(light_col[1], light_col[2], light_col[3])
if(.)
return
if(parent.shell)
set_atom_light(parent.shell)
/obj/item/circuit_component/light/proc/set_atom_light(atom/movable/target_atom)
// Clamp anyways just for safety
var/bright_val = min(max(brightness.value || 0, 0), max_power)
target_atom.set_light_power(bright_val)
target_atom.set_light_range(bright_val)
target_atom.set_light_color(shell_light_color)
target_atom.set_light_on(!!on.value)
@@ -0,0 +1,175 @@
/**
* # Man-Machine Interface Component
*
* Allows an MMI to be inserted into a shell, allowing it to be linked up. Requires a shell.
*/
/obj/item/circuit_component/mmi
display_name = "Man-Machine Interface"
desc = "A component that allows MMI to enter shells to send output signals."
/// The message to send to the MMI in the shell.
var/datum/port/input/message
/// Sends the current MMI a message
var/datum/port/input/send
/// Ejects the current MMI
var/datum/port/input/eject
/// Called when the MMI tries moving north
var/datum/port/output/north
/// Called when the MMI tries moving east
var/datum/port/output/east
/// Called when the MMI tries moving south
var/datum/port/output/south
/// Called when the MMI tries moving west
var/datum/port/output/west
/// Returns what the MMI last clicked on.
var/datum/port/output/clicked_atom
/// Called when the MMI clicks.
var/datum/port/output/attack
/// Called when the MMI right clicks.
var/datum/port/output/secondary_attack
/// The current MMI card
var/obj/item/mmi/brain
/// Maximum length of the message that can be sent to the MMI
var/max_length = 300
/obj/item/circuit_component/mmi/Initialize()
. = ..()
message = add_input_port("Message", PORT_TYPE_STRING)
send = add_input_port("Send Message", PORT_TYPE_SIGNAL)
eject = add_input_port("Eject", PORT_TYPE_SIGNAL)
north = add_output_port("North", PORT_TYPE_SIGNAL)
east = add_output_port("East", PORT_TYPE_SIGNAL)
south = add_output_port("South", PORT_TYPE_SIGNAL)
west = add_output_port("West", PORT_TYPE_SIGNAL)
attack = add_output_port("Attack", PORT_TYPE_SIGNAL)
secondary_attack = add_output_port("Secondary Attack", PORT_TYPE_SIGNAL)
clicked_atom = add_output_port("Target Entity", PORT_TYPE_ATOM)
/obj/item/circuit_component/mmi/Destroy()
remove_current_brain()
return ..()
/obj/item/circuit_component/mmi/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(!brain)
return
if(COMPONENT_TRIGGERED_BY(eject, port))
remove_current_brain()
if(COMPONENT_TRIGGERED_BY(send, port))
if(!message.value)
return
var/msg_str = copytext(html_encode(message.value), 1, max_length)
var/mob/living/target = brain.brainmob
if(!target)
return
to_chat(target, "[span_bold("You hear a message in your ear: ")][msg_str]")
/obj/item/circuit_component/mmi/register_shell(atom/movable/shell)
. = ..()
RegisterSignal(shell, COMSIG_PARENT_ATTACKBY, .proc/handle_attack_by)
/obj/item/circuit_component/mmi/unregister_shell(atom/movable/shell)
UnregisterSignal(shell, COMSIG_PARENT_ATTACKBY)
remove_current_brain()
return ..()
/obj/item/circuit_component/mmi/proc/handle_attack_by(atom/movable/shell, obj/item/item, mob/living/attacker)
SIGNAL_HANDLER
if(istype(item, /obj/item/mmi))
var/obj/item/mmi/target_mmi = item
if(!target_mmi.brainmob)
return
add_mmi(item)
return COMPONENT_NO_AFTERATTACK
/obj/item/circuit_component/mmi/proc/add_mmi(obj/item/mmi/to_add)
remove_current_brain()
to_add.forceMove(src)
if(to_add.brainmob)
update_mmi_mob(to_add, null, to_add.brainmob)
brain = to_add
RegisterSignal(to_add, COMSIG_PARENT_QDELETING, .proc/remove_current_brain)
RegisterSignal(to_add, COMSIG_MOVABLE_MOVED, .proc/mmi_moved)
/obj/item/circuit_component/mmi/proc/mmi_moved(atom/movable/mmi)
SIGNAL_HANDLER
if(mmi.loc != src)
remove_current_brain()
/obj/item/circuit_component/mmi/proc/remove_current_brain()
SIGNAL_HANDLER
if(!brain)
return
if(brain.brainmob)
update_mmi_mob(brain, brain.brainmob)
UnregisterSignal(brain, list(
COMSIG_PARENT_QDELETING,
COMSIG_MOVABLE_MOVED
))
if(brain.loc == src)
brain.forceMove(drop_location())
brain = null
/obj/item/circuit_component/mmi/proc/update_mmi_mob(datum/source, mob/living/old_mmi, mob/living/new_mmi)
SIGNAL_HANDLER
if(old_mmi)
old_mmi.remote_control = null
UnregisterSignal(old_mmi, COMSIG_MOB_CLICKON)
if(new_mmi)
new_mmi.remote_control = src
RegisterSignal(new_mmi, COMSIG_MOB_CLICKON, .proc/handle_mmi_attack)
/obj/item/circuit_component/mmi/relaymove(mob/living/user, direct)
if(user != brain.brainmob)
return ..()
if(direct & NORTH)
north.set_output(COMPONENT_SIGNAL)
if(direct & WEST)
west.set_output(COMPONENT_SIGNAL)
if(direct & EAST)
east.set_output(COMPONENT_SIGNAL)
if(direct & SOUTH)
south.set_output(COMPONENT_SIGNAL)
return TRUE
/obj/item/circuit_component/mmi/proc/handle_mmi_attack(mob/living/source, atom/target, list/mods)
SIGNAL_HANDLER
var/list/modifiers = params2list(mods)
if(modifiers[RIGHT_CLICK])
clicked_atom.set_output(target)
secondary_attack.set_output(COMPONENT_SIGNAL)
. = COMSIG_MOB_CANCEL_CLICKON
else if(modifiers[LEFT_CLICK] && !modifiers[SHIFT_CLICK] && !modifiers[ALT_CLICK] && !modifiers[CTRL_CLICK])
clicked_atom.set_output(target)
attack.set_output(COMPONENT_SIGNAL)
. = COMSIG_MOB_CANCEL_CLICKON
/obj/item/circuit_component/mmi/add_to(obj/item/integrated_circuit/add_to)
. = ..()
if(HAS_TRAIT(add_to, TRAIT_COMPONENT_MMI))
return FALSE
ADD_TRAIT(add_to, TRAIT_COMPONENT_MMI, src)
/obj/item/circuit_component/mmi/removed_from(obj/item/integrated_circuit/removed_from)
REMOVE_TRAIT(removed_from, TRAIT_COMPONENT_MMI, src)
remove_current_brain()
return ..()
@@ -0,0 +1,113 @@
/**
* # Pathfinding component
*
* Calcualtes a path, returns a list of entities. Each entity is the next step in the path. Can be used with the direction component to move.
*/
/obj/item/circuit_component/pathfind
display_name = "Pathfinder"
desc = "When triggered, the next step to the target's location as an entity. This can be used with the direction component and the drone shell to make it move on its own. The Id Card input port is for considering ID access when pathing, it does not give the shell actual access."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
var/datum/port/input/input_X
var/datum/port/input/input_Y
var/datum/port/input/id_card
var/datum/port/output/output
var/datum/port/output/finished
var/datum/port/output/failed
var/datum/port/output/reason_failed
var/list/path
var/turf/old_dest
var/turf/next_turf
// Cooldown to limit how frequently we can path to the same location.
var/same_path_cooldown = 5 SECONDS
var/different_path_cooldown = 30 SECONDS
var/max_range = 60
/obj/item/circuit_component/pathfind/get_ui_notices()
. = ..()
// Not necessary to show the same path cooldown, since it doesn't change much for the player
. += create_ui_notice("Pathfinding Cooldown: [DisplayTimeText(different_path_cooldown)]", "orange", "stopwatch")
. += create_ui_notice("Maximum Range: [max_range] tiles", "orange", "info")
/obj/item/circuit_component/pathfind/Initialize()
. = ..()
input_X = add_input_port("Target X", PORT_TYPE_NUMBER, FALSE)
input_Y = add_input_port("Target Y", PORT_TYPE_NUMBER, FALSE)
id_card = add_input_port("ID Card", PORT_TYPE_ATOM, FALSE)
output = add_output_port("Next step", PORT_TYPE_ATOM)
finished = add_output_port("Arrived to destination", PORT_TYPE_SIGNAL)
failed = add_output_port("Failed", PORT_TYPE_SIGNAL)
reason_failed = add_output_port("Fail reason", PORT_TYPE_STRING)
/obj/item/circuit_component/pathfind/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/target_X = input_X.value
if(isnull(target_X))
return
var/target_Y = input_Y.value
if(isnull(target_Y))
return
var/atom/path_id = id_card.value
if(path_id && !istype(path_id, /obj/item/card/id))
path_id = null
failed.set_output(COMPONENT_SIGNAL)
reason_failed.set_output("Object marked is not an ID! Using no ID instead.")
// Get both the current turf and the destination's turf
var/turf/current_turf = get_turf(src)
var/turf/destination = locate(target_X, target_Y, current_turf?.z)
// We're already here! No need to do anything.
if(current_turf == destination)
finished.set_output(COMPONENT_SIGNAL)
old_dest = null
TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)
next_turf = null
return
// If we're going to the same place and the cooldown hasn't subsided, we're probably on the same path as before
if (destination == old_dest && TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME))
// Check if the current turf is the same as the current turf we're supposed to be in. If so, then we set the next step as the next turf on the list
if(current_turf == next_turf)
popleft(path)
next_turf = get_turf(path[1])
output.set_output(next_turf)
// Restart the cooldown since we don't need a new path ( TIMER_COOLDOWN_START might restart the timer by itself and i dont need to call TIMER_COOLDOWN_END, but better safe than sorry )
TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME, same_path_cooldown)
else // Either we're not going to the same place or the cooldown is over. Either way, we need a new path
if(destination != old_dest && TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_PATHFIND_DIF))
failed.set_output(COMPONENT_SIGNAL)
reason_failed.set_output("Cooldown still active!")
return
TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)
old_dest = destination
path = get_path_to(src, destination, max_range, id=path_id)
if(length(path) == 0 || !path)// Check if we can even path there
next_turf = null
failed.set_output(COMPONENT_SIGNAL)
reason_failed.set_output("Can't go there!")
return
else
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_DIF, different_path_cooldown)
next_turf = get_turf(path[1])
output.set_output(next_turf)
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME, same_path_cooldown)
@@ -0,0 +1,31 @@
/**
* # Pull Component
*
* Tells the shell to start pulling on a designated atom. Only works on movable shells.
*/
/obj/item/circuit_component/pull
display_name = "Start Pulling"
desc = "A component that can force the shell to pull entities. Only works for drone shells."
/// Frequency input
var/datum/port/input/target
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/obj/item/circuit_component/pull/Initialize()
. = ..()
target = add_input_port("Target", PORT_TYPE_ATOM)
/obj/item/circuit_component/pull/input_received(datum/port/input/port)
. = ..()
if(.)
return
var/atom/target_atom = target.value
if(!target_atom)
return
var/mob/shell = parent.shell
if(!istype(shell) || get_dist(shell, target_atom) > 1 || shell.z != target_atom.z)
return
shell.start_pulling(target_atom)
@@ -0,0 +1,74 @@
#define COMP_RADIO_PUBLIC "public"
#define COMP_RADIO_PRIVATE "private"
/**
* # Radio Component
*
* Listens out for signals on the designated frequencies and sends signals on designated frequencies
*/
/obj/item/circuit_component/radio
display_name = "Radio"
desc = "A component that can listen and send frequencies. If set to private, the component will only receive signals from other components attached to circuitboards with the same owner id."
/// The publicity options. Controls whether it's public or private.
var/datum/port/input/option/public_options
/// Frequency input
var/datum/port/input/freq
/// Signal input
var/datum/port/input/code
/// Current frequency value
var/current_freq = DEFAULT_SIGNALER_CODE
var/datum/radio_frequency/radio_connection
/obj/item/circuit_component/radio/populate_options()
var/static/component_options = list(
COMP_RADIO_PUBLIC,
COMP_RADIO_PRIVATE,
)
public_options = add_option_port("Encryption Options", component_options)
/obj/item/circuit_component/radio/Initialize()
. = ..()
freq = add_input_port("Frequency", PORT_TYPE_NUMBER, default = FREQ_SIGNALER)
code = add_input_port("Code", PORT_TYPE_NUMBER, default = DEFAULT_SIGNALER_CODE)
TRIGGER_CIRCUIT_COMPONENT(src, null)
// These are cleaned up on the parent
trigger_input = add_input_port("Send", PORT_TYPE_SIGNAL)
trigger_output = add_output_port("Received", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/radio/Destroy()
SSradio.remove_object(src, current_freq)
return ..()
/obj/item/circuit_component/radio/input_received(datum/port/input/port)
. = ..()
freq.set_value(sanitize_frequency(freq.value, TRUE))
if(.)
return
var/frequency = freq.value
SSradio.remove_object(src, current_freq)
radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER)
current_freq = frequency
if(COMPONENT_TRIGGERED_BY(trigger_input, port))
var/datum/signal/signal = new(list("code" = round(code.value) || 0, "key" = parent?.owner_id))
radio_connection.post_signal(src, signal)
/obj/item/circuit_component/radio/receive_signal(datum/signal/signal)
. = FALSE
if(!signal)
return
if(signal.data["code"] != round(code.value || 0))
return
if(public_options.value == COMP_RADIO_PRIVATE && parent?.owner_id != signal.data["key"])
return
trigger_output.set_output(COMPONENT_SIGNAL)
#undef COMP_RADIO_PUBLIC
#undef COMP_RADIO_PRIVATE
@@ -0,0 +1,66 @@
/**
* # Sound Emitter Component
*
* A component that emits a sound when it receives an input.
*/
/obj/item/circuit_component/soundemitter
display_name = "Sound Emitter"
desc = "A component that emits a sound when it receives an input. The frequency is a multiplier which determines the speed at which the sound is played"
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/// Sound to play
var/datum/port/input/option/sound_file
/// Volume of the sound when played
var/datum/port/input/volume
/// Frequency of the sound when played
var/datum/port/input/frequency
/// The cooldown for this component of how often it can play sounds.
var/sound_cooldown = 2 SECONDS
var/list/options_map
/obj/item/circuit_component/soundemitter/get_ui_notices()
. = ..()
. += create_ui_notice("Sound Cooldown: [DisplayTimeText(sound_cooldown)]", "orange", "stopwatch")
/obj/item/circuit_component/soundemitter/Initialize()
. = ..()
volume = add_input_port("Volume", PORT_TYPE_NUMBER, default = 35)
frequency = add_input_port("Frequency", PORT_TYPE_NUMBER, default = 0)
/obj/item/circuit_component/soundemitter/populate_options()
var/static/component_options = list(
"Buzz" = 'sound/machines/buzz-sigh.ogg',
"Buzz Twice" = 'sound/machines/buzz-two.ogg',
"Chime" = 'sound/machines/chime.ogg',
"Honk" = 'sound/items/bikehorn.ogg',
"Ping" = 'sound/machines/ping.ogg',
"Sad Trombone" = 'sound/misc/sadtrombone.ogg',
"Warn" = 'sound/machines/warning-buzzer.ogg',
"Slow Clap" = 'sound/machines/slowclap.ogg',
)
sound_file = add_option_port("Sound Option", component_options)
options_map = component_options
/obj/item/circuit_component/soundemitter/input_received(datum/port/input/port)
. = ..()
volume.set_value(clamp(volume.value, 0, 100))
frequency.set_value(clamp(frequency.value, -100, 100))
if(.)
return
if(TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_SOUNDEMITTER))
return
var/sound_to_play = options_map[sound_file.value]
if(!sound_to_play)
return
playsound(src, sound_to_play, volume.value, frequency != 0, frequency = frequency.value)
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_SOUNDEMITTER, sound_cooldown)
@@ -0,0 +1,40 @@
/**
* # Speech Component
*
* Sends a message. Requires a shell.
*/
/obj/item/circuit_component/speech
display_name = "Speech"
desc = "A component that sends a message. Requires a shell."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
/// The message to send
var/datum/port/input/message
/// The cooldown for this component of how often it can send speech messages.
var/speech_cooldown = 1 SECONDS
/obj/item/circuit_component/speech/get_ui_notices()
. = ..()
. += create_ui_notice("Speech Cooldown: [DisplayTimeText(speech_cooldown)]", "orange", "stopwatch")
/obj/item/circuit_component/speech/Initialize()
. = ..()
message = add_input_port("Message", PORT_TYPE_STRING, FALSE)
/obj/item/circuit_component/speech/input_received(datum/port/input/port)
. = ..()
if(.)
return
if(TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_SPEECH))
return
if(message.value)
var/atom/movable/shell = parent.shell
// Prevents appear as the individual component if there is a shell.
if(shell)
shell.say(message.value)
else
say(message.value)
TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_SPEECH, speech_cooldown)