Merge remote-tracking branch 'ParadiseSS13/master' into pr/Kyep/14176

This commit is contained in:
AffectedArc07
2020-09-15 20:36:24 +01:00
672 changed files with 23471 additions and 10963 deletions
+2 -2
View File
@@ -624,11 +624,11 @@ Traitors and the like can also be revived with the previous role mostly intact.
var/beepsound = input(usr, "What sound should the announcement make?", "Announcement Sound", "") as anything in MsgSound
GLOB.command_announcement.Announce(input, customname, MsgSound[beepsound], , , type)
print_command_report(input, "[command_name()] Update")
print_command_report(input, customname)
if("No")
//same thing as the blob stuff - it's not public, so it's classified, dammit
GLOB.command_announcer.autosay("A classified message has been printed out at all communication consoles.")
print_command_report(input, "Classified [command_name()] Update")
print_command_report(input, "Classified: [customname]")
else
return
+1
View File
@@ -164,6 +164,7 @@ GLOBAL_VAR_INIT(sent_strike_team, 0)
R.set_frequency(DTH_FREQ)
R.requires_tcomms = FALSE
R.instant = TRUE
R.freqlock = TRUE
equip_to_slot_or_del(R, slot_l_ear)
if(is_leader)
equip_to_slot_or_del(new /obj/item/clothing/under/rank/centcom_officer(src), slot_w_uniform)
-136
View File
@@ -1,136 +0,0 @@
#define ALARM_RESET_DELAY 100 // How long will the alarm/trigger remain active once origin/source has been found to be gone?
/datum/alarm_source
var/source = null // The source trigger
var/source_name = "" // The name of the source should it be lost (for example a destroyed camera)
var/duration = 0 // How long this source will be alarming, 0 for indefinetely.
var/severity = 1 // How severe the alarm from this source is.
var/start_time = 0 // When this source began alarming.
var/end_time = 0 // Use to set when this trigger should clear, in case the source is lost.
/datum/alarm_source/New(var/atom/source)
src.source = source
start_time = world.time
source_name = source.get_source_name()
/datum/alarm
var/atom/origin //Used to identify the alarm area.
var/list/sources = new() //List of sources triggering the alarm. Used to determine when the alarm should be cleared.
var/list/sources_assoc = new() //Associative list of source triggers. Used to efficiently acquire the alarm source.
var/list/cameras //List of cameras that can be switched to, if the player has that capability.
var/area/last_area //The last acquired area, used should origin be lost (for example a destroyed borg containing an alarming camera).
var/area/last_name //The last acquired name, used should origin be lost
var/area/last_camera_area //The last area in which cameras where fetched, used to see if the camera list should be updated.
var/end_time //Used to set when this alarm should clear, in case the origin is lost.
/datum/alarm/New(var/atom/origin, var/atom/source, var/duration, var/severity)
src.origin = origin
cameras() // Sets up both cameras and last alarm area.
set_source_data(source, duration, severity)
/datum/alarm/process()
// Has origin gone missing?
if(!origin && !end_time)
end_time = world.time + ALARM_RESET_DELAY
for(var/datum/alarm_source/AS in sources)
// Has the alarm passed its best before date?
if((AS.end_time && world.time > AS.end_time) || (AS.duration && world.time > (AS.start_time + AS.duration)))
sources -= AS
// Has the source gone missing? Then reset the normal duration and set end_time
if(!AS.source && !AS.end_time) // end_time is used instead of duration to ensure the reset doesn't remain in the future indefinetely.
AS.duration = 0
AS.end_time = world.time + ALARM_RESET_DELAY
/datum/alarm/proc/set_source_data(var/atom/source, var/duration, var/severity)
var/datum/alarm_source/AS = sources_assoc[source]
if(!AS)
AS = new/datum/alarm_source(source)
sources += AS
sources_assoc[source] = AS
// Currently only non-0 durations can be altered (normal alarms VS EMP blasts)
if(AS.duration)
duration = duration SECONDS
AS.duration = duration
AS.severity = severity
/datum/alarm/proc/clear(var/source)
var/datum/alarm_source/AS = sources_assoc[source]
sources -= AS
sources_assoc -= source
/datum/alarm/proc/alarm_area()
if(!origin)
return last_area
last_area = origin.get_alarm_area()
return last_area
/datum/alarm/proc/alarm_name()
if(!origin)
return last_name
last_name = origin.get_alarm_name()
return last_name
/datum/alarm/proc/cameras()
// If the alarm origin has changed area, for example a borg containing an alarming camera, reset the list of cameras
if(cameras && (last_camera_area != alarm_area()))
cameras = null
// The list of cameras is also reset by /proc/invalidateCameraCache()
if(!cameras)
cameras = origin ? origin.get_alarm_cameras() : last_area.get_alarm_cameras()
last_camera_area = last_area
return cameras
/datum/alarm/proc/max_severity()
var/max_severity = 0
for(var/datum/alarm_source/AS in sources)
max_severity = max(AS.severity, max_severity)
return max_severity
/******************
* Assisting procs *
******************/
/atom/proc/get_alarm_area()
var/area/A = get_area(src)
return A
/area/get_alarm_area()
return src
/atom/proc/get_alarm_name()
var/area/A = get_area(src)
return A.name
/area/get_alarm_name()
return name
/mob/get_alarm_name()
return name
/atom/proc/get_source_name()
return name
/obj/machinery/camera/get_source_name()
return c_tag
/atom/proc/get_alarm_cameras()
var/area/A = get_area(src)
return A.get_cameras()
/area/get_alarm_cameras()
return get_cameras()
/mob/living/silicon/robot/get_alarm_cameras()
var/list/cameras = ..()
if(camera)
cameras += camera
return cameras
/mob/living/silicon/robot/syndicate/get_alarm_cameras()
return list()
-103
View File
@@ -1,103 +0,0 @@
#define ALARM_RAISED 1
#define ALARM_CLEARED 0
/datum/alarm_handler
var/category = ""
var/list/datum/alarm/alarms = new // All alarms, to handle cases when an origin has been deleted with one or more active alarms
var/list/datum/alarm/alarms_assoc = new // Associative list of alarms, to efficiently acquire them based on origin.
var/list/listeners = new // A list of all objects interested in alarm changes.
/datum/alarm_handler/process()
for(var/datum/alarm/A in alarms)
A.process()
check_alarm_cleared(A)
/datum/alarm_handler/proc/triggerAlarm(var/atom/origin, var/atom/source, var/duration = 0, var/severity = 1)
var/new_alarm
//Proper origin and source mandatory
if(!(origin && source))
return
origin = origin.get_alarm_origin()
new_alarm = 0
//see if there is already an alarm of this origin
var/datum/alarm/existing = alarms_assoc[origin]
if(existing)
existing.set_source_data(source, duration, severity)
else
existing = new/datum/alarm(origin, source, duration, severity)
new_alarm = 1
alarms |= existing
alarms_assoc[origin] = existing
if(new_alarm)
alarms = dd_sortedObjectList(alarms)
on_alarm_change(existing, ALARM_RAISED)
return new_alarm
/datum/alarm_handler/proc/clearAlarm(var/atom/origin, var/source)
//Proper origin and source mandatory
if(!(origin && source))
return
origin = origin.get_alarm_origin()
var/datum/alarm/existing = alarms_assoc[origin]
if(existing)
existing.clear(source)
return check_alarm_cleared(existing)
/datum/alarm_handler/proc/has_major_alarms()
if(alarms && alarms.len)
return 1
return 0
/datum/alarm_handler/proc/major_alarms()
return alarms
/datum/alarm_handler/proc/minor_alarms()
return alarms
/datum/alarm_handler/proc/check_alarm_cleared(var/datum/alarm/alarm)
if((alarm.end_time && world.time > alarm.end_time) || !alarm.sources.len)
alarms -= alarm
alarms_assoc -= alarm.origin
on_alarm_change(alarm, ALARM_CLEARED)
return 1
return 0
/datum/alarm_handler/proc/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
for(var/obj/machinery/camera/C in alarm.cameras())
if(was_raised)
C.network.Add(category)
else
C.network.Remove(category)
notify_listeners(alarm, was_raised)
/datum/alarm_handler/proc/get_alarm_severity_for_origin(var/atom/origin)
if(!origin)
return
origin = origin.get_alarm_origin()
var/datum/alarm/existing = alarms_assoc[origin]
if(!existing)
return
return existing.max_severity()
/atom/proc/get_alarm_origin()
return src
/turf/get_alarm_origin()
var/area/area = get_area(src)
return area // Very important to get area.master, as dynamic lightning can and will split areas.
/datum/alarm_handler/proc/register(var/object, var/procName)
listeners[object] = procName
/datum/alarm_handler/proc/unregister(var/object)
listeners -= object
/datum/alarm_handler/proc/notify_listeners(var/alarm, var/was_raised)
for(var/listener in listeners)
call(listener, listeners[listener])(src, alarm, was_raised)
-19
View File
@@ -1,19 +0,0 @@
/datum/alarm_handler/atmosphere
category = "Atmosphere Alarms"
/datum/alarm_handler/atmosphere/triggerAlarm(var/atom/origin, var/atom/source, var/duration = 0, var/severity = 1)
..()
/datum/alarm_handler/atmosphere/major_alarms()
var/list/major_alarms = new()
for(var/datum/alarm/A in alarms)
if(A.max_severity() > 1)
major_alarms.Add(A)
return major_alarms
/datum/alarm_handler/atmosphere/minor_alarms()
var/list/minor_alarms = new()
for(var/datum/alarm/A in alarms)
if(A.max_severity() == 1)
minor_alarms.Add(A)
return minor_alarms
-2
View File
@@ -1,2 +0,0 @@
/datum/alarm_handler/burglar
category = "Burglar Alarms"
-2
View File
@@ -1,2 +0,0 @@
/datum/alarm_handler/camera
category = "Camera Alarms"
-11
View File
@@ -1,11 +0,0 @@
/datum/alarm_handler/fire
category = "Fire Alarms"
/datum/alarm_handler/fire/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
var/area/A = alarm.origin
if(istype(A))
if(was_raised)
A.fire_alert()
else
A.fire_reset()
..()
-2
View File
@@ -1,2 +0,0 @@
/datum/alarm_handler/motion
category = "Motion Alarms"
-10
View File
@@ -1,10 +0,0 @@
/datum/alarm_handler/power
category = "Power Alarms"
/datum/alarm_handler/power/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
var/area/A = alarm.origin
if(istype(A))
A.power_alert(was_raised)
..()
/area/proc/power_alert(var/alarming)
+11 -10
View File
@@ -1,3 +1,9 @@
#define WIRE_RECEIVE (1<<0) //Allows pulse(0) to call Activate()
#define WIRE_PULSE (1<<1) //Allows pulse(0) to act on the holder
#define WIRE_PULSE_SPECIAL (1<<2) //Allows pulse(0) to act on the holders special assembly
#define WIRE_RADIO_RECEIVE (1<<3) //Allows pulse(1) to call Activate()
#define WIRE_RADIO_PULSE (1<<4) //Allows pulse(1) to send a radio message
/obj/item/assembly
name = "assembly"
desc = "A small electronic device that should never exist."
@@ -22,21 +28,12 @@
var/wires = WIRE_RECEIVE | WIRE_PULSE
var/datum/wires/connected = null // currently only used by timer/signaler
var/const/WIRE_RECEIVE = 1 //Allows Pulsed(0) to call Activate()
var/const/WIRE_PULSE = 2 //Allows Pulse(0) to act on the holder
var/const/WIRE_PULSE_SPECIAL = 4 //Allows Pulse(0) to act on the holders special assembly
var/const/WIRE_RADIO_RECEIVE = 8 //Allows Pulsed(1) to call Activate()
var/const/WIRE_RADIO_PULSE = 16 //Allows Pulse(1) to send a radio message
/obj/item/assembly/proc/activate() //What the device does when turned on
return
/obj/item/assembly/proc/pulsed(radio = FALSE) //Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
return
/obj/item/assembly/proc/pulse(radio = FALSE) //Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct
return
/obj/item/assembly/proc/toggle_secure() //Code that has to happen when the assembly is un\secured goes here
return
@@ -79,7 +76,11 @@
activate()
return TRUE
/obj/item/assembly/pulse(radio = FALSE)
//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 && wires)
connected.pulse_assembly(src)
return TRUE
if(holder && (wires & WIRE_PULSE))
holder.process_activation(src, 1, 0)
if(holder && (wires & WIRE_PULSE_SPECIAL))
-6
View File
@@ -128,12 +128,6 @@
if(usr)
GLOB.lastsignalers.Add("[time] <B>:</B> [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) <B>:</B> [format_frequency(frequency)]/[code]")
/obj/item/assembly/signaler/pulse(var/radio = FALSE)
if(connected && wires)
connected.Pulse(src)
else
return ..(radio)
/obj/item/assembly/signaler/receive_signal(datum/signal/signal)
if(!receiving || !signal)
return FALSE
+12 -5
View File
@@ -16,7 +16,9 @@
var/death = TRUE //Kill the mob
var/roundstart = TRUE //fires on initialize
var/instant = FALSE //fires on New
var/flavour_text = "The mapper forgot to set this!"
var/flavour_text = "" //flavour/fluff about the role, optional.
var/description = "A description for this has not been set. This is either an oversight or an admin-spawned spawner not in normal use." //intended as OOC info about the role
var/important_info = "" //important info such as rules that apply to you, etc. Optional.
var/faction = null
var/permanent = FALSE //If true, the spawner will not disappear upon running out of uses.
var/random = FALSE //Don't set a name or gender, just go random
@@ -336,7 +338,7 @@
name = "sleeper"
icon = 'icons/obj/cryogenic2.dmi'
icon_state = "sleeper"
flavour_text = "<span class='big bold'>You are a space doctor!</span>"
flavour_text = "You are a space doctor!"
assignedrole = "Space Doctor"
/obj/effect/mob_spawn/human/doctor/alive/equip(mob/living/carbon/human/H)
@@ -470,11 +472,14 @@
name = "bartender sleeper"
icon = 'icons/obj/cryogenic2.dmi'
icon_state = "sleeper"
flavour_text = "<span class='big bold'>You are a space bartender!</span><b> Time to mix drinks and change lives.</b>"
description = "Stuck on Lavaland, you could try getting back to civilisation...or serve drinks to those that wander by."
flavour_text = "You are a space bartender! Time to mix drinks and change lives. Wait, where did your bar just get transported to?"
assignedrole = "Space Bartender"
/obj/effect/mob_spawn/human/beach/alive/lifeguard
flavour_text = "<span class='big bold'>You're a spunky lifeguard!</span><b> It's up to you to make sure nobody drowns or gets eaten by sharks and stuff.</b>"
flavour_text = "You're a spunky lifeguard! It's up to you to make sure nobody drowns or gets eaten by sharks and stuff. Then suddenly your entire beach was transported to this strange hell.\
You aren't trained for this, but you'll still keep your guests alive!"
description = "Try to survive on lavaland with the pitiful equipment of a lifeguard. Or hide in your biodome."
mob_gender = "female"
name = "lifeguard sleeper"
id_job = "Lifeguard"
@@ -502,7 +507,8 @@
name = "beach bum sleeper"
icon = 'icons/obj/cryogenic2.dmi'
icon_state = "sleeper"
flavour_text = "You are a beach bum!"
flavour_text = "You are a beach bum! You think something just happened to the beach but you don't really pay too much attention."
description = "Try to survive on lavaland or just enjoy the beach, waiting for visitors."
assignedrole = "Beach Bum"
/datum/outfit/beachbum
@@ -523,6 +529,7 @@
roundstart = FALSE
icon = 'icons/effects/blood.dmi'
icon_state = "remains"
description = "Be a spooky scary skeleton." //not mapped in anywhere so admin spawner, who knows what they'll use this for.
flavour_text = "By unknown powers, your skeletal remains have been reanimated! Walk this mortal plain and terrorize all living adventurers who dare cross your path."
assignedrole = "Skeleton"
@@ -64,9 +64,11 @@
var/has_owner = FALSE
var/can_transfer = TRUE //if golems can switch bodies to this new shell
var/mob/living/owner = null //golem's owner if it has one
flavour_text = "<span class='big bold'>You are a Free Golem.</span><b> Your family worships <span class='danger'>The Liberator</span>. In his infinite and divine wisdom, he set your clan free to \
important_info = "You are not an antag. Do not mess with the station or create AIs."
description = "As a Free Golem on lavaland, you are unable to use most weapons, but you can mine, research and make more of your kind. Earn enough mining points and you can even move your shuttle out of there."
flavour_text = "You are a Free Golem. Your family worships The Liberator. In his infinite and divine wisdom, he set your clan free to \
travel the stars with a single declaration: \"Yeah go do whatever.\" Though you are bound to the one who created you, it is customary in your society to repeat those same words to newborn \
golems, so that no golem may ever be forced to serve again.</b>"
golems, so that no golem may ever be forced to serve again."
/obj/effect/mob_spawn/human/golem/Initialize(mapload, datum/species/golem/species = null, mob/creator = null)
if(species) //spawners list uses object name to register so this goes before ..()
@@ -77,8 +79,10 @@
if(!mapload && A)
notify_ghosts("\A [initial(species.prefix)] golem shell has been completed in [A.name].", source = src)
if(has_owner && creator)
flavour_text = "<span class='big bold'>You are a Golem.</span><b> You move slowly, but are highly resistant to heat and cold as well as blunt trauma. You are unable to wear clothes, but can still use most tools. \
Serve [creator], and assist [creator.p_them()] in completing [creator.p_their()] goals at any cost.</b>"
important_info = "Serve your creator, even if they are an antag."
flavour_text = "You are a golem created to serve your creator."
description = "You are a Golem. You move slowly, but are highly resistant to heat and cold as well as blunt trauma. You are unable to wear clothes, but can still use most tools. \
Serve [creator], and assist [creator.p_them()] in completing [creator.p_their()] goals at any cost."
owner = creator
/obj/effect/mob_spawn/human/golem/special(mob/living/new_spawn, name)
@@ -10,10 +10,11 @@
death = FALSE
random = TRUE
mob_species = /datum/species/human
flavour_text = "<span class='big bold'>You are a security officer working for Nanotrasen,</span><b> stationed onboard a state of the art research station. You vaguely recall rushing into a \
description = "Work as a team with your fellow survivors aboard a ruined, ancient space station."
important_info = ""
flavour_text = "You are a security officer working for Nanotrasen, stationed onboard a state of the art research station. You vaguely recall rushing into a \
cryogenics pod due to an oncoming radiation storm. The last thing you remember is the station's Artificial Program telling you that you would only be asleep for eight hours. As you open \
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod. \
Work as a team with your fellow survivors and do not abandon them.</b>"
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod."
uniform = /obj/item/clothing/under/retro/security
shoes = /obj/item/clothing/shoes/jackboots
id = /obj/item/card/id/away/old/sec
@@ -35,10 +36,11 @@
death = FALSE
random = TRUE
mob_species = /datum/species/human
flavour_text = "<span class='big bold'>You are a medical working for Nanotrasen,</span><b> stationed onboard a state of the art research station. You vaguely recall rushing into a \
description = "Work as a team with your fellow survivors aboard a ruined, ancient space station."
important_info = ""
flavour_text = "You are a medical doctor working for Nanotrasen, stationed onboard a state of the art research station. You vaguely recall rushing into a \
cryogenics pod due to an oncoming radiation storm. The last thing you remember is the station's Artificial Program telling you that you would only be asleep for eight hours. As you open \
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod. \
Work as a team with your fellow survivors and do not abandon them.</b>"
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod."
uniform = /obj/item/clothing/under/retro/medical
shoes = /obj/item/clothing/shoes/black
id = /obj/item/card/id/away/old/med
@@ -60,10 +62,11 @@
death = FALSE
random = TRUE
mob_species = /datum/species/human
flavour_text = "<span class='big bold'>You are an engineer working for Nanotrasen,</span><b> stationed onboard a state of the art research station. You vaguely recall rushing into a \
description = "Work as a team with your fellow survivors aboard a ruined, ancient space station."
important_info = ""
flavour_text = "You are an engineer working for Nanotrasen, stationed onboard a state of the art research station. You vaguely recall rushing into a \
cryogenics pod due to an oncoming radiation storm. The last thing you remember is the station's Artificial Program telling you that you would only be asleep for eight hours. As you open \
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod. \
Work as a team with your fellow survivors and do not abandon them.</b>"
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod."
uniform = /obj/item/clothing/under/retro/engineering
shoes = /obj/item/clothing/shoes/workboots
id = /obj/item/card/id/away/old/eng
@@ -85,10 +88,11 @@
death = FALSE
random = TRUE
mob_species = /datum/species/human
flavour_text = "<span class='big bold'>You are a scientist working for Nanotrasen,</span><b> stationed onboard a state of the art research station. You vaguely recall rushing into a \
description = "Work as a team with your fellow survivors aboard a ruined, ancient space station."
important_info = ""
flavour_text = "You are a scientist working for Nanotrasen, stationed onboard a state of the art research station. You vaguely recall rushing into a \
cryogenics pod due to an oncoming radiation storm. The last thing you remember is the station's Artificial Program telling you that you would only be asleep for eight hours. As you open \
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod. \
Work as a team with your fellow survivors and do not abandon them.</b>"
your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod."
uniform = /obj/item/clothing/under/retro/science
shoes = /obj/item/clothing/shoes/laceup
id = /obj/item/card/id/away/old/sci
+14
View File
@@ -309,6 +309,20 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/chem_master/send(client)
send_asset_list(client, assets, verify)
//Cloning pod sprites for UIs
/datum/asset/cloning
var/assets = list()
var/verify = FALSE
/datum/asset/cloning/register()
assets["pod_idle.gif"] = icon('icons/obj/cloning.dmi', "pod_idle")
assets["pod_cloning.gif"] = icon('icons/obj/cloning.dmi', "pod_cloning")
assets["pod_mess.gif"] = icon('icons/obj/cloning.dmi', "pod_mess")
for(var/asset_name in assets)
register_asset(asset_name, assets[asset_name])
/datum/asset/cloning/send(client)
send_asset_list(client, assets, verify)
//Pipe sprites for UIs
/datum/asset/rpd
@@ -166,7 +166,3 @@
/datum/gear/hat/flowerpin
display_name = "hair flower"
path = /obj/item/clothing/head/hairflower
/datum/gear/hat/kitty
display_name = "kitty headband"
path = /obj/item/clothing/head/kitty
-18
View File
@@ -8,21 +8,3 @@
strip_delay = 15
put_on_delay = 25
resistance_flags = FLAMMABLE
/obj/item/clothing/ears/headphones
name = "headphones"
desc = "Unce unce unce unce."
var/on = 0
icon_state = "headphones0"
item_state = null
actions_types = list(/datum/action/item_action/toggle_headphones)
/obj/item/clothing/ears/headphones/attack_self(mob/user)
on = !on
icon_state = "headphones[on]"
for(var/X in actions)
var/datum/action/A = X
A.UpdateButtonIcon()
user.update_inv_ears()
+2 -1
View File
@@ -11,7 +11,8 @@
resistance_flags = NONE
armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
sprite_sheets = list(
"Vox" = 'icons/mob/species/vox/suit.dmi'
"Vox" = 'icons/mob/species/vox/suit.dmi',
"Grey" = 'icons/mob/species/grey/suit.dmi'
)
w_class = WEIGHT_CLASS_NORMAL
+2 -2
View File
@@ -162,7 +162,7 @@
desc = "A slick, authoritative cloak designed for the Chief Engineer."
icon_state = "cemantle"
item_state = "cemantle"
allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/t_scanner, /obj/item/rcd)
allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/t_scanner, /obj/item/rcd, /obj/item/rpd)
//Chief Medical Officer
/obj/item/clothing/suit/mantle/labcoat/chief_medical_officer
@@ -231,7 +231,7 @@
icon_state = "hazard"
item_state = "hazard"
blood_overlay_type = "armor"
allowed = list (/obj/item/flashlight, /obj/item/t_scanner, /obj/item/tank/emergency_oxygen)
allowed = list (/obj/item/flashlight, /obj/item/t_scanner, /obj/item/tank/emergency_oxygen, /obj/item/rcd, /obj/item/rpd)
resistance_flags = NONE
sprite_sheets = list(
+1 -1
View File
@@ -357,7 +357,7 @@
if(!fail_msg)
to_chat(usr, "<span class='notice'>[TR.name] constructed.</span>")
if(TR.alert_admins_on_craft)
message_admins("[usr.ckey] has created a [TR.name] at [ADMIN_COORDJMP(usr)]")
message_admins("[key_name_admin(usr)] has created a [TR.name] at [ADMIN_COORDJMP(usr)]")
else
to_chat(usr, "<span class ='warning'>Construction failed[fail_msg]</span>")
busy = FALSE
+4 -4
View File
@@ -46,10 +46,10 @@
if(prob(APC_BREAK_PROBABILITY))
// if it has internal wires, cut the power wires
if(C.wires)
if(!C.wires.IsIndexCut(APC_WIRE_MAIN_POWER1))
C.wires.CutWireIndex(APC_WIRE_MAIN_POWER1)
if(!C.wires.IsIndexCut(APC_WIRE_MAIN_POWER2))
C.wires.CutWireIndex(APC_WIRE_MAIN_POWER2)
if(!C.wires.is_cut(WIRE_MAIN_POWER1))
C.wires.cut(WIRE_MAIN_POWER1)
if(!C.wires.is_cut(WIRE_MAIN_POWER2))
C.wires.cut(WIRE_MAIN_POWER2)
// if it was operating, toggle off the breaker
if(C.operating)
C.toggle_breaker()
+1 -1
View File
@@ -32,7 +32,7 @@
log_debug("Original brand intelligence machine: [originMachine] ([ADMIN_VV(originMachine,"VV")]) [ADMIN_JMP(originMachine)]")
/datum/event/brand_intelligence/tick()
if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.IsAllCut()) //if the original vending machine is missing or has it's voice switch flipped
if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut()) //if the original vending machine is missing or has it's voice switch flipped
for(var/obj/machinery/vending/saved in infectedMachines)
saved.shoot_inventory = 0
if(originMachine)
+1 -1
View File
@@ -39,7 +39,7 @@
SA.health = SA.maxHealth
SA.del_on_death = FALSE
greet_sentient(SA)
print_command_report(sentience_report, "[command_name()] Update")
print_command_report(sentience_report, "[command_name()] Update", FALSE)
processing = TRUE // Let it naturally end, if it runs successfully
/datum/event/sentience/proc/greet_sentient(var/mob/living/carbon/human/M)
@@ -7,6 +7,7 @@
materials = list(MAT_GLASS=100)
var/light_intensity = 2
light_color = LIGHT_COLOR_LIGHTBLUE
resistance_flags = FLAMMABLE
/obj/item/reagent_containers/food/drinks/drinkingglass/shotglass/on_reagent_change()
if(!isShotFlammable() && (resistance_flags & ON_FIRE))
@@ -30,6 +31,7 @@
overlays += filling
name = "shot glass of " + reagents.get_master_reagent_name() //No matter what, the glass will tell you the reagent's name. Might be too abusable in the future.
if(resistance_flags & ON_FIRE)
cut_overlay(GLOB.fire_overlay, TRUE)
overlays += "shotglass_fire"
name = "flaming [name]"
else
@@ -30,7 +30,7 @@
name = "-meat"
var/subjectname = ""
var/subjectjob = null
tastes = list("tender meat" = 1)
tastes = list("salty meat" = 1)
/obj/item/reagent_containers/food/snacks/meat/slab/meatproduct
name = "meat product"
@@ -70,6 +70,7 @@
max_n_of_items = 1500 * B.rating
/obj/machinery/smartfridge/Destroy()
SStgui.close_uis(wires)
QDEL_NULL(wires)
for(var/atom/movable/A in contents)
A.forceMove(loc)
+15 -5
View File
@@ -198,10 +198,17 @@
return
return ..()
/obj/structure/beebox/crowbar_act(mob/user, obj/item/I)
. = TRUE
if(!I.use_tool(src, user, 0))
return
TOOL_ATTEMPT_DISMANTLE_MESSAGE
if(I.use_tool(src, user, 50, volume = I.tool_volume))
TOOL_DISMANTLE_SUCCESS_MESSAGE
deconstruct(disassembled = TRUE)
/obj/structure/beebox/wrench_act(mob/user, obj/item/I)
. = TRUE
if(!I.tool_use_check(user, 0))
return
default_unfasten_wrench(user, I, time = 20)
/obj/structure/beebox/attack_hand(mob/user)
@@ -261,15 +268,18 @@
visible_message("<span class='notice'>[user] removes the queen from the apiary.</span>")
queen_bee = null
/obj/structure/beebox/deconstruct(disassembled = TRUE)
new /obj/item/stack/sheet/wood(loc, 20)
/obj/structure/beebox/deconstruct(disassembled = FALSE)
var/mat_drop = 20
if(disassembled)
mat_drop = 40
new /obj/item/stack/sheet/wood(loc, mat_drop)
for(var/mob/living/simple_animal/hostile/poison/bees/B in bees)
if(B.loc == src)
B.forceMove(drop_location())
for(var/obj/item/honey_frame/HF in honey_frames)
HF.forceMove(drop_location())
honey_frames -= HF
qdel(src)
..()
/obj/structure/beebox/unwrenched
anchored = FALSE
+21 -1
View File
@@ -4,7 +4,7 @@
icon = 'icons/obj/objects.dmi'
icon_state = "barrel"
density = TRUE
anchored = FALSE
anchored = TRUE
container_type = DRAINABLE | AMOUNT_VISIBLE
pressure_resistance = 2 * ONE_ATMOSPHERE
max_integrity = 300
@@ -65,6 +65,26 @@
to_chat(user, "<span class='notice'>You close [src], letting you draw from its tap.</span>")
update_icon()
/obj/structure/fermenting_barrel/crowbar_act(mob/living/user, obj/item/I)
. = TRUE
if(!I.use_tool(src, user, 0))
return
TOOL_ATTEMPT_DISMANTLE_MESSAGE
if(I.use_tool(src, user, 50, volume = I.tool_volume))
TOOL_DISMANTLE_SUCCESS_MESSAGE
deconstruct(disassembled = TRUE)
/obj/structure/fermenting_barrel/wrench_act(mob/living/user, obj/item/I)
. = TRUE
default_unfasten_wrench(user, I, time = 20)
/obj/structure/fermenting_barrel/deconstruct(disassembled = FALSE)
var/mat_drop = 15
if(disassembled)
mat_drop = 30
new /obj/item/stack/sheet/wood(drop_location(), mat_drop)
..()
/obj/structure/fermenting_barrel/update_icon()
if(open)
icon_state = "barrel_open"
+2 -1
View File
@@ -29,7 +29,8 @@
if(istype(W,/obj/item/reagent_containers/syringe))
if(!contains_sample)
for(var/datum/reagent/blood/bloodSample in W.reagents.reagent_list)
if(bloodSample.data["mind"] && bloodSample.data["cloneable"] == 1)
var/datum/dna/dna = bloodSample.data["dna"]
if(bloodSample.data["mind"] && bloodSample.data["cloneable"] && !(NO_SCAN in dna.species.species_traits))
var/datum/mind/tempmind = bloodSample.data["mind"]
if(tempmind.is_revivable())
mind = bloodSample.data["mind"]
@@ -0,0 +1,113 @@
/**
* Get all non admin_only instruments as a list of text ids.
*/
/proc/get_allowed_instrument_ids()
. = list()
for(var/id in SSinstruments.instrument_data)
var/datum/instrument/I = SSinstruments.instrument_data[id]
if(!I.admin_only)
. += I.id
/**
* # Instrument Datums
*
* Instrument datums hold the data for any given instrument, as well as data on how to play it and what bounds there are to playing it.
*
* The datums themselves are kept in SSinstruments in a list by their unique ID. The reason it uses ID instead of typepath is to support the runtime creation of instruments.
* Since songs cache them while playing, there isn't realistic issues regarding performance from accessing.
*/
/datum/instrument
/// Name of the instrument
var/name = "Generic instrument"
/// Uniquely identifies this instrument so runtime changes are possible as opposed to paths. If this is unset, things will use path instead.
var/id
/// Category
var/category = "Unsorted"
/// Used for categorization subtypes
var/abstract_type = /datum/instrument
/// Write here however many samples, follow this syntax: "%note num%"='%sample file%' eg. "27"='synthesizer/e2.ogg'. Key must never be lower than 0 and higher than 127
var/list/real_samples
/// assoc list key = /datum/instrument_key. do not fill this yourself!
var/list/samples
/// See __DEFINES/flags/instruments.dm
var/instrument_flags = NONE
/// For legacy instruments, the path to our notes
var/legacy_instrument_path
/// For legacy instruments, our file extension
var/legacy_instrument_ext
/// What songs are using us
var/list/datum/song/songs_using = list()
/// Don't touch this
var/static/HIGHEST_KEY = 127
/// Don't touch this x2
var/static/LOWEST_KEY = 0
/// Oh no - For truly troll instruments.
var/admin_only = FALSE
/// Volume multiplier. Synthesized instruments are quite loud and I don't like to cut off potential detail via editing. (someone correct me if this isn't a thing)
var/volume_multiplier = 0.33
/datum/instrument/New()
if(isnull(id))
id = "[type]"
/datum/instrument/Destroy()
SSinstruments.instrument_data -= id
for(var/i in songs_using)
var/datum/song/S = i
S.set_instrument(null)
real_samples = null
samples = null
songs_using = null
return ..()
/**
* Initializes the instrument, calculating its samples if necessary.
*/
/datum/instrument/proc/Initialize()
if(instrument_flags & (INSTRUMENT_LEGACY | INSTRUMENT_DO_NOT_AUTOSAMPLE))
return
calculate_samples()
/**
* Checks if this instrument is ready to play.
*/
/datum/instrument/proc/is_ready()
if(instrument_flags & INSTRUMENT_LEGACY)
return legacy_instrument_path && legacy_instrument_ext
else if(instrument_flags & INSTRUMENT_DO_NOT_AUTOSAMPLE)
return length(samples)
return length(samples) >= 128
/**
* For synthesized instruments, this is how the instrument generates the "keys" that a [/datum/song] uses to play notes.
* Calculating them on the fly would be unperformant, so we do it during init and keep it all cached in a list.
*/
/datum/instrument/proc/calculate_samples()
if(!length(real_samples))
CRASH("No real samples defined for [id] [type] on calculate_samples() call.")
var/list/real_keys = list()
samples = list()
for(var/key in real_samples)
real_keys += text2num(key)
sortTim(real_keys, /proc/cmp_numeric_asc, associative = FALSE)
for(var/i in 1 to (length(real_keys) - 1))
var/from_key = real_keys[i]
var/to_key = real_keys[i + 1]
var/sample1 = real_samples[num2text(from_key)]
var/sample2 = real_samples[num2text(to_key)]
var/pivot = FLOOR((from_key + to_key) / 2, 1) //original code was a round but I replaced it because that's effectively a floor, thanks Baystation! who knows what was intended.
for(var/key in from_key to pivot)
samples[num2text(key)] = new /datum/instrument_key(sample1, key, key - from_key)
for(var/key in (pivot + 1) to to_key)
samples[num2text(key)] = new /datum/instrument_key(sample2, key, key - to_key)
// Fill in 0 to first key and last key to 127
var/first_key = real_keys[1]
var/last_key = real_keys[length(real_keys)]
var/first_sample = real_samples[num2text(first_key)]
var/last_sample = real_samples[num2text(last_key)]
for(var/key in LOWEST_KEY to (first_key - 1))
samples[num2text(key)] = new /datum/instrument_key(first_sample, key, key - first_key)
for(var/key in last_key to HIGHEST_KEY)
samples[num2text(key)] = new /datum/instrument_key(last_sample, key, key - last_key)
@@ -0,0 +1,33 @@
#define KEY_TWELTH (1/12)
/**
* Instrument key datums contain everything needed to know how to play a specific
* note of an instrument.*
*/
/datum/instrument_key
/// The numerical key of what this is, from 1 to 127 on a standard piano keyboard.
var/key
/// The actual sample file that will be loaded when playing.
var/sample
/// The frequency to play the sample to get our desired note.
var/frequency
/// Deviation up/down from the pivot point that uses its sample. Used to calculate frequency.
var/deviation
/datum/instrument_key/New(sample, key, deviation, frequency)
src.sample = sample
src.key = key
src.deviation = deviation
src.frequency = frequency
if(!frequency && deviation)
calculate()
/**
* Calculates and stores our deviation.
*/
/datum/instrument_key/proc/calculate()
if(!deviation)
CRASH("Invalid calculate call: No deviation or sample in instrument_key")
frequency = 2 ** (KEY_TWELTH * deviation)
#undef KEY_TWELTH
+26
View File
@@ -0,0 +1,26 @@
/datum/instrument/brass
name = "Generic brass instrument"
category = "Brass"
abstract_type = /datum/instrument/brass
/datum/instrument/brass/crisis_section
name = "Crisis Brass Section"
id = "crbrass"
real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg',
"48"='sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg',
"60"='sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg',
"72"='sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg')
/datum/instrument/brass/crisis_trombone
name = "Crisis Trombone"
id = "crtrombone"
real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_trombone/c2.ogg',
"48"='sound/instruments/synthesis_samples/brass/crisis_trombone/c3.ogg',
"60"='sound/instruments/synthesis_samples/brass/crisis_trombone/c4.ogg',
"72"='sound/instruments/synthesis_samples/brass/crisis_trombone/c5.ogg')
/datum/instrument/brass/crisis_trumpet
name = "Crisis Trumpet"
id = "crtrumpet"
real_samples = list("60"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c4.ogg',
"72"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c5.ogg')
@@ -0,0 +1,31 @@
/datum/instrument/chromatic
name = "Generic chromatic percussion instrument"
category = "Chromatic percussion"
abstract_type = /datum/instrument/chromatic
/datum/instrument/chromatic/vibraphone1
name = "Crisis Vibraphone"
id = "crvibr"
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg',
"48"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg',
"60"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg',
"72"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg')
/datum/instrument/chromatic/musicbox1
name = "SGM Music Box"
id = "sgmmbox"
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg',
"48"='sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg',
"60"='sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg',
"72"='sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg')
/datum/instrument/chromatic/fluid_celeste
name = "FluidR3 Celeste"
id = "r3celeste"
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c2.ogg',
"48"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c3.ogg',
"60"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c4.ogg',
"72"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c5.ogg',
"84"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c6.ogg',
"96"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c7.ogg',
"108"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c8.ogg')
+19
View File
@@ -0,0 +1,19 @@
/datum/instrument/fun
name = "Generic Fun Instrument"
category = "Fun"
abstract_type = /datum/instrument/fun
/datum/instrument/fun/honk
name = "!!HONK!!"
id = "honk"
real_samples = list("74"='sound/items/bikehorn.ogg') // Cluwne Heaven
/datum/instrument/fun/signal
name = "Ping"
id = "ping"
real_samples = list("79"='sound/machines/ping.ogg')
/datum/instrument/fun/chime
name = "Chime"
id = "chime"
real_samples = list("79"='sound/machines/chime.ogg')
+36
View File
@@ -0,0 +1,36 @@
/datum/instrument/guitar
name = "Generic guitar-like instrument"
category = "Guitar"
abstract_type = /datum/instrument/guitar
/datum/instrument/guitar/steel_crisis
name = "Crisis Steel String Guitar"
id = "csteelgt"
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg',
"48"='sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg',
"60"='sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg',
"72"='sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg')
/datum/instrument/guitar/nylon_crisis
name = "Crisis Nylon String Guitar"
id = "cnylongt"
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg',
"48"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg',
"60"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg',
"72"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg')
/datum/instrument/guitar/clean_crisis
name = "Crisis Clean Guitar"
id = "ccleangt"
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_clean/c2.ogg',
"48"='sound/instruments/synthesis_samples/guitar/crisis_clean/c3.ogg',
"60"='sound/instruments/synthesis_samples/guitar/crisis_clean/c4.ogg',
"72"='sound/instruments/synthesis_samples/guitar/crisis_clean/c5.ogg')
/datum/instrument/guitar/muted_crisis
name = "Crisis Muted Guitar"
id = "cmutedgt"
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_muted/c2.ogg',
"48"='sound/instruments/synthesis_samples/guitar/crisis_muted/c3.ogg',
"60"='sound/instruments/synthesis_samples/guitar/crisis_muted/c4.ogg',
"72"='sound/instruments/synthesis_samples/guitar/crisis_muted/c5.ogg')
+86
View File
@@ -0,0 +1,86 @@
//THESE ARE HARDCODED INSTRUMENT SAMPLES.
//SONGS WILL BE AUTOMATICALLY SWITCHED TO LEGACY MODE IF THEY USE THIS KIND OF INSTRUMENT!
//I'd prefer these stayed. They sound different from the mechanical synthesis of synthed instruments, and I quite like them that way. It's not legacy, it's hardcoded, old style. - kevinz000
/datum/instrument/hardcoded
abstract_type = /datum/instrument/hardcoded
category = "Non-Synthesized"
instrument_flags = INSTRUMENT_LEGACY
volume_multiplier = 1 //not as loud as synth'd
/datum/instrument/hardcoded/accordion
name = "Accordion"
id = "accordion"
legacy_instrument_ext = "mid"
legacy_instrument_path = "accordion"
/datum/instrument/hardcoded/bikehorn
name = "Bike Horn"
id = "bikehorn"
legacy_instrument_ext = "ogg"
legacy_instrument_path = "bikehorn"
/datum/instrument/hardcoded/eguitar
name = "Electric Guitar"
id = "eguitar"
legacy_instrument_ext = "ogg"
legacy_instrument_path = "eguitar"
/datum/instrument/hardcoded/glockenspiel
name = "Glockenspiel"
id = "glockenspiel"
legacy_instrument_ext = "mid"
legacy_instrument_path = "glockenspiel"
/datum/instrument/hardcoded/guitar
name = "Guitar"
id = "guitar"
legacy_instrument_ext = "ogg"
legacy_instrument_path = "guitar"
/datum/instrument/hardcoded/harmonica
name = "Harmonica"
id = "harmonica"
legacy_instrument_ext = "mid"
legacy_instrument_path = "harmonica"
/datum/instrument/hardcoded/piano
name = "Piano"
id = "piano"
legacy_instrument_ext = "ogg"
legacy_instrument_path = "piano"
/datum/instrument/hardcoded/recorder
name = "Recorder"
id = "recorder"
legacy_instrument_ext = "mid"
legacy_instrument_path = "recorder"
/datum/instrument/hardcoded/saxophone
name = "Saxophone"
id = "saxophone"
legacy_instrument_ext = "mid"
legacy_instrument_path = "saxophone"
/datum/instrument/hardcoded/trombone
name = "Trombone"
id = "trombone"
legacy_instrument_ext = "mid"
legacy_instrument_path = "trombone"
/datum/instrument/hardcoded/violin
name = "Violin"
id = "violin"
legacy_instrument_ext = "mid"
legacy_instrument_path = "violin"
/datum/instrument/hardcoded/xylophone
name = "Xylophone"
id = "xylophone"
legacy_instrument_ext = "mid"
legacy_instrument_path = "xylophone"
/datum/instrument/hardcoded/banjo
name = "Banjo"
id = "banjo"
legacy_instrument_ext = "ogg"
legacy_instrument_path = "banjo"
@@ -0,0 +1,53 @@
//copy pasta of the space piano, don't hurt me -Pete
/obj/item/instrument
name = "generic instrument"
force = 10
max_integrity = 100
resistance_flags = FLAMMABLE
icon = 'icons/obj/musician.dmi'
lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi'
/// Our song datum.
var/datum/song/handheld/song
/// Our allowed list of instrument ids. This is nulled on initialize.
var/list/allowed_instrument_ids
/// How far away our song datum can be heard.
var/instrument_range = 15
/obj/item/instrument/Initialize(mapload)
. = ..()
song = new(src, allowed_instrument_ids, instrument_range)
allowed_instrument_ids = null //We don't need this clogging memory after it's used.
/obj/item/instrument/Destroy()
QDEL_NULL(song)
return ..()
/obj/item/instrument/suicide_act(mob/user)
user.visible_message("<span class='suicide'>[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!</span>")
return BRUTELOSS
/obj/item/instrument/attack_self(mob/user)
tgui_interact(user)
/obj/item/instrument/tgui_data(mob/user)
return song.tgui_data(user)
/obj/item/instrument/tgui_interact(mob/user)
if(!isliving(user) || user.incapacitated())
return
song.tgui_interact(user)
/obj/item/instrument/tgui_act(action, params)
if(..())
return
return song.tgui_act(action, params)
/**
* Whether the instrument should stop playing
*
* Arguments:
* * user - The user
*/
/obj/item/instrument/proc/should_stop_playing(mob/user)
return !(src in user) || !isliving(user) || user.incapacitated()
@@ -0,0 +1,80 @@
/obj/item/clothing/ears/headphones
name = "headphones"
desc = "Unce unce unce unce."
icon_state = "headphones0"
item_state = "headphones0"
actions_types = list(/datum/action/item_action/change_headphones_song)
var/datum/song/headphones/song
/obj/item/clothing/ears/headphones/Initialize(mapload)
. = ..()
song = new(src, "piano") // Piano is the default instrument but all instruments are allowed
song.instrument_range = 0
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
// To update the icon
RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing)
RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing)
/obj/item/clothing/ears/headphones/Destroy()
QDEL_NULL(song)
return ..()
/obj/item/clothing/ears/headphones/attack_self(mob/user)
tgui_interact(user)
/obj/item/clothing/ears/headphones/tgui_data(mob/user)
return song.tgui_data(user)
/obj/item/clothing/ears/headphones/tgui_interact(mob/user)
if(should_stop_playing(user) || user.incapacitated())
return
song.tgui_interact(user)
/obj/item/clothing/ears/headphones/tgui_act(action, params)
if(..())
return
return song.tgui_act(action, params)
/obj/item/clothing/ears/headphones/update_icon()
var/mob/living/carbon/human/user = loc
if(istype(user))
user.update_action_buttons_icon()
user.update_inv_ears()
..()
/obj/item/clothing/ears/headphones/item_action_slot_check(slot)
if(slot == slot_l_ear || slot == slot_r_ear)
return TRUE
/**
* Called by a component signal when our song starts playing.
*/
/obj/item/clothing/ears/headphones/proc/start_playing()
icon_state = item_state = "headphones1"
update_icon()
/**
* Called by a component signal when our song stops playing.
*/
/obj/item/clothing/ears/headphones/proc/stop_playing()
icon_state = item_state = "headphones0"
update_icon()
/**
* Whether the headphone's song should stop playing
*
* Arguments:
* * user - The user
*/
/obj/item/clothing/ears/headphones/proc/should_stop_playing(mob/living/carbon/human/user)
return !(src in user) || !istype(user) || !((src == user.l_ear) || (src == user.r_ear))
// special subtype so it uses the correct item type
/datum/song/headphones
/datum/song/headphones/should_stop_playing(mob/user)
. = ..()
if(.)
return TRUE
var/obj/item/clothing/ears/headphones/I = parent
return I.should_stop_playing(user)
@@ -0,0 +1,216 @@
/obj/item/instrument/violin
name = "space violin"
desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
icon_state = "violin"
item_state = "violin"
hitsound = "swing_hit"
allowed_instrument_ids = "violin"
/obj/item/instrument/violin/golden
name = "golden violin"
desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
icon_state = "golden_violin"
item_state = "golden_violin"
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
/obj/item/instrument/piano_synth
name = "synthesizer"
desc = "An advanced electronic synthesizer that can be used as various instruments."
icon_state = "synth"
item_state = "synth"
allowed_instrument_ids = "piano"
/obj/item/instrument/piano_synth/Initialize(mapload)
. = ..()
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
/obj/item/instrument/banjo
name = "banjo"
desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
icon_state = "banjo"
item_state = "banjo"
attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered")
hitsound = 'sound/weapons/banjoslap.ogg'
allowed_instrument_ids = "banjo"
/obj/item/instrument/guitar
name = "guitar"
desc = "It's made of wood and has bronze strings."
icon_state = "guitar"
item_state = "guitar"
attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
hitsound = 'sound/weapons/guitarslam.ogg'
allowed_instrument_ids = "guitar"
/obj/item/instrument/eguitar
name = "electric guitar"
desc = "Makes all your shredding needs possible."
icon_state = "eguitar"
item_state = "eguitar"
force = 12
attack_verb = list("played metal on", "shredded", "crashed", "smashed")
hitsound = 'sound/weapons/stringsmash.ogg'
allowed_instrument_ids = "eguitar"
/obj/item/instrument/glockenspiel
name = "glockenspiel"
desc = "Smooth metal bars perfect for any marching band."
icon_state = "glockenspiel"
item_state = "glockenspiel"
allowed_instrument_ids = "glockenspiel"
/obj/item/instrument/accordion
name = "accordion"
desc = "Pun-Pun not included."
icon_state = "accordion"
item_state = "accordion"
allowed_instrument_ids = "accordion"
/obj/item/instrument/trumpet
name = "trumpet"
desc = "To announce the arrival of the king!"
icon_state = "trumpet"
item_state = "trumpet"
allowed_instrument_ids = "trombone"
/obj/item/instrument/trumpet/spectral
name = "spectral trumpet"
desc = "Things are about to get spooky!"
icon_state = "spectral_trumpet"
item_state = "spectral_trumpet"
force = 0
attack_verb = list("played", "jazzed", "trumpeted", "mourned", "dooted", "spooked")
/obj/item/instrument/trumpet/spectral/Initialize()
. = ..()
AddComponent(/datum/component/spooky)
/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
playsound(src, 'sound/instruments/trombone/En4.mid', 100, 1, -1)
..()
/obj/item/instrument/saxophone
name = "saxophone"
desc = "This soothing sound will be sure to leave your audience in tears."
icon_state = "saxophone"
item_state = "saxophone"
allowed_instrument_ids = "saxophone"
/obj/item/instrument/saxophone/spectral
name = "spectral saxophone"
desc = "This spooky sound will be sure to leave mortals in bones."
icon_state = "saxophone"
item_state = "saxophone"
force = 0
attack_verb = list("played", "jazzed", "saxxed", "mourned", "dooted", "spooked")
/obj/item/instrument/saxophone/spectral/Initialize()
. = ..()
AddComponent(/datum/component/spooky)
/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
playsound(src, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
..()
/obj/item/instrument/trombone
name = "trombone"
desc = "How can any pool table ever hope to compete?"
icon_state = "trombone"
allowed_instrument_ids = "trombone"
item_state = "trombone"
/obj/item/instrument/trombone/spectral
name = "spectral trombone"
desc = "A skeleton's favorite instrument. Apply directly on the mortals."
icon_state = "trombone"
item_state = "trombone"
force = 0
attack_verb = list("played", "jazzed", "tromboned", "mourned", "dooted", "spooked")
/obj/item/instrument/trombone/spectral/Initialize()
. = ..()
AddComponent(/datum/component/spooky)
/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
playsound (src, 'sound/instruments/trombone/Cn4.mid', 100,1,-1)
..()
/obj/item/instrument/recorder
name = "recorder"
desc = "Just like in school, playing ability and all."
force = 5
icon_state = "recorder"
item_state = "recorder"
allowed_instrument_ids = "recorder"
/obj/item/instrument/harmonica
name = "harmonica"
desc = "For when you get a bad case of the space blues."
icon_state = "harmonica"
item_state = "harmonica"
force = 5
w_class = WEIGHT_CLASS_SMALL
allowed_instrument_ids = "harmonica"
/obj/item/instrument/xylophone
name = "xylophone"
desc = "A percussion instrument with a bright tone."
icon_state = "xylophone"
item_state = "xylophone"
allowed_instrument_ids = "bikehorn"
/obj/item/instrument/bikehorn
name = "gilded bike horn"
desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes."
icon_state = "bike_horn"
item_state = "bike_horn"
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
attack_verb = list("beautifully honks")
w_class = WEIGHT_CLASS_TINY
force = 0
throw_speed = 3
throw_range = 7
hitsound = 'sound/items/bikehorn.ogg'
allowed_instrument_ids = "bikehorn"
// Crafting recipes
/datum/crafting_recipe/violin
name = "Violin"
result = /obj/item/instrument/violin
reqs = list(/obj/item/stack/sheet/wood = 5,
/obj/item/stack/cable_coil = 6,
/obj/item/stack/tape_roll = 5)
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
time = 8 SECONDS
category = CAT_MISC
/datum/crafting_recipe/guitar
name = "Guitar"
result = /obj/item/instrument/guitar
reqs = list(/obj/item/stack/sheet/wood = 5,
/obj/item/stack/cable_coil = 6,
/obj/item/stack/tape_roll = 5)
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
time = 8 SECONDS
category = CAT_MISC
/datum/crafting_recipe/eguitar
name = "Electric Guitar"
result = /obj/item/instrument/eguitar
reqs = list(/obj/item/stack/sheet/metal = 5,
/obj/item/stack/cable_coil = 6,
/obj/item/stack/tape_roll = 5)
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
time = 8 SECONDS
category = CAT_MISC
/datum/crafting_recipe/banjo
name = "Banjo"
result = /obj/item/instrument/banjo
reqs = list(/obj/item/stack/sheet/wood = 5,
/obj/item/stack/cable_coil = 6,
/obj/item/stack/tape_roll = 5)
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
time = 8 SECONDS
category = CAT_MISC
@@ -0,0 +1,47 @@
/obj/structure/musician
name = "Not A Piano"
desc = "Something broke!"
var/can_play_unanchored = FALSE
var/list/allowed_instrument_ids
var/datum/song/song
/obj/structure/musician/Initialize(mapload)
. = ..()
song = new(src, allowed_instrument_ids)
allowed_instrument_ids = null
/obj/structure/musician/Destroy()
QDEL_NULL(song)
return ..()
/obj/structure/musician/attack_hand(mob/user)
add_fingerprint(user)
tgui_interact(user)
/obj/structure/musician/tgui_data(mob/user)
return song.tgui_data(user)
/obj/structure/musician/tgui_interact(mob/user)
song.tgui_interact(user)
/obj/structure/musician/tgui_act(action, params)
if(..())
return
return song.tgui_act(action, params)
/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
default_unfasten_wrench(user, I, 40)
return TRUE
/**
* Whether the instrument should stop playing
*
* Arguments:
* * user - The user
*/
/obj/structure/musician/proc/should_stop_playing(mob/user)
if(!(anchored || can_play_unanchored))
return TRUE
if(!user)
return FALSE
return !CanUseTopic(user, GLOB.physical_state)
@@ -0,0 +1,22 @@
/obj/structure/piano
parent_type = /obj/structure/musician // TODO: Can't edit maps right now due to a freeze, remove and update path when it's done
name = "space minimoog"
icon = 'icons/obj/musician.dmi'
icon_state = "minimoog"
anchored = TRUE
density = TRUE
allowed_instrument_ids = "piano"
/obj/structure/piano/unanchored
anchored = FALSE
/obj/structure/piano/Initialize(mapload)
. = ..()
if(prob(50) && icon_state == initial(icon_state))
name = "space minimoog"
desc = "This is a minimoog, like a space piano, but more spacey!"
icon_state = "minimoog"
else
name = "space piano"
desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
icon_state = "piano"
+43
View File
@@ -0,0 +1,43 @@
/datum/instrument/organ
name = "Generic organ"
category = "Organ"
abstract_type = /datum/instrument/organ
/datum/instrument/organ/crisis_church
name = "Crisis Church Organ"
id = "crichugan"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg')
/datum/instrument/organ/crisis_hammond
name = "Crisis Hammond Organ"
id = "crihamgan"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg')
/datum/instrument/organ/crisis_accordian
name = "Crisis Accordion"
id = "crack"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg')
/datum/instrument/organ/crisis_harmonica
name = "Crisis Harmonica"
id = "crharmony"
real_samples = list("48"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg')
/datum/instrument/organ/crisis_tango_accordian
name = "Crisis Tango Accordion"
id = "crtango"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg')
+56
View File
@@ -0,0 +1,56 @@
/datum/instrument/piano
name = "Generic piano"
category = "Piano"
abstract_type = /datum/instrument/piano
/datum/instrument/piano/fluid_piano
name = "FluidR3 Grand Piano"
id = "r3grand"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg',
"84"='sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg',
"96"='sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg',
"108"='sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg')
/datum/instrument/piano/fluid_harpsichord
name = "FluidR3 Harpsichord"
id = "r3harpsi"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c5.ogg',
"84"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c6.ogg',
"96"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c7.ogg',
"108"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c8.ogg')
/datum/instrument/piano/crisis_harpsichord
name = "Crisis Harpsichord"
id = "crharpsi"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg')
/datum/instrument/piano/crisis_grandpiano_uni
name = "Crisis Grand Piano One"
id = "crgrand1"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg',
"84"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg',
"96"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg',
"108"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg')
/datum/instrument/piano/crisis_brightpiano_uni
name = "Crisis Bright Piano One"
id = "crbright1"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg',
"84"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg',
"96"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg',
"108"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg')
+410
View File
@@ -0,0 +1,410 @@
#define MUSICIAN_HEARCHECK_MINDELAY 4
#define MUSIC_MAXLINES 1000
#define MUSIC_MAXLINECHARS 300
/**
* # Song datum
*
* These are the actual backend behind instruments.
* They attach to an atom and provide the editor + playback functionality.
*/
/datum/song
/// Name of the song
var/name = "Untitled"
/// The atom we're attached to/playing from
var/atom/parent
/// Our song lines
var/list/lines
/// delay between notes in deciseconds
var/tempo = 5
/// How far we can be heard
var/instrument_range = 15
/// Are we currently playing?
var/playing = FALSE
/// Are we currently editing?
var/editing = TRUE
/// Is the help screen open?
var/help = FALSE
/// Repeats left
var/repeat = 0
/// Maximum times we can repeat
var/max_repeats = 10
/// Our volume
var/volume = 75
/// Max volume
var/max_volume = 75
/// Min volume - This is so someone doesn't decide it's funny to set it to 0 and play invisible songs.
var/min_volume = 1
/// What instruments our built in picker can use. The picker won't show unless this is longer than one.
var/list/allowed_instrument_ids
//////////// Cached instrument variables /////////////
/// Instrument we are currently using
var/datum/instrument/using_instrument
/// Cached legacy ext for legacy instruments
var/cached_legacy_ext
/// Cached legacy dir for legacy instruments
var/cached_legacy_dir
/// Cached list of samples, referenced directly from the instrument for synthesized instruments
var/list/cached_samples
/// Are we operating in legacy mode (so if the instrument is a legacy instrument)
var/legacy = FALSE
//////////////////////////////////////////////////////
/////////////////// Playing variables ////////////////
/**
* Build by compile_chords()
* Must be rebuilt on instrument switch.
* Compilation happens when we start playing and is cleared after we finish playing.
* Format: list of chord lists, with chordlists having (key1, key2, key3, tempodiv)
*/
var/list/compiled_chords
/// Current section of a long chord we're on, so we don't need to make a billion chords, one for every unit ticklag.
var/elapsed_delay
/// Amount of delay to wait before playing the next chord
var/delay_by
/// Current chord we're on.
var/current_chord
/// Channel as text = current volume percentage but it's 0 to 100 instead of 0 to 1.
var/list/channels_playing
/// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
var/list/channels_idle
/// Person playing us
var/mob/user_playing
//////////////////////////////////////////////////////
/// Last world.time we checked for who can hear us
var/last_hearcheck = 0
/// The list of mobs that can hear us
var/list/hearing_mobs
/// If this is enabled, some things won't be strictly cleared when they usually are (liked compiled_chords on play stop)
var/debug_mode = FALSE
/// Max sound channels to occupy
var/max_sound_channels = CHANNELS_PER_INSTRUMENT
/// Current channels, so we can save a length() call.
var/using_sound_channels = 0
/// Last channel to play. text.
var/last_channel_played
/// Should we not decay our last played note?
var/full_sustain_held_note = TRUE
/////////////////////// DO NOT TOUCH THESE ///////////////////
var/octave_min = INSTRUMENT_MIN_OCTAVE
var/octave_max = INSTRUMENT_MAX_OCTAVE
var/key_min = INSTRUMENT_MIN_KEY
var/key_max = INSTRUMENT_MAX_KEY
var/static/list/note_offset_lookup
var/static/list/accent_lookup
//////////////////////////////////////////////////////////////
///////////// !!FUN!! - Only works in synthesized mode! /////////////////
/// Note numbers to shift.
var/note_shift = 0
var/note_shift_min = -100
var/note_shift_max = 100
var/can_noteshift = TRUE
/// The kind of sustain we're using
var/sustain_mode = SUSTAIN_LINEAR
/// When a note is considered dead if it is below this in volume
var/sustain_dropoff_volume = INSTRUMENT_MIN_SUSTAIN_DROPOFF
/// Total duration of linear sustain for 100 volume note to get to SUSTAIN_DROPOFF
var/sustain_linear_duration = 5
/// Exponential sustain dropoff rate per decisecond
var/sustain_exponential_dropoff = 1.4
////////// DO NOT DIRECTLY SET THESE!
/// Do not directly set, use update_sustain()
var/cached_linear_dropoff = 10
/// Do not directly set, use update_sustain()
var/cached_exponential_dropoff = 1.045
/////////////////////////////////////////////////////////////////////////
var/static/list/valid_files[0] // Cache to avoid running fexists() every time
/datum/song/New(atom/parent, list/instrument_ids, new_range)
SSinstruments.on_song_new(src)
lines = list()
tempo = sanitize_tempo(tempo)
src.parent = parent
if(instrument_ids)
allowed_instrument_ids = islist(instrument_ids) ? instrument_ids : list(instrument_ids)
if(length(allowed_instrument_ids))
set_instrument(allowed_instrument_ids[1])
hearing_mobs = list()
volume = clamp(volume, min_volume, max_volume)
channels_playing = list()
channels_idle = list()
if(!note_offset_lookup)
note_offset_lookup = list(9, 11, 0, 2, 4, 5, 7)
accent_lookup = list("b" = -1, "s" = 1, "#" = 1, "n" = 0)
update_sustain()
if(new_range)
instrument_range = new_range
/datum/song/Destroy()
stop_playing()
SSinstruments.on_song_del(src)
lines = null
using_instrument = null
allowed_instrument_ids = null
parent = null
return ..()
/**
* Checks and stores which mobs can hear us. Terminates sounds for mobs that leave our range.
*/
/datum/song/proc/do_hearcheck()
last_hearcheck = world.time
var/list/old = hearing_mobs.Copy()
hearing_mobs.len = 0
var/turf/source = get_turf(parent)
for(var/mob/M in GLOB.player_list)
var/dist = get_dist(M, source)
if(dist > instrument_range) // Distance check
continue
if(!isInSight(M, source)) // Visibility check (direct line of sight)
continue
hearing_mobs[M] = dist
var/list/exited = old - hearing_mobs
for(var/i in exited)
terminate_sound_mob(i)
/**
* Sets our instrument, caching anything necessary for faster accessing. Accepts an ID, typepath, or instantiated instrument datum.
*/
/datum/song/proc/set_instrument(datum/instrument/I)
terminate_all_sounds()
var/old_legacy
if(using_instrument)
using_instrument.songs_using -= src
old_legacy = (using_instrument.instrument_flags & INSTRUMENT_LEGACY)
using_instrument = null
cached_samples = null
cached_legacy_ext = null
cached_legacy_dir = null
legacy = null
if(istext(I) || ispath(I))
I = SSinstruments.instrument_data[I]
if(istype(I))
using_instrument = I
I.songs_using += src
var/instrument_legacy = (I.instrument_flags & INSTRUMENT_LEGACY)
if(instrument_legacy)
cached_legacy_ext = I.legacy_instrument_ext
cached_legacy_dir = I.legacy_instrument_path
legacy = TRUE
else
cached_samples = I.samples
legacy = FALSE
if(isnull(old_legacy) || (old_legacy != instrument_legacy))
if(playing)
compile_chords()
/**
* Attempts to start playing our song.
*/
/datum/song/proc/start_playing(mob/user)
if(playing)
return
if(!using_instrument?.is_ready())
to_chat(user, "<span class='warning'>An error has occured with [src]. Please reset the instrument.</span>")
return
compile_chords()
if(!length(compiled_chords))
to_chat(user, "<span class='warning'>Song is empty.</span>")
return
playing = TRUE
SStgui.update_uis(parent)
//we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
//wrap the rest of the stuff to ensure stop_playing() is called.
do_hearcheck()
SEND_SIGNAL(parent, COMSIG_SONG_START)
elapsed_delay = 0
delay_by = 0
current_chord = 1
user_playing = user
START_PROCESSING(SSinstruments, src)
/**
* Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs.
*/
/datum/song/proc/stop_playing()
if(!playing)
return
playing = FALSE
if(!debug_mode)
compiled_chords = null
STOP_PROCESSING(SSinstruments, src)
SEND_SIGNAL(parent, COMSIG_SONG_END)
terminate_all_sounds(TRUE)
hearing_mobs.len = 0
SStgui.update_uis(parent)
user_playing = null
/**
* Processes our song.
*/
/datum/song/proc/process_song(wait)
if(!length(compiled_chords) || current_chord > length(compiled_chords) || should_stop_playing(user_playing))
stop_playing()
return
var/list/chord = compiled_chords[current_chord]
if(++elapsed_delay >= delay_by)
play_chord(chord)
elapsed_delay = 0
delay_by = tempodiv_to_delay(chord[length(chord)])
current_chord++
if(current_chord > length(compiled_chords))
if(repeat)
repeat--
current_chord = 1
SStgui.update_uis(parent)
return
else
stop_playing()
return
/**
* Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo.
*/
/datum/song/proc/tempodiv_to_delay(tempodiv)
tempodiv = tempodiv || world.tick_lag // Default to world.tick_lag in case someone's trying to be smart
return max(1, round((tempo / tempodiv) / world.tick_lag, 1))
/**
* Compiles chords.
*/
/datum/song/proc/compile_chords()
legacy ? compile_legacy() : compile_synthesized()
/**
* Plays a chord.
*/
/datum/song/proc/play_chord(list/chord)
// last value is timing information
for(var/i in 1 to (length(chord) - 1))
legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing)
/**
* Checks if we should halt playback.
*/
/datum/song/proc/should_stop_playing(mob/user)
return QDELETED(parent) || !using_instrument || !playing
/**
* Sanitizes tempo to a value that makes sense and fits the current world.tick_lag.
*/
/datum/song/proc/sanitize_tempo(new_tempo)
new_tempo = abs(new_tempo)
return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
/**
* Gets our beats per minute based on our tempo.
*/
/datum/song/proc/get_bpm()
return 600 / tempo
/**
* Sets our tempo from a beats-per-minute, sanitizing it to a valid number first.
*/
/datum/song/proc/set_bpm(bpm)
tempo = sanitize_tempo(600 / bpm)
/datum/song/process(wait)
if(!playing)
return PROCESS_KILL
// it's expected this ticks at every world.tick_lag. if it lags, do not attempt to catch up.
process_song(world.tick_lag)
process_decay(world.tick_lag)
/**
* Updates our cached linear/exponential falloff stuff, saving calculations down the line.
*/
/datum/song/proc/update_sustain()
// Exponential is easy
cached_exponential_dropoff = sustain_exponential_dropoff
// Linear, not so much, since it's a target duration from 100 volume rather than an exponential rate.
var/target_duration = sustain_linear_duration
var/volume_diff = max(0, 100 - sustain_dropoff_volume)
var/volume_decrease_per_decisecond = volume_diff / target_duration
cached_linear_dropoff = volume_decrease_per_decisecond
/**
* Setter for setting output volume.
*/
/datum/song/proc/set_volume(volume)
src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
update_sustain()
// We don't want to send the whole payload (song included) just for volume
var/datum/tgui/ui = SStgui.get_open_ui(usr, parent, "main")
if(ui)
ui.push_data(list("volume" = volume), force = TRUE)
/**
* Setter for setting how low the volume has to get before a note is considered "dead" and dropped
*/
/datum/song/proc/set_dropoff_volume(volume, no_refresh = FALSE)
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
update_sustain()
if(!no_refresh)
SStgui.update_uis(parent)
/**
* Setter for setting exponential falloff factor.
*/
/datum/song/proc/set_exponential_drop_rate(drop, no_refresh = FALSE)
sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
update_sustain()
if(!no_refresh)
SStgui.update_uis(parent)
/**
* Setter for setting linear falloff duration.
*/
/datum/song/proc/set_linear_falloff_duration(duration, no_refresh = FALSE)
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
update_sustain()
if(!no_refresh)
SStgui.update_uis(parent)
/datum/song/vv_edit_var(var_name, var_value)
. = ..()
if(.)
switch(var_name)
if(NAMEOF(src, volume))
set_volume(var_value)
if(NAMEOF(src, sustain_dropoff_volume))
set_dropoff_volume(var_value)
if(NAMEOF(src, sustain_exponential_dropoff))
set_exponential_drop_rate(var_value)
if(NAMEOF(src, sustain_linear_duration))
set_linear_falloff_duration(var_value)
// subtype for handheld instruments, like violin
/datum/song/handheld
/datum/song/handheld/should_stop_playing(mob/user)
. = ..()
if(.)
return TRUE
var/obj/item/instrument/I = parent
return I.should_stop_playing(user)
// subtype for stationary structures, like pianos
/datum/song/stationary
/datum/song/stationary/should_stop_playing(mob/user)
. = ..()
if(.)
return TRUE
var/obj/structure/musician/M = parent
return M.should_stop_playing(user)
+179
View File
@@ -0,0 +1,179 @@
/datum/song/tgui_data(mob/user)
var/data[0]
// General
data["playing"] = playing
data["repeat"] = repeat
data["maxRepeats"] = max_repeats
data["editing"] = editing
data["lines"] = lines
data["tempo"] = tempo
data["minTempo"] = world.tick_lag
data["maxTempo"] = 5 SECONDS
data["tickLag"] = world.tick_lag
data["help"] = help
// Status
var/list/allowed_instrument_names = list()
for(var/i in allowed_instrument_ids)
var/datum/instrument/I = SSinstruments.get_instrument(i)
if(I)
allowed_instrument_names += I.name
data["allowedInstrumentNames"] = allowed_instrument_names
data["instrumentLoaded"] = !isnull(using_instrument)
if(using_instrument)
data["instrument"] = using_instrument.name
data["canNoteShift"] = can_noteshift
if(can_noteshift)
data["noteShift"] = note_shift
data["noteShiftMin"] = note_shift_min
data["noteShiftMax"] = note_shift_max
data["sustainMode"] = sustain_mode
switch(sustain_mode)
if(SUSTAIN_LINEAR)
data["sustainLinearDuration"] = sustain_linear_duration
if(SUSTAIN_EXPONENTIAL)
data["sustainExponentialDropoff"] = sustain_exponential_dropoff
data["ready"] = using_instrument?.is_ready()
data["legacy"] = legacy
data["volume"] = volume
data["minVolume"] = min_volume
data["maxVolume"] = max_volume
data["sustainDropoffVolume"] = sustain_dropoff_volume
data["sustainHeldNote"] = full_sustain_held_note
return data
/datum/song/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
ui = SStgui.try_update_ui(user, parent, ui_key, ui, force_open)
if(!ui)
ui = new(user, parent, ui_key, "Instrument", parent?.name || "Instrument", 700, 500)
ui.open()
ui.set_autoupdate(FALSE) // NO!!! Don't auto-update this!!
/datum/song/tgui_act(action, params)
. = TRUE
switch(action)
if("newsong")
lines = new()
tempo = sanitize_tempo(5) // default 120 BPM
name = ""
if("import")
var/t = ""
do
t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
if(!in_range(parent, usr))
return
if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
if(cont == "no")
break
while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
parse_song(t)
return FALSE
if("help")
help = !help
if("edit")
editing = !editing
if("repeat") //Changing this from a toggle to a number of repeats to avoid infinite loops.
if(playing)
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
repeat = clamp(round(text2num(params["new"])), 0, max_repeats)
if("tempo")
tempo = sanitize_tempo(text2num(params["new"]))
if("play")
INVOKE_ASYNC(src, .proc/start_playing, usr)
if("newline")
var/newline = html_encode(input("Enter your line: ", parent.name) as text|null)
if(!newline || !in_range(parent, usr))
return
if(length(lines) > MUSIC_MAXLINES)
return
if(length(newline) > MUSIC_MAXLINECHARS)
newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
lines.Add(newline)
if("deleteline")
var/num = round(text2num(params["line"]))
if(num > length(lines) || num < 1)
return
lines.Cut(num, num + 1)
if("modifyline")
var/num = round(text2num(params["line"]))
var/content = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS)
if(!content || !in_range(parent, usr))
return
if(num > length(lines) || num < 1)
return
lines[num] = content
if("stop")
stop_playing()
if("setlinearfalloff")
set_linear_falloff_duration(round(text2num(params["new"]) * 10, world.tick_lag), TRUE)
if("setexpfalloff")
set_exponential_drop_rate(round(text2num(params["new"]), 0.00001), TRUE)
if("setvolume")
set_volume(round(text2num(params["new"]), 1))
if("setdropoffvolume")
set_dropoff_volume(round(text2num(params["new"]), 0.01), TRUE)
if("switchinstrument")
if(!length(allowed_instrument_ids))
return
else if(length(allowed_instrument_ids) == 1)
set_instrument(allowed_instrument_ids[1])
return
var/choice = params["name"]
for(var/i in allowed_instrument_ids)
var/datum/instrument/I = SSinstruments.get_instrument(i)
if(I && I.name == choice)
set_instrument(I)
if("setnoteshift")
note_shift = clamp(round(text2num(params["new"])), note_shift_min, note_shift_max)
if("setsustainmode")
var/static/list/sustain_modes
if(!length(sustain_modes))
sustain_modes = list("Linear" = SUSTAIN_LINEAR, "Exponential" = SUSTAIN_EXPONENTIAL)
var/choice = params["new"]
sustain_mode = sustain_modes[choice] || sustain_mode
if("togglesustainhold")
full_sustain_held_note = !full_sustain_held_note
if("reset")
var/default_instrument = allowed_instrument_ids[1]
if(using_instrument != SSinstruments.instrument_data[default_instrument])
set_instrument(default_instrument)
note_shift = initial(note_shift)
sustain_mode = initial(sustain_mode)
set_linear_falloff_duration(initial(sustain_linear_duration), TRUE)
set_exponential_drop_rate(initial(sustain_exponential_dropoff), TRUE)
set_dropoff_volume(initial(sustain_dropoff_volume), TRUE)
else
return FALSE
parent.add_fingerprint(usr)
/**
* Parses a song the user has input into lines and stores them.
*/
/datum/song/proc/parse_song(text)
set waitfor = FALSE
//split into lines
stop_playing()
lines = splittext(text, "\n")
if(length(lines))
var/bpm_string = "BPM: "
if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default
tempo = sanitize_tempo(600 / round(divisor, 1))
lines.Cut(1, 2)
else
tempo = sanitize_tempo(5) // default 120 BPM
if(length(lines) > MUSIC_MAXLINES)
to_chat(usr, "Too many lines!")
lines.Cut(MUSIC_MAXLINES + 1)
var/linenum = 1
for(var/l in lines)
if(length_char(l) > MUSIC_MAXLINECHARS)
to_chat(usr, "Line [linenum] too long!")
lines.Remove(l)
else
linenum++
SStgui.update_uis(parent)
@@ -0,0 +1,95 @@
/**
* Compiles our lines into "chords" with filenames for legacy playback. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
*/
/datum/song/proc/compile_legacy()
if(!length(src.lines))
return
var/list/lines = src.lines //cache for hyepr speed!
compiled_chords = list()
var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
for(var/line in lines)
var/list/chords = splittext(lowertext(line), ",")
for(var/chord in chords)
var/list/compiled_chord = list()
var/tempodiv = 1
var/list/notes_tempodiv = splittext(chord, "/")
var/len = length(notes_tempodiv)
if(len >= 2)
tempodiv = text2num(notes_tempodiv[2])
if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
var/list/notes = splittext(notes_tempodiv[1], "-")
for(var/note in notes)
if(length(note) == 0)
continue
// 1-7, A-G
var/key = text2ascii(note) - 96
if((key < 1) || (key > 7))
continue
for(var/i in 2 to length(note))
var/oct_acc = copytext(note, i, i + 1)
var/num = text2num(oct_acc)
if(!num) //it's an accidental
accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
else //octave
octaves[key] = clamp(num, octave_min, octave_max)
compiled_chord[++compiled_chord.len] = list(key, accents[key], octaves[key])
compiled_chord += tempodiv //this goes last
if(length(compiled_chord))
compiled_chords[++compiled_chords.len] = compiled_chord
/**
* Proc to play a legacy note. Just plays the sound to hearing mobs (and does hearcheck if necessary), no fancy channel/sustain/management.
*
* Arguments:
* * note - number from 1-7 for A-G
* * acc - either "b", "n", or "#"
* * oct - 1-8 (or 9 for C)
*/
/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
// handle accidental -> B<>C of E<>F
if(acc == "b" && (note == 3 || note == 6)) // C or F
if(note == 3)
oct--
note--
acc = "n"
else if(acc == "#" && (note == 2 || note == 5)) // B or E
if(note == 2)
oct++
note++
acc = "n"
else if(acc == "#" && (note == 7)) //G#
note = 1
acc = "b"
else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
acc = "b"
note++
// check octave, C is allowed to go to 9
if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
return
// now generate name
var/filename = "sound/instruments/[cached_legacy_dir]/[ascii2text(note + 64)][acc][oct].[cached_legacy_ext]"
var/soundfile = file(filename)
// make sure the note exists
var/cached_fexists = valid_files[filename]
if(!isnull(cached_fexists))
if(!cached_fexists)
return
else if(!fexists(soundfile))
valid_files[filename] = FALSE
return
else
valid_files[filename] = TRUE
// and play
var/turf/source = get_turf(parent)
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
do_hearcheck()
var/sound/music_played = sound(soundfile)
for(var/i in hearing_mobs)
var/mob/M = i
if(!(M.client?.prefs?.sound & SOUND_INSTRUMENTS))
continue
M.playsound_local(source, null, volume * using_instrument.volume_multiplier, falloff = 5, S = music_played)
// Could do environment and echo later but not for now
@@ -0,0 +1,134 @@
/**
* Compiles our lines into "chords" with numbers. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
*/
/datum/song/proc/compile_synthesized()
if(!length(src.lines))
return
var/list/lines = src.lines //cache for hyepr speed!
compiled_chords = list()
var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
for(var/line in lines)
var/list/chords = splittext(lowertext(line), ",")
for(var/chord in chords)
var/list/compiled_chord = list()
var/tempodiv = 1
var/list/notes_tempodiv = splittext(chord, "/")
var/len = length(notes_tempodiv)
if(len >= 2)
tempodiv = text2num(notes_tempodiv[2])
if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
var/list/notes = splittext(notes_tempodiv[1], "-")
for(var/note in notes)
if(length(note) == 0)
continue
// 1-7, A-G
var/key = text2ascii(note) - 96
if((key < 1) || (key > 7))
continue
for(var/i in 2 to length(note))
var/oct_acc = copytext(note, i, i + 1)
var/num = text2num(oct_acc)
if(!num) //it's an accidental
accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
else //octave
octaves[key] = clamp(num, octave_min, octave_max)
compiled_chord += clamp((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max)
compiled_chord += tempodiv //this goes last
if(length(compiled_chord))
compiled_chords[++compiled_chords.len] = compiled_chord
/**
* Plays a specific numerical key from our instrument to anyone who can hear us.
* Does a hearing check if enough time has passed.
*/
/datum/song/proc/playkey_synth(key, mob/user)
if(can_noteshift)
key = clamp(key + note_shift, key_min, key_max)
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
do_hearcheck()
var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc!
//Should probably add channel limiters here at some point but I don't care right now.
var/channel = pop_channel()
if(isnull(channel))
return FALSE
. = TRUE
var/sound/copy = sound(K.sample)
var/volume = src.volume * using_instrument.volume_multiplier
copy.frequency = K.frequency
copy.volume = volume
var/channel_text = num2text(channel)
channels_playing[channel_text] = 100
last_channel_played = channel_text
for(var/i in hearing_mobs)
var/mob/M = i
if(!(M.client?.prefs?.sound & SOUND_INSTRUMENTS))
continue
M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
// Could do environment and echo later but not for now
/**
* Stops all sounds we are "responsible" for. Only works in synthesized mode.
*/
/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
for(var/i in hearing_mobs)
terminate_sound_mob(i)
if(clear_channels && channels_playing)
channels_playing.len = 0
channels_idle.len = 0
SSinstruments.current_instrument_channels -= using_sound_channels
using_sound_channels = 0
SSsounds.free_datum_channels(src)
/**
* Stops all sounds we are responsible for in a given person. Only works in synthesized mode.
*/
/datum/song/proc/terminate_sound_mob(mob/M)
for(var/channel in channels_playing)
M.stop_sound_channel(text2num(channel))
/**
* Pops a channel we have reserved so we don't have to release and re-request them from SSsounds every time we play a note. This is faster.
*/
/datum/song/proc/pop_channel()
if(length(channels_idle)) //just pop one off of here if we have one available
. = text2num(channels_idle[1])
channels_idle.Cut(1, 2)
return
if(using_sound_channels >= max_sound_channels)
return
. = SSinstruments.reserve_instrument_channel(src)
if(!isnull(.))
using_sound_channels++
/**
* Decays our channels and updates their volumes to mobs who can hear us.
*
* Arguments:
* * wait_ds - the deciseconds we should decay by. This is to compensate for any lag, as otherwise songs would get pretty nasty during high time dilation.
*/
/datum/song/proc/process_decay(wait_ds)
var/linear_dropoff = cached_linear_dropoff * wait_ds
var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
for(var/channel in channels_playing)
if(full_sustain_held_note && (channel == last_channel_played))
continue
var/current_volume = channels_playing[channel]
switch(sustain_mode)
if(SUSTAIN_LINEAR)
current_volume -= linear_dropoff
if(SUSTAIN_EXPONENTIAL)
current_volume /= exponential_dropoff
channels_playing[channel] = current_volume
var/dead = current_volume <= sustain_dropoff_volume
var/channelnumber = text2num(channel)
if(dead)
channels_playing -= channel
channels_idle += channel
for(var/i in hearing_mobs)
var/mob/M = i
M.stop_sound_channel(channelnumber)
else
for(var/i in hearing_mobs)
var/mob/M = i
M.set_sound_channel_volume(channelnumber, (current_volume * 0.01) * volume * using_instrument.volume_multiplier)
+19
View File
@@ -0,0 +1,19 @@
/datum/instrument/tones
name = "Ideal tone"
category = "Tones"
abstract_type = /datum/instrument/tones
/datum/instrument/tones/square_wave
name = "Ideal square wave"
id = "square"
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Square.ogg')
/datum/instrument/tones/sine_wave
name = "Ideal sine wave"
id = "sine"
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sine.ogg')
/datum/instrument/tones/saw_wave
name = "Ideal sawtooth wave"
id = "saw"
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sawtooth.ogg')
@@ -13,15 +13,3 @@
possible_destinations = "laborcamp_away"
circuit = /obj/item/circuitboard/labor_shuttle/one_way
req_access = list( )
/obj/machinery/computer/shuttle/labor/one_way/Topic(href, href_list)
if(href_list["move"])
var/obj/docking_port/mobile/M = SSshuttle.getShuttle("laborcamp")
if(!M)
to_chat(usr, "<span class='warning'>Cannot locate shuttle!</span>")
return 0
var/obj/docking_port/stationary/S = M.get_docked()
if(S && S.name == "laborcamp_away")
to_chat(usr, "<span class='warning'>Shuttle is already at the outpost!</span>")
return 0
..()
+212 -151
View File
@@ -1,104 +1,21 @@
/**********************Mining Equipment Locker**************************/
// Use this define to register something as a purchasable!
// * n — The proper name of the purchasable
// * o — The object type path of the purchasable to spawn
// * p — The price of the purchasable in mining points
#define EQUIPMENT(n, o, p) n = new /datum/data/mining_equipment(n, o, p)
/**********************Mining Equipment Vendor**************************/
/obj/machinery/mineral/equipment_vendor
name = "mining equipment vendor"
desc = "An equipment vendor for miners, points collected at an ore redemption machine can be spent here."
icon = 'icons/obj/machines/mining_machines.dmi'
icon_state = "mining"
density = 1
anchored = 1.0
density = TRUE
anchored = TRUE
var/obj/item/card/id/inserted_id
var/list/prize_list = list( //if you add something to this, please, for the love of god, sort it by price/type. use tabs and not spaces.
new /datum/data/mining_equipment("1 Marker Beacon", /obj/item/stack/marker_beacon, 10),
new /datum/data/mining_equipment("10 Marker Beacons", /obj/item/stack/marker_beacon/ten, 100),
new /datum/data/mining_equipment("30 Marker Beacons", /obj/item/stack/marker_beacon/thirty, 300),
new /datum/data/mining_equipment("Whiskey", /obj/item/reagent_containers/food/drinks/bottle/whiskey, 100),
new /datum/data/mining_equipment("Absinthe", /obj/item/reagent_containers/food/drinks/bottle/absinthe/premium, 100),
new /datum/data/mining_equipment("Cigar", /obj/item/clothing/mask/cigarette/cigar/havana, 150),
new /datum/data/mining_equipment("Soap", /obj/item/soap/nanotrasen, 200),
new /datum/data/mining_equipment("Laser Pointer", /obj/item/laser_pointer, 300),
new /datum/data/mining_equipment("Alien Toy", /obj/item/clothing/mask/facehugger/toy, 300),
new /datum/data/mining_equipment("Stabilizing Serum", /obj/item/hivelordstabilizer, 400),
new /datum/data/mining_equipment("Space Cash", /obj/item/stack/spacecash/c1000, 2000),
new /datum/data/mining_equipment("Point Transfer Card", /obj/item/card/mining_point_card, 500),
new /datum/data/mining_equipment("Fulton Beacon", /obj/item/fulton_core, 400),
new /datum/data/mining_equipment("Shelter Capsule", /obj/item/survivalcapsule, 400),
new /datum/data/mining_equipment("GAR Meson Scanners", /obj/item/clothing/glasses/meson/gar, 500),
new /datum/data/mining_equipment("Explorer's Webbing", /obj/item/storage/belt/mining, 500),
new /datum/data/mining_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/autoinjector/survival, 500),
new /datum/data/mining_equipment("Brute First-Aid Kit", /obj/item/storage/firstaid/brute, 600),
new /datum/data/mining_equipment("Tracking Implant Kit", /obj/item/storage/box/minertracker, 600),
new /datum/data/mining_equipment("Jaunter", /obj/item/wormhole_jaunter, 750),
new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/twohanded/kinetic_crusher, 750),
new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
new /datum/data/mining_equipment("Advanced Scanner", /obj/item/t_scanner/adv_mining_scanner, 800),
new /datum/data/mining_equipment("Resonator", /obj/item/resonator, 800),
new /datum/data/mining_equipment("Fulton Pack", /obj/item/extraction_pack, 1000),
new /datum/data/mining_equipment("Lazarus Injector", /obj/item/lazarus_injector, 1000),
new /datum/data/mining_equipment("Silver Pickaxe", /obj/item/pickaxe/silver, 1000),
new /datum/data/mining_equipment("Mining Conscription Kit", /obj/item/storage/backpack/duffel/mining_conscript/full, 1500),
new /datum/data/mining_equipment("Jetpack Upgrade", /obj/item/tank/jetpack/suit, 2000),
new /datum/data/mining_equipment("Mining Hardsuit", /obj/item/clothing/suit/space/hardsuit/mining, 2000),
new /datum/data/mining_equipment("Diamond Pickaxe", /obj/item/pickaxe/diamond, 2000),
new /datum/data/mining_equipment("Super Resonator", /obj/item/resonator/upgraded, 2500),
new /datum/data/mining_equipment("Jump Boots", /obj/item/clothing/shoes/bhop, 2500),
new /datum/data/mining_equipment("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
new /datum/data/mining_equipment("Nanotrasen Minebot", /obj/item/mining_drone_cube, 800),
new /datum/data/mining_equipment("Minebot Melee Upgrade", /obj/item/mine_bot_upgrade, 400),
new /datum/data/mining_equipment("Minebot Armor Upgrade", /obj/item/mine_bot_upgrade/health, 400),
new /datum/data/mining_equipment("Minebot Cooldown Upgrade", /obj/item/borg/upgrade/modkit/cooldown/minebot, 600),
new /datum/data/mining_equipment("Minebot AI Upgrade", /obj/item/slimepotion/sentience/mining, 1000),
new /datum/data/mining_equipment("KA Minebot Passthrough", /obj/item/borg/upgrade/modkit/minebot_passthrough, 100),
new /datum/data/mining_equipment("Lazarus Capsule", /obj/item/mobcapsule, 800),
new /datum/data/mining_equipment("Lazarus Capsule belt", /obj/item/storage/belt/lazarus, 200),
new /datum/data/mining_equipment("KA White Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer, 100),
new /datum/data/mining_equipment("KA Adjustable Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer/adjustable, 150),
new /datum/data/mining_equipment("KA Super Chassis", /obj/item/borg/upgrade/modkit/chassis_mod, 250),
new /datum/data/mining_equipment("KA Hyper Chassis", /obj/item/borg/upgrade/modkit/chassis_mod/orange, 300),
new /datum/data/mining_equipment("KA Range Increase", /obj/item/borg/upgrade/modkit/range, 1000),
new /datum/data/mining_equipment("KA Damage Increase", /obj/item/borg/upgrade/modkit/damage, 1000),
new /datum/data/mining_equipment("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000),
new /datum/data/mining_equipment("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000)
)
/obj/machinery/mineral/equipment_vendor/golem
name = "golem ship equipment vendor"
/obj/machinery/mineral/equipment_vendor/golem/New()
..()
component_parts = list()
component_parts += new /obj/item/circuitboard/mining_equipment_vendor/golem(null)
component_parts += new /obj/item/stock_parts/matter_bin(null)
component_parts += new /obj/item/stock_parts/matter_bin(null)
component_parts += new /obj/item/stock_parts/matter_bin(null)
component_parts += new /obj/item/stack/sheet/glass(null)
RefreshParts()
/obj/machinery/mineral/equipment_vendor/golem/Initialize()
. = ..()
desc += "\nIt seems a few selections have been added."
prize_list += list(
new /datum/data/mining_equipment("Extra Id", /obj/item/card/id/golem, 250),
new /datum/data/mining_equipment("Science Backpack", /obj/item/storage/backpack/science, 250),
new /datum/data/mining_equipment("Full Toolbelt", /obj/item/storage/belt/utility/full/multitool, 250),
new /datum/data/mining_equipment("Monkey Cube", /obj/item/reagent_containers/food/snacks/monkeycube, 250),
new /datum/data/mining_equipment("Royal Cape of the Liberator", /obj/item/bedsheet/rd/royal_cape, 500),
new /datum/data/mining_equipment("Grey Slime Extract", /obj/item/slime_extract/grey, 1000),
new /datum/data/mining_equipment("KA Trigger Modification Kit", /obj/item/borg/upgrade/modkit/trigger_guard, 1000),
new /datum/data/mining_equipment("Shuttle Console Board", /obj/item/circuitboard/shuttle/golem_ship, 2000),
new /datum/data/mining_equipment("The Liberator's Legacy", /obj/item/storage/box/rndboards, 2000)
)
/datum/data/mining_equipment
var/equipment_name = "generic"
var/equipment_path = null
var/cost = 0
/datum/data/mining_equipment/New(name, path, equipment_cost)
equipment_name = name
equipment_path = path
cost = equipment_cost
var/list/prize_list // Initialized just below! (if you're wondering why - check CONTRIBUTING.md, look for: "hidden" init proc)
var/dirty_items = FALSE // Used to refresh the static/redundant data in case the machine gets VV'd
/obj/machinery/mineral/equipment_vendor/New()
..()
@@ -110,6 +27,72 @@
component_parts += new /obj/item/stack/sheet/glass(null)
RefreshParts()
/obj/machinery/mineral/equipment_vendor/Initialize(mapload)
. = ..()
prize_list = list()
prize_list["Gear"] = list(
EQUIPMENT("Advanced Scanner", /obj/item/t_scanner/adv_mining_scanner, 800),
EQUIPMENT("Explorer's Webbing", /obj/item/storage/belt/mining, 500),
EQUIPMENT("Fulton Beacon", /obj/item/fulton_core, 400),
EQUIPMENT("Mining Conscription Kit", /obj/item/storage/backpack/duffel/mining_conscript, 1500),
EQUIPMENT("Jetpack Upgrade", /obj/item/tank/jetpack/suit, 2000),
EQUIPMENT("Jump Boots", /obj/item/clothing/shoes/bhop, 2500),
EQUIPMENT("Lazarus Capsule", /obj/item/mobcapsule, 800),
EQUIPMENT("Lazarus Capsule belt", /obj/item/storage/belt/lazarus, 200),
EQUIPMENT("Mining Hardsuit", /obj/item/clothing/suit/space/hardsuit/mining, 2000),
EQUIPMENT("Tracking Implant Kit", /obj/item/storage/box/minertracker, 600),
)
prize_list["Consumables"] = list(
EQUIPMENT("10 Marker Beacons", /obj/item/stack/marker_beacon/ten, 100),
EQUIPMENT("Brute First-Aid Kit", /obj/item/storage/firstaid/brute, 600),
EQUIPMENT("Fulton Pack", /obj/item/extraction_pack, 1000),
EQUIPMENT("Jaunter", /obj/item/wormhole_jaunter, 750),
EQUIPMENT("Lazarus Injector", /obj/item/lazarus_injector, 1000),
EQUIPMENT("Point Transfer Card", /obj/item/card/mining_point_card, 500),
EQUIPMENT("Shelter Capsule", /obj/item/survivalcapsule, 400),
EQUIPMENT("Stabilizing Serum", /obj/item/hivelordstabilizer, 400),
EQUIPMENT("Survival Medipen", /obj/item/reagent_containers/hypospray/autoinjector/survival, 500),
)
prize_list["Kinetic Accelerator"] = list(
EQUIPMENT("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
EQUIPMENT("KA Adjustable Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer/adjustable, 150),
EQUIPMENT("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000),
EQUIPMENT("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000),
EQUIPMENT("KA Damage Increase", /obj/item/borg/upgrade/modkit/damage, 1000),
EQUIPMENT("KA Hyper Chassis", /obj/item/borg/upgrade/modkit/chassis_mod/orange, 300),
EQUIPMENT("KA Minebot Passthrough", /obj/item/borg/upgrade/modkit/minebot_passthrough, 100),
EQUIPMENT("KA Range Increase", /obj/item/borg/upgrade/modkit/range, 1000),
EQUIPMENT("KA Super Chassis", /obj/item/borg/upgrade/modkit/chassis_mod, 250),
EQUIPMENT("KA White Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer, 100),
)
prize_list["Digging Tools"] = list(
EQUIPMENT("Diamond Pickaxe", /obj/item/pickaxe/diamond, 2000),
EQUIPMENT("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
EQUIPMENT("Kinetic Crusher", /obj/item/twohanded/kinetic_crusher, 750),
EQUIPMENT("Resonator", /obj/item/resonator, 800),
EQUIPMENT("Silver Pickaxe", /obj/item/pickaxe/silver, 1000),
EQUIPMENT("Super Resonator", /obj/item/resonator/upgraded, 2500),
)
prize_list["Minebot"] = list(
EQUIPMENT("Nanotrasen Minebot", /obj/item/mining_drone_cube, 800),
EQUIPMENT("Minebot AI Upgrade", /obj/item/slimepotion/sentience/mining, 1000),
EQUIPMENT("Minebot Armor Upgrade", /obj/item/mine_bot_upgrade/health, 400),
EQUIPMENT("Minebot Cooldown Upgrade", /obj/item/borg/upgrade/modkit/cooldown/minebot, 600),
EQUIPMENT("Minebot Melee Upgrade", /obj/item/mine_bot_upgrade, 400),
)
prize_list["Miscellaneous"] = list(
EQUIPMENT("Absinthe", /obj/item/reagent_containers/food/drinks/bottle/absinthe/premium, 100),
EQUIPMENT("Alien Toy", /obj/item/clothing/mask/facehugger/toy, 300),
EQUIPMENT("Cigar", /obj/item/clothing/mask/cigarette/cigar/havana, 150),
EQUIPMENT("GAR Meson Scanners", /obj/item/clothing/glasses/meson/gar, 500),
EQUIPMENT("Laser Pointer", /obj/item/laser_pointer, 300),
EQUIPMENT("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
EQUIPMENT("Soap", /obj/item/soap/nanotrasen, 200),
EQUIPMENT("Space Cash", /obj/item/stack/spacecash/c1000, 2000),
EQUIPMENT("Whiskey", /obj/item/reagent_containers/food/drinks/bottle/whiskey, 100),
)
prize_list["Extra"] = list() // Used in child vendors
/obj/machinery/mineral/equipment_vendor/power_change()
..()
update_icon()
@@ -126,90 +109,126 @@
/obj/machinery/mineral/equipment_vendor/attack_hand(mob/user)
if(..())
return
interact(user)
tgui_interact(user)
/obj/machinery/mineral/equipment_vendor/attack_ghost(mob/user)
interact(user)
tgui_interact(user)
/obj/machinery/mineral/equipment_vendor/interact(mob/user)
user.set_machine(src)
/obj/machinery/mineral/equipment_vendor/tgui_data(mob/user)
var/list/data[0]
var/dat
dat +="<div class='statusDisplay'>"
if(istype(inserted_id))
dat += "You have [inserted_id.mining_points] mining points collected. <A href='?src=[UID()];choice=eject'>Eject ID.</A><br>"
// ID
if(inserted_id)
data["has_id"] = TRUE
data["id"] = list(
"name" = inserted_id.registered_name,
"points" = inserted_id.mining_points,
)
else
dat += "No ID inserted. <A href='?src=[UID()];choice=insert'>Insert ID.</A><br>"
dat += "</div>"
dat += "<br><b>Equipment point cost list:</b><BR><table border='0' width='200'>"
for(var/datum/data/mining_equipment/prize in prize_list)
dat += "<tr><td>[prize.equipment_name]</td><td>[prize.cost]</td><td><A href='?src=[UID()];purchase=\ref[prize]'>Purchase</A></td></tr>"
dat += "</table>"
var/datum/browser/popup = new(user, "miningvendor", "Mining Equipment Vendor", 400, 350)
popup.set_content(dat)
popup.open()
data["has_id"] = FALSE
/obj/machinery/mineral/equipment_vendor/Topic(href, href_list)
return data
/obj/machinery/mineral/equipment_vendor/tgui_static_data(mob/user)
var/list/static_data[0]
// Available items - in static data because we don't wanna compute this list every time! It hardly changes.
static_data["items"] = list()
for(var/cat in prize_list)
var/list/cat_items = list()
for(var/prize_name in prize_list[cat])
var/datum/data/mining_equipment/prize = prize_list[cat][prize_name]
cat_items[prize_name] = list("name" = prize_name, "price" = prize.cost)
static_data["items"][cat] = cat_items
return static_data
/obj/machinery/mineral/equipment_vendor/vv_edit_var(var_name, var_value)
// Gotta update the static data in case an admin VV's the items for some reason..!
if(var_name == "prize_list")
dirty_items = TRUE
return ..()
/obj/machinery/mineral/equipment_vendor/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
// Update static data if need be
if(dirty_items)
if(!ui)
ui = SStgui.get_open_ui(user, src, ui_key)
if(ui) // OK so ui?. somehow breaks the implied src so this is needed
ui.initial_static_data = tgui_static_data(user)
dirty_items = FALSE
// Open the window
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "MiningVendor", name, 400, 450)
ui.open()
ui.set_autoupdate(FALSE)
/obj/machinery/mineral/equipment_vendor/tgui_act(action, params)
if(..())
return 1
return
if(href_list["choice"])
if(istype(inserted_id))
if(href_list["choice"] == "eject")
inserted_id.loc = loc
inserted_id.verb_pickup()
inserted_id = null
else if(href_list["choice"] == "insert")
var/obj/item/card/id/I = usr.get_active_hand()
if(istype(I))
if(!usr.drop_item())
return
I.loc = src
inserted_id = I
else
to_chat(usr, "<span class='danger'>No valid ID.</span>")
if(href_list["purchase"])
if(istype(inserted_id))
var/datum/data/mining_equipment/prize = locate(href_list["purchase"])
if(!prize || !(prize in prize_list) || prize.cost > inserted_id.mining_points)
. = TRUE
switch(action)
if("logoff")
if(!inserted_id)
return
usr.put_in_hands(inserted_id)
inserted_id = null
if("purchase")
if(!inserted_id)
return
var/category = params["cat"] // meow
var/name = params["name"]
if(!(category in prize_list) || !(name in prize_list[category])) // Not trying something that's not in the list, are you?
return
var/datum/data/mining_equipment/prize = prize_list[category][name]
if(prize.cost > inserted_id.mining_points) // shouldn't be able to access this since the button is greyed out, but..
to_chat(usr, "<span class='danger'>You have insufficient points.</span>")
return
inserted_id.mining_points -= prize.cost
new prize.equipment_path(src.loc)
updateUsrDialog()
new prize.equipment_path(loc)
else
return FALSE
add_fingerprint()
/obj/machinery/mineral/equipment_vendor/attackby(obj/item/I, mob/user, params)
if(default_deconstruction_screwdriver(user, "mining-open", "mining", I))
updateUsrDialog()
return
if(panel_open)
if(istype(I, /obj/item/crowbar))
if(inserted_id)
inserted_id.forceMove(loc) //Prevents deconstructing the ORM from deleting whatever ID was inside it.
default_deconstruction_crowbar(user, I)
return 1
return TRUE
if(istype(I, /obj/item/mining_voucher))
if(!powered())
return
else
RedeemVoucher(I, user)
redeem_voucher(I, user)
return
if(istype(I,/obj/item/card/id))
if(istype(I, /obj/item/card/id))
if(!powered())
return
else
var/obj/item/card/id/C = usr.get_active_hand()
if(istype(C) && !istype(inserted_id))
if(!usr.drop_item())
return
C.forceMove(src)
inserted_id = C
interact(user)
var/obj/item/card/id/C = user.get_active_hand()
if(istype(C) && !istype(inserted_id))
if(!user.drop_item())
return
C.forceMove(src)
inserted_id = C
tgui_interact(user)
return
return ..()
/obj/machinery/mineral/equipment_vendor/proc/RedeemVoucher(obj/item/mining_voucher/voucher, mob/redeemer)
/**
* Called when someone slaps the machine with a mining voucher
*
* Arguments:
* * voucher - The voucher card item
* * redeemer - The person holding it
*/
/obj/machinery/mineral/equipment_vendor/proc/redeem_voucher(obj/item/mining_voucher/voucher, mob/redeemer)
var/items = list("Survival Capsule and Explorer's Webbing", "Resonator Kit", "Minebot Kit", "Extraction and Rescue Kit", "Crusher Kit", "Mining Conscription Kit")
var/selection = input(redeemer, "Pick your equipment", "Mining Voucher Redemption") as null|anything in items
@@ -240,11 +259,51 @@
qdel(voucher)
/obj/machinery/mineral/equipment_vendor/ex_act(severity, target)
do_sparks(5, 1, src)
do_sparks(5, TRUE, src)
if(prob(50 / severity) && severity < 3)
qdel(src)
/**********************Mining Equipment Locker Items**************************/
/**********************Mining Equiment Vendor (Golem)**************************/
/obj/machinery/mineral/equipment_vendor/golem
name = "golem ship equipment vendor"
/obj/machinery/mineral/equipment_vendor/golem/New()
..()
component_parts = list()
component_parts += new /obj/item/circuitboard/mining_equipment_vendor/golem(null)
component_parts += new /obj/item/stock_parts/matter_bin(null)
component_parts += new /obj/item/stock_parts/matter_bin(null)
component_parts += new /obj/item/stock_parts/matter_bin(null)
component_parts += new /obj/item/stack/sheet/glass(null)
RefreshParts()
/obj/machinery/mineral/equipment_vendor/golem/Initialize()
. = ..()
desc += "\nIt seems a few selections have been added."
prize_list["Extra"] += list(
EQUIPMENT("Extra ID", /obj/item/card/id/golem, 250),
EQUIPMENT("Science Backpack", /obj/item/storage/backpack/science, 250),
EQUIPMENT("Full Toolbelt", /obj/item/storage/belt/utility/full/multitool, 250),
EQUIPMENT("Monkey Cube", /obj/item/reagent_containers/food/snacks/monkeycube, 250),
EQUIPMENT("Royal Cape of the Liberator", /obj/item/bedsheet/rd/royal_cape, 500),
EQUIPMENT("Grey Slime Extract", /obj/item/slime_extract/grey, 1000),
EQUIPMENT("KA Trigger Modification Kit", /obj/item/borg/upgrade/modkit/trigger_guard, 1000),
EQUIPMENT("Shuttle Console Board", /obj/item/circuitboard/shuttle/golem_ship, 2000),
EQUIPMENT("The Liberator's Legacy", /obj/item/storage/box/rndboards, 2000),
)
/**********************Mining Equipment Datum**************************/
/datum/data/mining_equipment
var/equipment_name = "generic"
var/equipment_path = null
var/cost = 0
/datum/data/mining_equipment/New(name, path, equipment_cost)
equipment_name = name
equipment_path = path
cost = equipment_cost
/**********************Mining Equipment Voucher**********************/
@@ -300,3 +359,5 @@
/obj/item/storage/backpack/duffel/mining_conscript/full/New()
..()
new /obj/item/card/id/mining_access_card(src)
#undef EQUIPMENT
+1
View File
@@ -220,6 +220,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
var/datum/wires/explosive/gibtonite/wires
/obj/item/twohanded/required/gibtonite/Destroy()
SStgui.close_uis(wires)
QDEL_NULL(wires)
return ..()
+1 -1
View File
@@ -780,4 +780,4 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set category = "Ghost"
var/datum/spawners_menu/menu = new /datum/spawners_menu(src)
menu.ui_interact(src)
menu.tgui_interact(src)
+2 -12
View File
@@ -1,20 +1,10 @@
/mob/dead/observer/say(var/message)
/mob/dead/observer/say(message)
message = sanitize(copytext(message, 1, MAX_MESSAGE_LEN))
if(!message)
return
log_ghostsay(message, src)
if(src.client)
if(src.client.prefs.muted & MUTE_DEADCHAT)
to_chat(src, "<span class='warning'>You cannot talk in deadchat (muted).</span>")
return
if(src.client.handle_spam_prevention(message,MUTE_DEADCHAT))
return
. = src.say_dead(message)
return say_dead(message)
/mob/dead/observer/emote(act, type, message, force)
+8 -1
View File
@@ -171,7 +171,14 @@
to_chat(src, heard)
/mob/proc/hear_holopad_talk(list/message_pieces, var/verb = "says", var/mob/speaker = null)
/mob/proc/hear_holopad_talk(list/message_pieces, verb = "says", mob/speaker = null)
if(sleeping || stat == UNCONSCIOUS)
hear_sleep(multilingual_to_message(message_pieces))
return
if(!can_hear())
return
var/message = combine_message(message_pieces, verb, speaker)
var/name = speaker.name
+1 -1
View File
@@ -186,7 +186,7 @@
if(prob(80))
new_name += " [pick(list("Hadii","Kaytam","Zhan-Khazan","Hharar","Njarir'Akhan"))]"
else
new_name += ..(gender,1)
new_name += " [..(gender,1)]"
return new_name
/datum/language/vulpkanin
@@ -301,14 +301,13 @@
else if(nutrition >= NUTRITION_LEVEL_FAT)
msg += "[p_they(TRUE)] [p_are()] quite chubby.\n"
if(!ismachineperson(src) && blood_volume < BLOOD_VOLUME_SAFE)
if(blood_volume < BLOOD_VOLUME_SAFE)
msg += "[p_they(TRUE)] [p_have()] pale skin.\n"
if(bleedsuppress)
msg += "[p_they(TRUE)] [p_are()] bandaged with something.\n"
else if(bleed_rate)
var/bleed_message = !ismachineperson(src) ? "bleeding" : "leaking"
msg += "<B>[p_they(TRUE)] [p_are()] [bleed_message]!</B>\n"
msg += "<B>[p_they(TRUE)] [p_are()] bleeding!</B>\n"
if(reagents.has_reagent("teslium"))
msg += "[p_they(TRUE)] [p_are()] emitting a gentle blue glow!\n"
@@ -572,41 +572,34 @@ emp_act
if(M.a_intent == INTENT_HARM)
if(w_uniform)
w_uniform.add_fingerprint(M)
var/damage = rand(15, 30)
var/damage = prob(90) ? 20 : 0
if(!damage)
playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1)
playsound(loc, 'sound/weapons/slashmiss.ogg', 50, TRUE, -1)
visible_message("<span class='danger'>[M] has lunged at [src]!</span>")
return 0
var/obj/item/organ/external/affecting = get_organ(ran_zone(M.zone_selected))
var/armor_block = run_armor_check(affecting, "melee")
var/armor_block = run_armor_check(affecting, "melee", armour_penetration = 10)
playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1)
playsound(loc, 'sound/weapons/slice.ogg', 25, TRUE, -1)
visible_message("<span class='danger'>[M] has slashed at [src]!</span>", \
"<span class='userdanger'>[M] has slashed at [src]!</span>")
apply_damage(damage, BRUTE, affecting, armor_block)
if(damage >= 25)
visible_message("<span class='danger'>[M] has wounded [src]!</span>", \
"<span class='userdanger'>[M] has wounded [src]!</span>")
apply_effect(4, WEAKEN, armor_block)
add_attack_logs(M, src, "Alien attacked")
add_attack_logs(M, src, "Alien attacked")
updatehealth("alien attack")
if(M.a_intent == INTENT_DISARM)
if(prob(80))
if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stun instead.
var/obj/item/I = get_active_hand()
if(I && unEquip(I))
playsound(loc, 'sound/weapons/slash.ogg', 25, TRUE, -1)
visible_message("<span class='danger'>[M] disarms [src]!</span>", "<span class='userdanger'>[M] disarms you!</span>", "<span class='hear'>You hear aggressive shuffling!</span>")
to_chat(M, "<span class='danger'>You disarm [src]!</span>")
else
var/obj/item/organ/external/affecting = get_organ(ran_zone(M.zone_selected))
playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1)
apply_effect(5, WEAKEN, run_armor_check(affecting, "melee"))
add_attack_logs(M, src, "Alien tackled")
visible_message("<span class='danger'>[M] has tackled down [src]!</span>")
else
if(prob(99)) //this looks fucking stupid but it was previously 'var/randn = rand(1, 100); if(randn <= 99)'
playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1)
drop_item()
visible_message("<span class='danger'>[M] disarmed [src]!</span>")
else
playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1)
visible_message("<span class='danger'>[M] has tried to disarm [src]!</span>")
/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M)
. = ..()
@@ -74,6 +74,10 @@
..()
H.gender = NEUTER
/datum/species/diona/on_species_loss(mob/living/carbon/human/H)
. = ..()
H.clear_alert("nolight")
/datum/species/diona/handle_reagents(mob/living/carbon/human/H, datum/reagent/R)
if(R.id == "glyphosate" || R.id == "atrazine")
H.adjustToxLoss(3) //Deal aditional damage
@@ -22,7 +22,7 @@
death_message = "gives a short series of shrill beeps, their chassis shuddering before falling limp, nonfunctional."
death_sounds = list('sound/voice/borg_deathsound.ogg') //I've made this a list in the event we add more sounds for dead robots.
species_traits = list(IS_WHITELISTED, NO_BREATHE, NO_SCAN, NO_INTORGANS, NO_PAIN, NO_DNA, RADIMMUNE, VIRUSIMMUNE, NO_GERMS, NO_DECAY, NOTRANSSTING) //Computers that don't decay? What a lie!
species_traits = list(IS_WHITELISTED, NO_BREATHE, NO_BLOOD, NO_SCAN, NO_INTORGANS, NO_PAIN, NO_DNA, RADIMMUNE, VIRUSIMMUNE, NO_GERMS, NO_DECAY, NOTRANSSTING) //Computers that don't decay? What a lie!
clothing_flags = HAS_UNDERWEAR | HAS_UNDERSHIRT | HAS_SOCKS
bodyflags = HAS_SKIN_COLOR | HAS_HEAD_MARKINGS | HAS_HEAD_ACCESSORY | ALL_RPARTS
dietflags = 0 //IPCs can't eat, so no diet
@@ -30,10 +30,6 @@
blood_color = "#1F181F"
flesh_color = "#AAAAAA"
blood_color = "#3C3C3C"
exotic_blood = "oil"
blood_damage_type = STAMINA
//Default styles for created mobs.
default_hair = "Blue IPC Screen"
dies_at_threshold = TRUE
-2
View File
@@ -351,8 +351,6 @@ proc/get_radio_key_from_channel(var/channel)
return
if(stat)
if(stat == DEAD)
return say_dead(message_pieces)
return
if(is_muzzled())
+92 -15
View File
@@ -50,7 +50,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/list/connected_robots = list()
var/aiRestorePowerRoutine = 0
//var/list/laws = list()
var/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list())
alarms_listend_for = list("Motion", "Fire", "Atmosphere", "Power", "Camera", "Burglar")
var/viewalerts = 0
var/icon/holo_icon//Default is assigned when AI is created.
var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye.
@@ -173,19 +173,19 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
add_language("Galactic Common", 1)
add_language("Sol Common", 1)
add_language("Tradeband", 1)
add_language("Neo-Russkiya", 0)
add_language("Gutter", 0)
add_language("Sinta'unathi", 0)
add_language("Siik'tajr", 0)
add_language("Canilunzt", 0)
add_language("Skrellian", 0)
add_language("Vox-pidgin", 0)
add_language("Orluum", 0)
add_language("Rootspeak", 0)
add_language("Neo-Russkiya", 1)
add_language("Gutter", 1)
add_language("Sinta'unathi", 1)
add_language("Siik'tajr", 1)
add_language("Canilunzt", 1)
add_language("Skrellian", 1)
add_language("Vox-pidgin", 1)
add_language("Orluum", 1)
add_language("Rootspeak", 1)
add_language("Trinary", 1)
add_language("Chittin", 0)
add_language("Bubblish", 0)
add_language("Clownish", 0)
add_language("Chittin", 1)
add_language("Bubblish", 1)
add_language("Clownish", 1)
if(!safety)//Only used by AIize() to successfully spawn an AI.
if(!B)//If there is no player/brain inside.
@@ -242,6 +242,46 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
return
show_borg_info()
/mob/living/silicon/ai/proc/ai_alerts()
var/list/dat = list("<HEAD><TITLE>Current Station Alerts</TITLE><META HTTP-EQUIV='Refresh' CONTENT='10'></HEAD><BODY>\n")
dat += "<A HREF='?src=[UID()];mach_close=aialerts'>Close</A><BR><BR>"
var/list/list/temp_alarm_list = SSalarm.alarms.Copy()
for(var/cat in temp_alarm_list)
if(!(cat in alarms_listend_for))
continue
dat += text("<B>[]</B><BR>\n", cat)
var/list/list/L = temp_alarm_list[cat].Copy()
for(var/alarm in L)
var/list/list/alm = L[alarm].Copy()
var/area_name = alm[1]
var/C = alm[2]
var/list/list/sources = alm[3].Copy()
for(var/thing in sources)
var/atom/A = locateUID(thing)
if(A && A.z != z)
L -= alarm
continue
dat += "<NOBR>"
if(C && islist(C))
var/dat2 = ""
for(var/cam in C)
var/obj/machinery/camera/I = locateUID(cam)
if(!QDELETED(I))
dat2 += text("[]<A HREF=?src=[UID()];switchcamera=[cam]>[]</A>", (dat2 == "") ? "" : " | ", I.c_tag)
dat += text("-- [] ([])", area_name, (dat2 != "") ? dat2 : "No Camera")
else
dat += text("-- [] (No Camera)", area_name)
if(sources.len > 1)
dat += text("- [] sources", sources.len)
dat += "</NOBR><BR>\n"
if(!L.len)
dat += "-- All Systems Nominal<BR>\n"
dat += "<BR>\n"
viewalerts = TRUE
var/dat_text = dat.Join("")
src << browse(dat_text, "window=aialerts&can_close=0")
/mob/living/silicon/ai/proc/show_borg_info()
stat(null, text("Connected cyborgs: [connected_robots.len]"))
for(var/thing in connected_robots)
@@ -612,7 +652,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
if(href_list["switchcamera"])
switchCamera(locate(href_list["switchcamera"])) in GLOB.cameranet.cameras
if(href_list["showalerts"])
subsystem_alarm_monitor()
ai_alerts()
if(href_list["show_paper"])
if(last_paper_seen)
src << browse(last_paper_seen, "window=show_paper")
@@ -787,12 +827,49 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
Bot.call_bot(src, waypoint)
/mob/living/silicon/ai/alarm_triggered(src, class, area/A, list/O, obj/alarmsource)
if(!(class in alarms_listend_for))
return
if(alarmsource.z != z)
return
if(stat == DEAD)
return TRUE
if(O)
var/obj/machinery/camera/C = locateUID(O[1])
if(O.len == 1 && !QDELETED(C) && C.can_use())
queueAlarm("--- [class] alarm detected in [A.name]! (<A HREF=?src=[UID()];switchcamera=[O[1]]>[C.c_tag]</A>)", class)
else if(O && O.len)
var/foo = 0
var/dat2 = ""
for(var/thing in O)
var/obj/machinery/camera/I = locateUID(thing)
if(!QDELETED(I))
dat2 += text("[]<A HREF=?src=[UID()];switchcamera=[thing]>[]</A>", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit...
foo = 1
queueAlarm(text ("--- [] alarm detected in []! ([])", class, A.name, dat2), class)
else
queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class)
else
queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class)
if(viewalerts)
ai_alerts()
/mob/living/silicon/ai/alarm_cancelled(src, class, area/A, obj/origin, cleared)
if(cleared)
if(!(class in alarms_listend_for))
return
if(origin.z != z)
return
queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0)
if(viewalerts)
ai_alerts()
/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C)
if(!tracking)
cameraFollow = null
if(!C || stat == DEAD) //C.can_use())
if(QDELETED(C) || stat == DEAD) //C.can_use())
return FALSE
if(!eyeobj)
@@ -132,8 +132,6 @@
sleep(50)
theAPC = null
process_queued_alarms()
/mob/living/silicon/ai/updatehealth(reason = "none given")
if(status_flags & GODMODE)
health = 100
@@ -563,6 +563,10 @@
var/obj/item/holder/H = ..()
if(!istype(H))
return
if(stat == DEAD)
H.icon = 'icons/mob/pai.dmi'
H.icon_state = "[chassis]_dead"
return
if(resting)
icon_state = "[chassis]"
resting = 0
@@ -312,8 +312,3 @@ proc/robot_healthscan(mob/user, mob/living/M)
to_chat(user, "[capitalize(O.name)]: <font color='red'>[O.damage]</font>")
if(!organ_found)
to_chat(user, "<span class='warning'>No prosthetics located.</span>")
if(ismachineperson(H))
to_chat(user, "<span class='notice'>Internal Fluid Level:[H.blood_volume]/[H.max_blood]</span>")
if(H.bleed_rate)
to_chat(user, "<span class='warning'>Warning:External component leak detected!</span>")
@@ -53,7 +53,7 @@
emote("deathgasp", force = TRUE)
if(module)
module.handle_death(gibbed)
module.handle_death(src, gibbed)
// Only execute the below if we successfully died
. = ..(gibbed)
@@ -59,7 +59,7 @@
// Disable the microphone wire on Drones
if(radio)
radio.wires.CutWireIndex(RADIO_WIRE_TRANSMIT)
radio.wires.cut(WIRE_RADIO_TRANSMIT)
if(camera && ("Robots" in camera.network))
camera.network.Add("Engineering")
@@ -13,7 +13,6 @@
handle_robot_cell()
process_locks()
update_items()
process_queued_alarms()
/mob/living/silicon/robot/proc/handle_robot_cell()
@@ -46,7 +45,7 @@
/mob/living/silicon/robot/proc/handle_equipment()
if(camera && !scrambledcodes)
if(stat == DEAD || wires.IsCameraCut())
if(stat == DEAD || wires.is_cut(WIRE_BORG_CAMERA))
camera.status = 0
else
camera.status = 1
+75 -22
View File
@@ -59,7 +59,6 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/ear_protection = 0
var/damage_protection = 0
var/emp_protection = FALSE
var/xeno_disarm_chance = 85
var/list/force_modules = list()
var/allow_rename = TRUE
@@ -72,11 +71,10 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/list/req_access
var/ident = 0
//var/list/laws = list()
var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list())
var/viewalerts = 0
var/modtype = "Default"
var/lower_mod = 0
var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N
var/datum/effect_system/spark_spread/spark_system //So they can initialize sparks whenever/N
var/jeton = 0
var/low_power_mode = 0 //whether the robot has no charge left.
var/weapon_lock = 0
@@ -112,7 +110,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/get_cell()
return cell
/mob/living/silicon/robot/New(loc, syndie = FALSE, unfinished = FALSE, alien = FALSE, mob/living/silicon/ai/ai_to_sync_to = null)
/mob/living/silicon/robot/New(loc, syndie = FALSE, unfinished = FALSE, alien = FALSE, connect_to_AI = TRUE, mob/living/silicon/ai/ai_to_sync_to = null)
spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src)
spark_system.attach(src)
@@ -134,13 +132,13 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
radio = new /obj/item/radio/borg(src)
common_radio = radio
init(ai_to_sync_to = ai_to_sync_to)
init(alien, connect_to_AI, ai_to_sync_to)
if(has_camera && !camera)
camera = new /obj/machinery/camera(src)
camera.c_tag = real_name
camera.network = list("SS13","Robots")
if(wires.IsCameraCut()) // 5 = BORG CAMERA
if(wires.is_cut(WIRE_BORG_CAMERA)) // 5 = BORG CAMERA
camera.status = 0
if(mmi == null)
@@ -172,18 +170,20 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
scanner = new(src)
scanner.Grant(src)
/mob/living/silicon/robot/proc/init(alien = FALSE, mob/living/silicon/ai/ai_to_sync_to = null)
/mob/living/silicon/robot/proc/init(alien, connect_to_AI = TRUE, mob/living/silicon/ai/ai_to_sync_to = null)
aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
make_laws()
additional_law_channels["Binary"] = ":b "
if(!connect_to_AI)
return
var/found_ai = ai_to_sync_to
if(!found_ai)
found_ai = select_active_ai_with_fewest_borgs()
if(found_ai)
lawupdate = 1
lawupdate = TRUE
connect_to_ai(found_ai)
else
lawupdate = 0
lawupdate = FALSE
playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
@@ -270,6 +270,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO
//Improved /N
/mob/living/silicon/robot/Destroy()
SStgui.close_uis(wires)
if(mmi && mind)//Safety for when a cyborg gets dust()ed. Or there is no MMI inside.
var/turf/T = get_turf(loc)//To hopefully prevent run time errors.
if(T) mmi.loc = T
@@ -543,6 +544,43 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
src.verbs -= GLOB.robot_verbs_default
src.verbs -= silicon_subsystems
/mob/living/silicon/robot/verb/cmd_robot_alerts()
set category = "Robot Commands"
set name = "Show Alerts"
if(usr.stat == DEAD)
to_chat(src, "<span class='userdanger'>Alert: You are dead.</span>")
return //won't work if dead
robot_alerts()
/mob/living/silicon/robot/proc/robot_alerts()
var/list/dat = list()
var/list/list/temp_alarm_list = SSalarm.alarms.Copy()
for(var/cat in temp_alarm_list)
if(!(cat in alarms_listend_for))
continue
dat += text("<B>[cat]</B><BR>\n")
var/list/list/L = temp_alarm_list[cat].Copy()
for(var/alarm in L)
var/list/list/alm = L[alarm].Copy()
var/list/list/sources = alm[3].Copy()
var/area_name = alm[1]
for(var/thing in sources)
var/atom/A = locateUID(thing)
if(A && A.z != z)
L -= alarm
continue
dat += "<NOBR>"
dat += text("-- [area_name]")
dat += "</NOBR><BR>\n"
if(!L.len)
dat += "-- All Systems Nominal<BR>\n"
dat += "<BR>\n"
var/datum/browser/alerts = new(usr, "robotalerts", "Current Station Alerts", 400, 410)
var/dat_text = dat.Join("")
alerts.set_content(dat_text)
alerts.open()
/mob/living/silicon/robot/proc/ionpulse()
if(!ionpulse_on)
return
@@ -604,6 +642,23 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/InCritical()
return low_power_mode
/mob/living/silicon/robot/alarm_triggered(src, class, area/A, list/O, obj/alarmsource)
if(!(class in alarms_listend_for))
return
if(alarmsource.z != z)
return
if(stat == DEAD)
return
queueAlarm(text("--- [class] alarm detected in [A.name]!"), class)
/mob/living/silicon/robot/alarm_cancelled(src, class, area/A, obj/origin, cleared)
if(cleared)
if(!(class in alarms_listend_for))
return
if(origin.z != z)
return
queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0)
/mob/living/silicon/robot/ex_act(severity)
switch(severity)
if(1.0)
@@ -795,7 +850,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
opened = FALSE
update_icons()
return
else if(wiresexposed && wires.IsAllCut())
else if(wiresexposed && wires.is_all_cut())
//Cell is out, wires are exposed, remove MMI, produce damaged chassis, baleet original mob.
if(!mmi)
to_chat(user, "[src] has no brain to remove.")
@@ -1023,10 +1078,6 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
src << browse(null, t1)
return 1
if(href_list["showalerts"])
subsystem_alarm_monitor()
return 1
if(href_list["mod"])
var/obj/item/O = locate(href_list["mod"])
if(istype(O) && (O.loc == src))
@@ -1041,6 +1092,11 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
activate_module(O)
installed_modules()
//Show alerts window if user clicked on "Show alerts" in chat
if(href_list["showalerts"])
robot_alerts()
return TRUE
if(href_list["deact"])
var/obj/item/O = locate(href_list["deact"])
if(activated(O))
@@ -1063,7 +1119,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
return 1
/mob/living/silicon/robot/proc/radio_menu()
radio.interact(src)//Just use the radio's Topic() instead of bullshit special-snowflake code
radio.interact(src)
/mob/living/silicon/robot/proc/control_headlamp()
if(stat || lamp_recharging || low_power_mode)
@@ -1233,7 +1289,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
/mob/living/silicon/robot/proc/SetLockdown(var/state = 1)
// They stay locked down if their wire is cut.
if(wires.LockedCut())
if(wires.is_cut(WIRE_BORG_LOCKED))
state = 1
if(state)
throw_alert("locked", /obj/screen/alert/locked)
@@ -1343,14 +1399,13 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
eye_protection = 2 // Immunity to flashes and the visual part of flashbangs
ear_protection = 1 // Immunity to the audio part of flashbangs
damage_protection = 10 // Reduce all incoming damage by this number
xeno_disarm_chance = 20
allow_rename = FALSE
modtype = "Commando"
faction = list("nanotrasen")
is_emaggable = FALSE
default_cell_type = /obj/item/stock_parts/cell/bluespace
/mob/living/silicon/robot/deathsquad/init(alien = FALSE, mob/living/silicon/ai/ai_to_sync_to = null)
/mob/living/silicon/robot/deathsquad/init(alien = FALSE, connect_to_AI = TRUE, mob/living/silicon/ai/ai_to_sync_to = null)
laws = new /datum/ai_laws/deathsquad
module = new /obj/item/robot_module/deathsquad(src)
aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
@@ -1380,7 +1435,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
var/eprefix = "Amber"
/mob/living/silicon/robot/ert/init(alien = FALSE, mob/living/silicon/ai/ai_to_sync_to = null)
/mob/living/silicon/robot/ert/init(alien = FALSE, connect_to_AI = TRUE, mob/living/silicon/ai/ai_to_sync_to = null)
laws = new /datum/ai_laws/ert_override
radio = new /obj/item/radio/borg/ert(src)
radio.recalculateChannels()
@@ -1413,7 +1468,6 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
damage_protection = 5 // Reduce all incoming damage by this number
eprefix = "Gamma"
magpulse = 1
xeno_disarm_chance = 40
/mob/living/silicon/robot/destroyer
@@ -1433,10 +1487,9 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
ear_protection = 1 // Immunity to the audio part of flashbangs
emp_protection = TRUE // Immunity to EMP, due to heavy shielding
damage_protection = 20 // Reduce all incoming damage by this number. Very high in the case of /destroyer borgs, since it is an admin-only borg.
xeno_disarm_chance = 10
default_cell_type = /obj/item/stock_parts/cell/bluespace
/mob/living/silicon/robot/destroyer/init(alien = FALSE, mob/living/silicon/ai/ai_to_sync_to = null)
/mob/living/silicon/robot/destroyer/init(alien = FALSE, connect_to_AI = TRUE, mob/living/silicon/ai/ai_to_sync_to = null)
aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
additional_law_channels["Binary"] = ":b "
laws = new /datum/ai_laws/deathsquad
@@ -2,19 +2,17 @@
if(M.a_intent == INTENT_DISARM)
if(!lying)
M.do_attack_animation(src, ATTACK_EFFECT_DISARM)
if(prob(xeno_disarm_chance))
Stun(7)
step(src, get_dir(M,src))
spawn(5)
step(src, get_dir(M,src))
add_attack_logs(M, src, "Alien pushed over")
playsound(loc, 'sound/weapons/pierce.ogg', 50, 1, -1)
visible_message("<span class='danger'>[M] has forced back [src]!</span>",\
"<span class='userdanger'>[M] has forced back [src]!</span>")
var/obj/item/I = get_active_hand()
if(I)
uneq_active()
visible_message("<span class='danger'>[M] disarmed [src]!</span>", "<span class='userdanger'>[M] has disabled [src]'s active module!</span>")
add_attack_logs(M, src, "alien disarmed")
else
playsound(loc, 'sound/weapons/slashmiss.ogg', 25, 1, -1)
visible_message("<span class='danger'>[M] took a swipe at [src]!</span>",\
"<span class='userdanger'>[M] took a swipe at [src]!</span>")
Stun(2)
step(src, get_dir(M,src))
add_attack_logs(M, src, "Alien pushed over")
visible_message("<span class='danger'>[M] forces back [src]!</span>", "<span class='userdanger'>[M] forces back [src]!</span>")
playsound(loc, 'sound/weapons/pierce.ogg', 50, TRUE, -1)
else
..()
return
@@ -114,7 +114,7 @@
/obj/item/robot_module/proc/handle_custom_removal(component_id, mob/living/user, obj/item/W)
return FALSE
/obj/item/robot_module/proc/handle_death(gibbed)
/obj/item/robot_module/proc/handle_death(mob/living/silicon/robot/R, gibbed)
return
/obj/item/robot_module/standard
@@ -255,7 +255,7 @@
fix_modules()
/obj/item/robot_module/engineering/handle_death()
/obj/item/robot_module/engineering/handle_death(mob/living/silicon/robot/R, gibbed)
var/obj/item/gripper/G = locate(/obj/item/gripper) in modules
if(G)
G.drop_gripped_item(silent = TRUE)
@@ -368,6 +368,11 @@
R.add_language("Clownish",1)
R.add_language("Neo-Russkiya", 1)
/obj/item/robot_module/butler/handle_death(mob/living/silicon/robot/R, gibbed)
var/obj/item/storage/bag/tray/cyborg/T = locate(/obj/item/storage/bag/tray/cyborg) in modules
if(istype(T))
T.drop_inventory(R)
/obj/item/robot_module/miner
name = "miner robot module"
@@ -623,7 +628,7 @@
..()
/obj/item/robot_module/drone/handle_death()
/obj/item/robot_module/drone/handle_death(mob/living/silicon/robot/R, gibbed)
var/obj/item/gripper/G = locate(/obj/item/gripper) in modules
if(G)
G.drop_gripped_item(silent = TRUE)
+90 -63
View File
@@ -11,9 +11,11 @@
var/list/stating_laws = list()// Channels laws are currently being stated on
var/list/alarms_to_show = list()
var/list/alarms_to_clear = list()
var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0)
var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0)
var/list/alarms_listend_for = list("Motion", "Fire", "Atmosphere", "Power", "Camera")
//var/list/hud_list[10]
var/list/speech_synthesizer_langs = list() //which languages can be vocalized by the speech synthesizer
var/list/alarm_handlers = list() // List of alarm handlers this silicon is registered to
var/designation = ""
var/obj/item/camera/siliconcam/aiCamera = null //photography
//Used in say.dm, allows for pAIs to have different say flavor text, as well as silicons, although the latter is not implemented.
@@ -25,9 +27,6 @@
//var/sensor_mode = 0 //Determines the current HUD.
var/next_alarm_notice
var/list/datum/alarm/queued_alarms = new()
hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD)
@@ -46,6 +45,8 @@
diag_hud_set_health()
add_language("Galactic Common")
init_subsystems()
RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, .proc/alarm_triggered)
RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, .proc/alarm_cancelled)
/mob/living/silicon/med_hud_set_health()
return //we use a different hud
@@ -55,10 +56,93 @@
/mob/living/silicon/Destroy()
GLOB.silicon_mob_list -= src
for(var/datum/alarm_handler/AH in alarm_handlers)
AH.unregister(src)
return ..()
/mob/living/silicon/proc/alarm_triggered(src, class, area/A, list/O, obj/alarmsource)
return
/mob/living/silicon/proc/alarm_cancelled(src, class, area/A, obj/origin, cleared)
return
/mob/living/silicon/proc/queueAlarm(message, type, incoming = TRUE)
var/in_cooldown = (alarms_to_show.len > 0 || alarms_to_clear.len > 0)
if(incoming)
alarms_to_show += message
alarm_types_show[type] += 1
else
alarms_to_clear += message
alarm_types_clear[type] += 1
if(in_cooldown)
return
addtimer(CALLBACK(src, .proc/show_alarms), 3 SECONDS)
/mob/living/silicon/proc/show_alarms()
if(alarms_to_show.len < 5)
for(var/msg in alarms_to_show)
to_chat(src, msg)
else if(length(alarms_to_show))
var/list/msg = list("--- ")
if(alarm_types_show["Burglar"])
msg += "BURGLAR: [alarm_types_show["Burglar"]] alarms detected. - "
if(alarm_types_show["Motion"])
msg += "MOTION: [alarm_types_show["Motion"]] alarms detected. - "
if(alarm_types_show["Fire"])
msg += "FIRE: [alarm_types_show["Fire"]] alarms detected. - "
if(alarm_types_show["Atmosphere"])
msg += "ATMOSPHERE: [alarm_types_show["Atmosphere"]] alarms detected. - "
if(alarm_types_show["Power"])
msg += "POWER: [alarm_types_show["Power"]] alarms detected. - "
if(alarm_types_show["Camera"])
msg += "CAMERA: [alarm_types_show["Camera"]] alarms detected. - "
msg += "<A href=?src=[UID()];showalerts=1'>\[Show Alerts\]</a>"
var/msg_text = msg.Join("")
to_chat(src, msg_text)
if(alarms_to_clear.len < 3)
for(var/msg in alarms_to_clear)
to_chat(src, msg)
else if(alarms_to_clear.len)
var/list/msg = list("--- ")
if(alarm_types_clear["Motion"])
msg += "MOTION: [alarm_types_clear["Motion"]] alarms cleared. - "
if(alarm_types_clear["Fire"])
msg += "FIRE: [alarm_types_clear["Fire"]] alarms cleared. - "
if(alarm_types_clear["Atmosphere"])
msg += "ATMOSPHERE: [alarm_types_clear["Atmosphere"]] alarms cleared. - "
if(alarm_types_clear["Power"])
msg += "POWER: [alarm_types_clear["Power"]] alarms cleared. - "
if(alarm_types_show["Camera"])
msg += "CAMERA: [alarm_types_clear["Camera"]] alarms cleared. - "
msg += "<A href=?src=[UID()];showalerts=1'>\[Show Alerts\]</a>"
var/msg_text = msg.Join("")
to_chat(src, msg_text)
alarms_to_show.Cut()
alarms_to_clear.Cut()
for(var/key in alarm_types_show)
alarm_types_show[key] = 0
for(var/key in alarm_types_clear)
alarm_types_clear[key] = 0
/mob/living/silicon/rename_character(oldname, newname)
// we actually don't want it changing minds and stuff
if(!newname)
@@ -283,63 +367,6 @@
if("Disable")
to_chat(src, "Sensor augmentations disabled.")
/mob/living/silicon/proc/receive_alarm(var/datum/alarm_handler/alarm_handler, var/datum/alarm/alarm, was_raised)
if(!next_alarm_notice)
next_alarm_notice = world.time + 10 SECONDS
var/list/alarms = queued_alarms[alarm_handler]
if(was_raised)
// Raised alarms are always set
alarms[alarm] = 1
else
// Alarms that were raised but then cleared before the next notice are instead removed
if(alarm in alarms)
alarms -= alarm
// And alarms that have only been cleared thus far are set as such
else
alarms[alarm] = -1
/mob/living/silicon/proc/process_queued_alarms()
if(next_alarm_notice && (world.time > next_alarm_notice))
next_alarm_notice = 0
var/alarm_raised = 0
for(var/datum/alarm_handler/AH in queued_alarms)
var/list/alarms = queued_alarms[AH]
var/reported = 0
for(var/datum/alarm/A in alarms)
if(alarms[A] == 1)
if(!reported)
reported = 1
to_chat(src, "<span class='warning'>--- [AH.category] Detected ---</span>")
raised_alarm(A)
for(var/datum/alarm_handler/AH in queued_alarms)
var/list/alarms = queued_alarms[AH]
var/reported = 0
for(var/datum/alarm/A in alarms)
if(alarms[A] == -1)
if(!reported)
reported = 1
to_chat(src, "<span class='notice'>--- [AH.category] Cleared ---</span>")
to_chat(src, "\The [A.alarm_name()].")
if(alarm_raised)
to_chat(src, "<A HREF=?src=[UID()];showalerts=1>\[Show Alerts\]</A>")
for(var/datum/alarm_handler/AH in queued_alarms)
var/list/alarms = queued_alarms[AH]
alarms.Cut()
/mob/living/silicon/proc/raised_alarm(var/datum/alarm/A)
to_chat(src, "[A.alarm_name()]!")
/mob/living/silicon/ai/raised_alarm(var/datum/alarm/A)
var/cameratext = ""
for(var/obj/machinery/camera/C in A.cameras())
cameratext += "[(cameratext == "")? "" : "|"]<A HREF=?src=[UID()];switchcamera=\ref[C]>[C.c_tag]</A>"
to_chat(src, "[A.alarm_name()]! ([(cameratext)? cameratext : "No Camera"])")
/mob/living/silicon/adjustToxLoss(var/amount)
return STATUS_UPDATE_NONE
@@ -3,11 +3,10 @@
/mob/living/silicon/attack_alien(mob/living/carbon/alien/humanoid/M)
if(..()) //if harm or disarm intent
var/damage = rand(10, 20)
var/damage = 20
if(prob(90))
playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1)
visible_message("<span class='danger'>[M] has slashed at [src]!</span>", \
"<span class='userdanger'>[M] has slashed at [src]!</span>")
visible_message("<span class='danger'>[M] has slashed at [src]!</span>", "<span class='userdanger'>[M] has slashed at [src]!</span>")
if(prob(8))
flash_eyes(affect_silicon = 1)
add_attack_logs(M, src, "Alien attacked")
@@ -64,6 +63,6 @@
else
M.do_attack_animation(src, ATTACK_EFFECT_PUNCH)
playsound(loc, 'sound/effects/bang.ogg', 10, 1)
visible_message("<span class='danger'>[M] punches [src], but doesn't leave a dent.</span>", \
"<span class='userdanger'>[M] punches [src], but doesn't leave a dent.!</span>")
visible_message("<span class='notice'>[M] punches [src], but doesn't leave a dent.</span>", \
"<span class='notice'>[M] punches [src], but doesn't leave a dent.</span>")
return FALSE
@@ -8,13 +8,11 @@
/mob/living/silicon
var/list/silicon_subsystems = list(
/mob/living/silicon/proc/subsystem_alarm_monitor,
/mob/living/silicon/proc/subsystem_law_manager
)
/mob/living/silicon/ai
silicon_subsystems = list(
/mob/living/silicon/proc/subsystem_alarm_monitor,
/mob/living/silicon/proc/subsystem_atmos_control,
/mob/living/silicon/proc/subsystem_crew_monitor,
/mob/living/silicon/proc/subsystem_law_manager,
@@ -23,7 +21,6 @@
/mob/living/silicon/robot/drone
silicon_subsystems = list(
/mob/living/silicon/proc/subsystem_alarm_monitor,
/mob/living/silicon/proc/subsystem_law_manager,
/mob/living/silicon/proc/subsystem_power_monitor
)
@@ -32,30 +29,11 @@
register_alarms = 0
/mob/living/silicon/proc/init_subsystems()
alarm_monitor = new(src)
atmos_control = new(src)
crew_monitor = new(src)
law_manager = new(src)
power_monitor = new(src)
if(!register_alarms)
return
var/list/register_to = list(SSalarms.atmosphere_alarm, SSalarms.burglar_alarm, SSalarms.camera_alarm, SSalarms.fire_alarm, SSalarms.motion_alarm, SSalarms.power_alarm)
for(var/datum/alarm_handler/AH in register_to)
AH.register(src, /mob/living/silicon/proc/receive_alarm)
queued_alarms[AH] = list() // Makes sure alarms remain listed in consistent order
alarm_handlers |= AH
/********************
* Alarm Monitor *
********************/
/mob/living/silicon/proc/subsystem_alarm_monitor()
set name = "Alarm Monitor"
set category = "Subsystems"
alarm_monitor.ui_interact(usr, state = GLOB.self_state)
/********************
* Atmos Control *
********************/
@@ -42,13 +42,18 @@
/mob/living/simple_animal/attack_alien(mob/living/carbon/alien/humanoid/M)
if(..()) //if harm or disarm intent.
var/damage = rand(15, 30)
visible_message("<span class='danger'>[M] has slashed at [src]!</span>", \
"<span class='userdanger'>[M] has slashed at [src]!</span>")
playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1)
add_attack_logs(M, src, "Alien attacked")
attack_threshold_check(damage)
return
if(M.a_intent == INTENT_DISARM)
playsound(loc, 'sound/weapons/pierce.ogg', 25, TRUE, -1)
visible_message("<span class='danger'>[M] [response_disarm] [name]!</span>", "<span class='userdanger'>[M] [response_disarm] you!</span>")
add_attack_logs(M, src, "Alien disarmed")
else
var/damage = rand(15, 30)
visible_message("<span class='danger'>[M] has slashed at [src]!</span>", \
"<span class='userdanger'>[M] has slashed at [src]!</span>")
playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1)
add_attack_logs(M, src, "Alien attacked")
attack_threshold_check(damage)
return TRUE
/mob/living/simple_animal/attack_larva(mob/living/carbon/alien/larva/L)
if(..()) //successful larva bite
@@ -94,7 +94,6 @@
hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_PATH_HUD = HUD_LIST_LIST)//Diagnostic HUD views
/obj/item/radio/headset/bot
subspace_transmission = 1
requires_tcomms = FALSE
canhear_range = 0
@@ -66,6 +66,7 @@
RegisterSignal(src, COMSIG_CROSSED_MOVABLE, .proc/human_squish_check)
/mob/living/simple_animal/bot/mulebot/Destroy()
SStgui.close_uis(wires)
unload(0)
QDEL_NULL(wires)
QDEL_NULL(cell)
@@ -142,7 +143,7 @@
if(open)
icon_state="mulebot-hatch"
else
icon_state = "mulebot[!wires.MobAvoid()]"
icon_state = "mulebot[wires.is_cut(WIRE_MOB_AVOIDANCE)]"
overlays.Cut()
if(load && !ismob(load))//buckling handles the mob offsets
load.pixel_y = initial(load.pixel_y) + 9
@@ -158,9 +159,9 @@
qdel(src)
if(2)
for(var/i = 1; i < 3; i++)
wires.RandomCut()
wires.cut_random()
if(3)
wires.RandomCut()
wires.cut_random()
return
/mob/living/simple_animal/bot/mulebot/bullet_act(obj/item/projectile/Proj)
@@ -169,7 +170,7 @@
unload(0)
if(prob(25))
visible_message("<span class='danger'>Something shorts out inside [src]!</span>")
wires.RandomCut()
wires.cut_random()
/mob/living/simple_animal/bot/mulebot/Topic(href, list/href_list)
if(..())
@@ -318,7 +319,7 @@
// returns true if the bot has power
/mob/living/simple_animal/bot/mulebot/proc/has_power()
return !open && cell && cell.charge > 0 && wires.HasPower()
return !open && cell && cell.charge > 0 && !wires.is_cut(WIRE_MAIN_POWER1) && !wires.is_cut(WIRE_MAIN_POWER2)
/mob/living/simple_animal/bot/mulebot/proc/buzz(type)
switch(type)
@@ -362,7 +363,7 @@
if(istype(AM,/obj/structure/closet/crate))
CRATE = AM
else
if(wires.LoadCheck())
if(!wires.is_cut(WIRE_LOADCHECK))
buzz(SIGH)
return // if not hacked, only allow crates to be loaded
@@ -459,8 +460,7 @@
on = 0
return
if(on)
var/speed = (wires.Motor1() ? 1 : 0) + (wires.Motor2() ? 2 : 0)
// to_chat(world, "speed: [speed]")
var/speed = (wires.is_cut(WIRE_MOTOR1) ? 1 : 0) + (wires.is_cut(WIRE_MOTOR2) ? 2 : 0)
var/num_steps = 0
switch(speed)
if(0)
@@ -624,7 +624,7 @@
// not loaded
if(auto_pickup) // find a crate
var/atom/movable/AM
if(wires.LoadCheck()) // if hacked, load first unanchored thing we find
if(wires.is_cut(WIRE_LOADCHECK)) // if hacked, load first unanchored thing we find
for(var/atom/movable/A in get_step(loc, loaddir))
if(!A.anchored)
AM = A
@@ -672,7 +672,7 @@
// called when bot bumps into anything
/mob/living/simple_animal/bot/mulebot/Bump(atom/obs)
if(!wires.MobAvoid()) // usually just bumps, but if avoidance disabled knock over mobs
if(wires.is_cut(WIRE_MOB_AVOIDANCE)) // usually just bumps, but if avoidance disabled knock over mobs
var/mob/M = obs
if(ismob(M))
if(istype(M,/mob/living/silicon/robot))
@@ -736,8 +736,8 @@
..()
/mob/living/simple_animal/bot/mulebot/receive_signal(datum/signal/signal)
if(!wires.RemoteRX() || ..())
return 1
if(wires.is_cut(WIRE_REMOTE_RX) || ..())
return TRUE
var/recv = signal.data["command"]
@@ -772,7 +772,7 @@
// send a radio signal with multiple data key/values
/mob/living/simple_animal/bot/mulebot/post_signal_multiple(var/freq, var/list/keyval)
if(!wires.RemoteTX())
if(wires.is_cut(WIRE_REMOTE_TX))
return
..()
@@ -803,7 +803,7 @@
//Update navigation data. Called when commanded to deliver, return home, or a route update is needed...
/mob/living/simple_animal/bot/mulebot/proc/get_nav()
if(!on || !wires.BeaconRX())
if(!on || wires.is_cut(WIRE_BEACON_RX))
return
for(var/obj/machinery/navbeacon/NB in GLOB.deliverybeacons)
@@ -47,7 +47,7 @@
/mob/living/simple_animal/hostile/poison/terror_spider/purple/Life(seconds, times_fired)
. = ..()
if(.) // if mob is NOT dead
if(stat != DEAD) // Can't use if(.) for this due to the fact it can sometimes return FALSE even when mob is alive.
if(!degenerate && spider_myqueen)
if(dcheck_counter >= 10)
dcheck_counter = 0
@@ -66,7 +66,7 @@
/mob/living/simple_animal/hostile/poison/terror_spider/queen/Life(seconds, times_fired)
. = ..()
if(.) // if mob is NOT dead
if(stat != DEAD) // Can't use if(.) for this due to the fact it can sometimes return FALSE even when mob is alive.
if(ckey && canlay < 12 && hasnested) // max 12 eggs worth stored at any one time, realistically that's tons.
if(world.time > (spider_lastspawn + spider_spawnfrequency))
if(eggslaid >= 20)
@@ -298,7 +298,7 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list)
/mob/living/simple_animal/hostile/poison/terror_spider/Life(seconds, times_fired)
. = ..()
if(!.) // if mob is dead
if(stat == DEAD) // Can't use if(.) for this due to the fact it can sometimes return FALSE even when mob is alive.
if(prob(2))
// 2% chance every cycle to decompose
visible_message("<span class='notice'>\The dead body of the [src] decomposes!</span>")
+16 -7
View File
@@ -44,18 +44,27 @@
usr.emote(message)
/mob/proc/say_dead(var/message)
if(!(client && client.holder))
if(!config.dsay_allowed)
to_chat(src, "<span class='danger'>Deadchat is globally muted.</span>")
/mob/proc/say_dead(message)
if(client)
if(!client.holder)
if(!config.dsay_allowed)
to_chat(src, "<span class='danger'>Deadchat is globally muted.</span>")
return
if(client.prefs.muted & MUTE_DEADCHAT)
to_chat(src, "<span class='warning'>You cannot talk in deadchat (muted).</span>")
return
if(client && !(client.prefs.toggles & CHAT_DEAD))
to_chat(usr, "<span class='danger'>You have deadchat muted.</span>")
return
if(!(client.prefs.toggles & CHAT_DEAD))
to_chat(src, "<span class='danger'>You have deadchat muted.</span>")
return
if(client.handle_spam_prevention(message, MUTE_DEADCHAT))
return
say_dead_direct("[pick("complains", "moans", "whines", "laments", "blubbers", "salts")], <span class='message'>\"[message]\"</span>", src)
create_log(DEADCHAT_LOG, message)
log_ghostsay(message, src)
/mob/proc/say_understands(var/mob/other, var/datum/language/speaking = null)
if(stat == DEAD)
+22 -12
View File
@@ -44,26 +44,37 @@
//human -> robot
/mob/living/carbon/human/proc/Robotize()
/**
For transforming humans into robots (cyborgs).
Arguments:
* cell_type: A type path of the cell the new borg should receive.
* connect_to_default_AI: TRUE if you want /robot/New() to handle connecting the borg to the AI with the least borgs.
* AI: A reference to the AI we want to connect to.
*/
/mob/living/carbon/human/proc/Robotize(cell_type = null, connect_to_default_AI = TRUE, mob/living/silicon/ai/AI = null)
if(notransform)
return
for(var/obj/item/W in src)
unEquip(W)
regenerate_icons()
notransform = 1
canmove = 0
icon = null
invisibility = 101
for(var/t in bodyparts)
qdel(t)
for(var/i in internal_organs)
qdel(i)
var/mob/living/silicon/robot/O = new /mob/living/silicon/robot( loc )
// Creating a new borg here will connect them to a default AI and notify that AI, if `connect_to_default_AI` is TRUE.
var/mob/living/silicon/robot/O = new /mob/living/silicon/robot(loc, connect_to_AI = connect_to_default_AI)
// cyborgs produced by Robotize get an automatic power cell
O.cell = new /obj/item/stock_parts/cell/high(O)
// If `AI` is passed in, we want to connect to that AI specifically.
if(AI)
O.lawupdate = TRUE
O.connect_to_ai(AI)
if(!cell_type)
O.cell = new /obj/item/stock_parts/cell/high(O)
else
O.cell = new cell_type(O)
O.gender = gender
O.invisibility = 0
@@ -77,9 +88,8 @@
else
O.key = key
O.loc = loc
O.forceMove(loc)
O.job = "Cyborg"
O.notify_ai(1)
if(O.mind && O.mind.assigned_role == "Cyborg")
if(O.mind.role_alt_title == "Robot")
@@ -35,7 +35,7 @@
/obj/machinery/modular_computer/console/preset/engineering/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/power_monitor())
hard_drive.store_file(new/datum/computer_file/program/alarm_monitor())
// hard_drive.store_file(new/datum/computer_file/program/alarm_monitor()) //TO-DO:TGUI--Uncomment Modular computers
hard_drive.store_file(new/datum/computer_file/program/supermatter_monitor())
// ===== RESEARCH CONSOLE =====
@@ -63,7 +63,6 @@
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/chatclient())
hard_drive.store_file(new/datum/computer_file/program/card_mod())
hard_drive.store_file(new/datum/computer_file/program/comm())
// ===== CIVILIAN CONSOLE =====
/obj/machinery/modular_computer/console/preset/civilian
@@ -1,385 +0,0 @@
/datum/computer_file/program/comm
filename = "comm"
filedesc = "Command and communications"
program_icon_state = "comm"
extended_desc = "Used to command and control the station. Can relay long-range communications. This program can not be run on tablet computers."
required_access = ACCESS_HEADS
requires_ntnet = 1
size = 12
usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP
network_destination = "station long-range communication array"
var/authenticated = COMM_AUTHENTICATION_NONE
var/list/messagetitle = list()
var/list/messagetext = list()
var/currmsg = 0
var/aicurrmsg = 0
var/menu_state = COMM_SCREEN_MAIN
var/ai_menu_state = COMM_SCREEN_MAIN
var/message_cooldown = 0
var/centcomm_message_cooldown = 0
var/tmp_alertlevel = 0
var/stat_msg1
var/stat_msg2
var/display_type="blank"
var/datum/announcement/priority/crew_announcement = new
/datum/computer_file/program/comm/New()
GLOB.shuttle_caller_list += src
..()
crew_announcement.newscast = 0
/datum/computer_file/program/comm/Destroy()
GLOB.shuttle_caller_list -= src
SSshuttle.autoEvac()
return ..()
/datum/computer_file/program/comm/proc/is_authenticated(mob/user, loud = 1)
if(authenticated == COMM_AUTHENTICATION_MAX)
return COMM_AUTHENTICATION_MAX
else if(user.can_admin_interact())
return COMM_AUTHENTICATION_MAX
else if(authenticated)
return COMM_AUTHENTICATION_MIN
else
if(loud)
to_chat(user, "<span class='warning'>Access denied.</span>")
return COMM_AUTHENTICATION_NONE
/datum/computer_file/program/comm/proc/change_security_level(mob/user, new_level)
tmp_alertlevel = new_level
var/old_level = GLOB.security_level
if(!tmp_alertlevel)
tmp_alertlevel = SEC_LEVEL_GREEN
if(tmp_alertlevel < SEC_LEVEL_GREEN)
tmp_alertlevel = SEC_LEVEL_GREEN
if(tmp_alertlevel > SEC_LEVEL_BLUE)
tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this
set_security_level(tmp_alertlevel)
if(GLOB.security_level != old_level)
log_game("[key_name(user)] has changed the security level to [get_security_level()].")
message_admins("[key_name_admin(user)] has changed the security level to [get_security_level()].")
switch(GLOB.security_level)
if(SEC_LEVEL_GREEN)
feedback_inc("alert_comms_green", 1)
if(SEC_LEVEL_BLUE)
feedback_inc("alert_comms_blue", 1)
tmp_alertlevel = 0
/datum/computer_file/program/comm/proc/setCurrentMessage(mob/user, value)
if(isAI(user) || isrobot(user))
aicurrmsg = value
else
currmsg = value
/datum/computer_file/program/comm/proc/getCurrentMessage(mob/user)
if(isAI(user) || isrobot(user))
return aicurrmsg
else
return currmsg
/datum/computer_file/program/comm/proc/setMenuState(mob/user, value)
if(isAI(user) || isrobot(user))
ai_menu_state=value
else
menu_state=value
/datum/computer_file/program/comm/proc/getMenuState(mob/user)
if(isAI(user) || isrobot(user))
return ai_menu_state
else
return menu_state
/datum/computer_file/program/comm/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui)
if(!ui)
var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers)
assets.send(user)
ui = new(user, src, ui_key, "comm_program.tmpl", "Command and communications program", 575, 500)
ui.set_layout_key("program")
ui.open()
/datum/computer_file/program/comm/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
var/list/data = get_header_data()
data["is_ai"] = isAI(user) || isrobot(user)
data["menu_state"] = data["is_ai"] ? ai_menu_state : menu_state
data["emagged"] = computer ? computer.emagged : null
data["authenticated"] = is_authenticated(user, 0)
data["screen"] = getMenuState(usr)
data["stat_display"] = list(
"type" = display_type,
"line_1" = (stat_msg1 ? stat_msg1 : "-----"),
"line_2" = (stat_msg2 ? stat_msg2 : "-----"),
"presets" = list(
list("name" = "blank", "label" = "Clear", "desc" = "Blank slate"),
list("name" = "shuttle", "label" = "Shuttle ETA", "desc" = "Display how much time is left."),
list("name" = "message", "label" = "Message", "desc" = "A custom message.")
),
"alerts"=list(
list("alert" = "default", "label" = "Nanotrasen", "desc" = "Oh god."),
list("alert" = "redalert", "label" = "Red Alert", "desc" = "Nothing to do with communists."),
list("alert" = "lockdown", "label" = "Lockdown", "desc" = "Let everyone know they're on lockdown."),
list("alert" = "biohazard", "label" = "Biohazard", "desc" = "Great for virus outbreaks and parties."),
)
)
data["security_level"] = GLOB.security_level
data["str_security_level"] = capitalize(get_security_level())
data["levels"] = list(
list("id" = SEC_LEVEL_GREEN, "name" = "Green"),
list("id" = SEC_LEVEL_BLUE, "name" = "Blue"),
)
var/list/msg_data = list()
for(var/i = 1; i <= messagetext.len; i++)
msg_data.Add(list(list("title" = messagetitle[i], "body" = messagetext[i], "id" = i)))
data["messages"] = msg_data
if((data["is_ai"] && aicurrmsg) || (!data["is_ai"] && currmsg))
data["current_message"] = data["is_ai"] ? messagetext[aicurrmsg] : messagetext[currmsg]
data["current_message_title"] = data["is_ai"] ? messagetitle[aicurrmsg] : messagetitle[currmsg]
data["lastCallLoc"] = SSshuttle.emergencyLastCallLoc ? format_text(SSshuttle.emergencyLastCallLoc.name) : null
var/shuttle[0]
switch(SSshuttle.emergency.mode)
if(SHUTTLE_IDLE, SHUTTLE_RECALL)
shuttle["callStatus"] = 2 //#define
else
shuttle["callStatus"] = 1
if(SSshuttle.emergency.mode == SHUTTLE_CALL)
var/timeleft = SSshuttle.emergency.timeLeft()
shuttle["eta"] = "[timeleft / 60 % 60]:[add_zero(num2text(timeleft % 60), 2)]"
data["shuttle"] = shuttle
return data
/datum/computer_file/program/comm/Topic(href, href_list)
if(..())
return 1
var/turf/T = get_turf(computer)
if(!is_secure_level(T.z))
to_chat(usr, "<span class='warning'>Unable to establish a connection: You're too far away from the station!</span>")
return 1
if(href_list["PRG_login"])
if(!ishuman(usr))
to_chat(usr, "<span class='warning'>Access denied.</span>")
return
var/list/access = usr.get_access()
if(ACCESS_HEADS in access)
authenticated = COMM_AUTHENTICATION_MIN
if(ACCESS_CAPTAIN in access)
authenticated = COMM_AUTHENTICATION_MAX
var/mob/living/carbon/human/H = usr
var/obj/item/card/id = H.get_idcard(TRUE)
if(istype(id))
crew_announcement.announcer = GetNameAndAssignmentFromId(id)
SSnanoui.update_uis(src)
return 1
if(href_list["PRG_logout"])
authenticated = COMM_AUTHENTICATION_NONE
crew_announcement.announcer = ""
setMenuState(usr, COMM_SCREEN_MAIN)
SSnanoui.update_uis(src)
return 1
if(is_authenticated(usr))
switch(href_list["PRG_operation"])
if("main")
setMenuState(usr, COMM_SCREEN_MAIN)
if("changeseclevel")
setMenuState(usr,COMM_SCREEN_SECLEVEL)
if("newalertlevel")
if(isAI(usr) || isrobot(usr))
to_chat(usr, "<span class='warning'>Firewalls prevent you from changing the alert level.</span>")
return 1
else if(usr.can_admin_interact())
change_security_level(usr, text2num(href_list["level"]))
return 1
else if(!ishuman(usr))
to_chat(usr, "<span class='warning'>Security measures prevent you from changing the alert level.</span>")
return 1
var/mob/living/carbon/human/L = usr
var/obj/item/card = L.get_active_hand()
var/obj/item/card/id/I = (card && card.GetID()) || L.wear_id || L.wear_pda
if(istype(I, /obj/item/pda))
var/obj/item/pda/pda = I
I = pda.id
if(I && istype(I))
if(ACCESS_CAPTAIN in I.access)
change_security_level(usr, text2num(href_list["level"]))
else
to_chat(usr, "<span class='warning'>You are not authorized to do this.</span>")
setMenuState(usr, COMM_SCREEN_MAIN)
else
to_chat(usr, "<span class='warning'>You need to swipe your ID.</span>")
if("announce")
if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)
if(message_cooldown)
to_chat(usr, "<span class='warning'>Please allow at least one minute to pass between announcements.</span>")
SSnanoui.update_uis(src)
return 1
var/input = input(usr, "Please write a message to announce to the station crew.", "Priority Announcement")
if(!input || message_cooldown || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
SSnanoui.update_uis(src)
return 1
crew_announcement.Announce(input)
message_cooldown = 1
spawn(600)//One minute cooldown
message_cooldown = 0
if("callshuttle")
var/input = clean_input("Please enter the reason for calling the shuttle.", "Shuttle Call Reason.","")
if(!input || ..() || !is_authenticated(usr))
SSnanoui.update_uis(src)
return 1
call_shuttle_proc(usr, input)
if(SSshuttle.emergency.timer)
post_status("shuttle")
setMenuState(usr, COMM_SCREEN_MAIN)
if("cancelshuttle")
if(isAI(usr) || isrobot(usr))
to_chat(usr, "<span class='warning'>Firewalls prevent you from recalling the shuttle.</span>")
SSnanoui.update_uis(src)
return 1
var/response = alert("Are you sure you wish to recall the shuttle?", "Confirm", "Yes", "No")
if(response == "Yes")
cancel_call_proc(usr)
if(SSshuttle.emergency.timer)
post_status("shuttle")
setMenuState(usr, COMM_SCREEN_MAIN)
if("messagelist")
currmsg = 0
if(href_list["msgid"])
setCurrentMessage(usr, text2num(href_list["msgid"]))
setMenuState(usr,COMM_SCREEN_MESSAGES)
if("delmessage")
if(href_list["msgid"])
currmsg = text2num(href_list["msgid"])
var/response = alert("Are you sure you wish to delete this message?", "Confirm", "Yes", "No")
if(response == "Yes")
if(currmsg)
var/id = getCurrentMessage()
var/title = messagetitle[id]
var/text = messagetext[id]
messagetitle.Remove(title)
messagetext.Remove(text)
if(currmsg == id)
currmsg = 0
if(aicurrmsg == id)
aicurrmsg = 0
setMenuState(usr,COMM_SCREEN_MESSAGES)
if("status")
setMenuState(usr,COMM_SCREEN_STAT)
// Status display stuff
if("setstat")
display_type=href_list["statdisp"]
switch(display_type)
if("message")
post_status("message", stat_msg1, stat_msg2, usr)
if("alert")
post_status("alert", href_list["alert"], user = usr)
else
post_status(href_list["statdisp"], user = usr)
setMenuState(usr, COMM_SCREEN_STAT)
if("setmsg1")
stat_msg1 = clean_input("Line 1", "Enter Message Text", stat_msg1)
setMenuState(usr, COMM_SCREEN_STAT)
if("setmsg2")
stat_msg2 = clean_input("Line 2", "Enter Message Text", stat_msg2)
setMenuState(usr, COMM_SCREEN_STAT)
if("nukerequest")
if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)
if(centcomm_message_cooldown)
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
SSnanoui.update_uis(src)
return 1
var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self Destruct Code Request.","")
if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
SSnanoui.update_uis(src)
return 1
Nuke_request(input, usr)
to_chat(usr, "<span class='notice'>Request sent.</span>")
log_game("[key_name(usr)] has requested the nuclear codes from Centcomm")
GLOB.priority_announcement.Announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested",'sound/AI/commandreport.ogg')
centcomm_message_cooldown = 1
spawn(6000)//10 minute cooldown
centcomm_message_cooldown = 0
setMenuState(usr,COMM_SCREEN_MAIN)
if("MessageCentcomm")
if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)
if(centcomm_message_cooldown)
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
SSnanoui.update_uis(src)
return 1
var/input = stripped_input(usr, "Please choose a message to transmit to Centcomm via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "To abort, send an empty message.", "")
if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
SSnanoui.update_uis(src)
return 1
Centcomm_announce(input, usr)
to_chat(usr, "Message transmitted.")
log_game("[key_name(usr)] has made a Centcomm announcement: [input]")
centcomm_message_cooldown = 1
spawn(6000)//10 minute cooldown
centcomm_message_cooldown = 0
setMenuState(usr,COMM_SCREEN_MAIN)
// OMG SYNDICATE ...LETTERHEAD
if("MessageSyndicate")
if((is_authenticated(usr) == COMM_AUTHENTICATION_MAX) && (computer && computer.emagged))
if(centcomm_message_cooldown)
to_chat(usr, "Arrays recycling. Please stand by.")
SSnanoui.update_uis(src)
return 1
var/input = stripped_input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING CORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "To abort, send an empty message.", "")
if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
SSnanoui.update_uis(src)
return 1
Syndicate_announce(input, usr)
to_chat(usr, "Message transmitted.")
log_game("[key_name(usr)] has made a Syndicate announcement: [input]")
centcomm_message_cooldown = 1
spawn(6000)//10 minute cooldown
centcomm_message_cooldown = 0
setMenuState(usr,COMM_SCREEN_MAIN)
if("RestartNanoMob")
if(SSmob_hunt)
if(SSmob_hunt.manual_reboot())
var/loading_msg = pick("Respawning spawns", "Reticulating splines", "Flipping hat",
"Capturing all of them", "Fixing minor text issues", "Being the very best",
"Nerfing this", "Not communicating with playerbase", "Coding a ripoff in a 2D spaceman game")
to_chat(usr, "<span class='notice'>Restarting Nano-Mob Hunter GO! game server. [loading_msg]...</span>")
else
to_chat(usr, "<span class='warning'>Nano-Mob Hunter GO! game server reboot failed due to recent restart. Please wait before re-attempting.</span>")
else
to_chat(usr, "<span class='danger'>Nano-Mob Hunter GO! game server is offline for extended maintenance. Contact your Central Command administrators for more info if desired.</span>")
SSnanoui.update_uis(src)
return 1
@@ -7,65 +7,71 @@
requires_ntnet = 1
network_destination = "alarm monitoring network"
size = 5
var/list/datum/alarm_handler/alarm_handlers
var/tgui_id = "NtosStationAlertConsole"
var/ui_x = 315
var/ui_y = 500
var/has_alert = 0
var/list/alarms_listend_for = list("Fire", "Atmosphere", "Power")
/datum/computer_file/program/alarm_monitor/New()
/datum/computer_file/program/alarm_monitor/process_tick()
..()
alarm_handlers = list(SSalarms.atmosphere_alarm, SSalarms.fire_alarm, SSalarms.power_alarm)
for(var/datum/alarm_handler/AH in alarm_handlers)
AH.register(src, /datum/computer_file/program/alarm_monitor/proc/update_icon)
/datum/computer_file/program/alarm_monitor/Destroy()
for(var/datum/alarm_handler/AH in alarm_handlers)
AH.unregister(src)
QDEL_NULL(alarm_handlers)
return ..()
if(has_alert)
program_icon_state = "alert-red"
ui_header = "alarm_red.gif"
update_computer_icon()
else
program_icon_state = "alert-green"
ui_header = "alarm_green.gif"
update_computer_icon()
return TRUE
/datum/computer_file/program/alarm_monitor/proc/update_icon()
for(var/datum/alarm_handler/AH in alarm_handlers)
if(AH.has_major_alarms())
program_icon_state = "alert-red"
ui_header = "alarm_red.gif"
update_computer_icon()
return 1
program_icon_state = "alert-green"
ui_header = "alarm_green.gif"
update_computer_icon()
return 0
/datum/computer_file/program/alarm_monitor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers)
assets.send(user)
ui = new(user, src, ui_key, "alarm_monitor.tmpl", "Alarm Monitoring", 575, 700)
ui.set_auto_update(1)
ui.set_layout_key("program")
ui.open()
/datum/computer_file/program/alarm_monitor/ui_data(mob/user)
/datum/computer_file/program/alarm_monitor/tgui_data(mob/user)
var/list/data = get_header_data()
var/categories[0]
for(var/datum/alarm_handler/AH in alarm_handlers)
categories[++categories.len] = list("category" = AH.category, "alarms" = list())
for(var/datum/alarm/A in AH.major_alarms())
var/cameras[0]
var/lost_sources[0]
if(isAI(user))
for(var/obj/machinery/camera/C in A.cameras())
cameras[++cameras.len] = C.nano_structure()
for(var/datum/alarm_source/AS in A.sources)
if(!AS.source)
lost_sources[++lost_sources.len] = AS.source_name
categories[categories.len]["alarms"] += list(list(
"name" = sanitize(A.alarm_name()),
"origin_lost" = A.origin == null,
"has_cameras" = cameras.len,
"cameras" = cameras,
"lost_sources" = lost_sources.len ? sanitize(english_list(lost_sources, nothing_text = "", and_text = ", ")) : ""))
data["categories"] = categories
data["alarms"] = list()
for(var/class in SSalarm.alarms)
if(!(class in alarms_listend_for))
continue
data["alarms"][class] = list()
for(var/area in alarms[class])
data["alarms"][class] += area
return data
/datum/computer_file/program/alarm_monitor/proc/alarm_triggered(src, class, area/A, list/O, obj/alarmsource)
if(is_station_level(alarmsource.z))
if(!(A.type in GLOB.the_station_areas))
return
else if(!is_mining_level(alarmsource.z) || istype(A, /area/ruin))
return
update_alarm_display()
/datum/computer_file/program/alarm_monitor/proc/alarm_cancelled(src, class, area/A, obj/origin, cleared)
if(is_station_level(origin.z))
if(!(A.type in GLOB.the_station_areas))
return
else if(!is_mining_level(origin.z) || istype(A, /area/ruin))
return
update_alarm_display()
/datum/computer_file/program/alarm_monitor/proc/update_alarm_display()
has_alert = FALSE
for(var/cat in alarms)
if(!(cat in alarms_listend_for))
continue
var/list/L = alarms[cat]
if(length(L))
has_alert = TRUE
/datum/computer_file/program/alarm_monitor/run_program(mob/user)
. = ..(user)
GLOB.alarmdisplay += src
RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, .proc/alarm_triggered)
RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, .proc/alarm_cancelled)
/datum/computer_file/program/alarm_monitor/kill_program(forced = FALSE)
GLOB.alarmdisplay -= src
UnregisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM)
UnregisterSignal(SSalarm, COMSIG_CANCELLED_ALARM)
..()
@@ -1,90 +0,0 @@
/datum/nano_module/alarm_monitor
name = "Alarm monitor"
var/list_cameras = 0 // Whether or not to list camera references. A future goal would be to merge this with the enginering/security camera console. Currently really only for AI-use.
var/list/datum/alarm_handler/alarm_handlers // The particular list of alarm handlers this alarm monitor should present to the user.
/datum/nano_module/alarm_monitor/all/New()
..()
alarm_handlers = list(SSalarms.atmosphere_alarm, SSalarms.burglar_alarm, SSalarms.camera_alarm, SSalarms.fire_alarm, SSalarms.motion_alarm, SSalarms.power_alarm)
/datum/nano_module/alarm_monitor/engineering/New()
..()
alarm_handlers = list(SSalarms.atmosphere_alarm, SSalarms.fire_alarm, SSalarms.power_alarm)
/datum/nano_module/alarm_monitor/security/New()
..()
alarm_handlers = list(SSalarms.burglar_alarm, SSalarms.camera_alarm, SSalarms.motion_alarm)
/datum/nano_module/alarm_monitor/proc/register(var/object, var/procName)
for(var/datum/alarm_handler/AH in alarm_handlers)
AH.register(object, procName)
/datum/nano_module/alarm_monitor/proc/unregister(var/object)
for(var/datum/alarm_handler/AH in alarm_handlers)
AH.unregister(object)
/datum/nano_module/alarm_monitor/proc/all_alarms()
var/list/all_alarms = new()
for(var/datum/alarm_handler/AH in alarm_handlers)
all_alarms += AH.alarms
return all_alarms
/datum/nano_module/alarm_monitor/proc/major_alarms()
var/list/all_alarms = new()
for(var/datum/alarm_handler/AH in alarm_handlers)
all_alarms += AH.major_alarms()
return all_alarms
/datum/nano_module/alarm_monitor/proc/minor_alarms()
var/list/all_alarms = new()
for(var/datum/alarm_handler/AH in alarm_handlers)
all_alarms += AH.minor_alarms()
return all_alarms
/datum/nano_module/alarm_monitor/Topic(ref, href_list)
if(..())
return 1
if(href_list["switchTo"])
var/obj/machinery/camera/C = locate(href_list["switchTo"]) in GLOB.cameranet.cameras
if(!C || !isAI(usr))
return
usr.switch_to_camera(C)
return 1
/datum/nano_module/alarm_monitor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = GLOB.default_state)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "alarm_monitor.tmpl", "Alarm Monitoring Console", 800, 800, state = state)
ui.open()
ui.set_auto_update(1)
/datum/nano_module/alarm_monitor/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
var/data[0]
var/categories[0]
for(var/datum/alarm_handler/AH in alarm_handlers)
categories[++categories.len] = list("category" = AH.category, "alarms" = list())
for(var/datum/alarm/A in AH.major_alarms())
var/cameras[0]
var/lost_sources[0]
if(isAI(user))
for(var/obj/machinery/camera/C in A.cameras())
cameras[++cameras.len] = C.nano_structure()
for(var/datum/alarm_source/AS in A.sources)
if(!AS.source)
lost_sources[++lost_sources.len] = AS.source_name
categories[categories.len]["alarms"] += list(list(
"name" = sanitize(A.alarm_name()),
"origin_lost" = A.origin == null,
"has_cameras" = cameras.len,
"cameras" = cameras,
"lost_sources" = lost_sources.len ? sanitize(english_list(lost_sources, nothing_text = "", and_text = ", ")) : ""))
data["categories"] = categories
return data
+155 -145
View File
@@ -12,24 +12,32 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
insert_anim = "faxsend"
pass_flags = PASSTABLE
var/fax_network = "Local Fax Network"
var/syndie_restricted = FALSE //is it a syndicate base fax restricted from contacting NT assets?
/// If true, prevents fax machine from sending messages to NT machines
var/syndie_restricted = FALSE
var/long_range_enabled = 0 // Can we send messages off the station?
/// Can we send messages off-station?
var/long_range_enabled = FALSE
req_one_access = list(ACCESS_LAWYER, ACCESS_HEADS, ACCESS_ARMORY)
use_power = IDLE_POWER_USE
idle_power_usage = 30
active_power_usage = 200
var/obj/item/card/id/scan = null // identification
/// ID card inserted into the machine, used to log in with
var/obj/item/card/id/scan = null
var/authenticated = 0
var/sendcooldown = 0 // to avoid spamming fax messages
/// Whether the machine is "logged in" or not
var/authenticated = FALSE
/// Next world.time at which this fax machine can send a message to CC/syndicate
var/sendcooldown = 0
/// After sending a message to CC/syndicate, cannot send another to them for this many deciseconds
var/cooldown_time = 1800
var/department = "Unknown" // our department
/// Our department, determines whether this machine gets faxes sent to a department
var/department = "Unknown"
var/destination = "Not Selected" // the department we're sending to
/// Target department to send outgoing faxes to
var/destination
/obj/machinery/photocopier/faxmachine/New()
..()
@@ -44,7 +52,7 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
/obj/machinery/photocopier/faxmachine/longrange
name = "long range fax machine"
fax_network = "Central Command Quantum Entanglement Network"
long_range_enabled = 1
long_range_enabled = TRUE
/obj/machinery/photocopier/faxmachine/longrange/syndie
name = "syndicate long range fax machine"
@@ -58,17 +66,17 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
GLOB.hidden_departments |= department
/obj/machinery/photocopier/faxmachine/attack_hand(mob/user)
ui_interact(user)
tgui_interact(user)
/obj/machinery/photocopier/faxmachine/attack_ghost(mob/user)
ui_interact(user)
tgui_interact(user)
/obj/machinery/photocopier/faxmachine/attackby(obj/item/item, mob/user, params)
if(istype(item,/obj/item/card/id) && !scan)
scan(item)
else if(istype(item, /obj/item/paper) || istype(item, /obj/item/photo) || istype(item, /obj/item/paper_bundle))
..()
SSnanoui.update_uis(src)
SStgui.update_uis(src)
else
return ..()
@@ -80,42 +88,6 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
else
to_chat(user, "<span class='warning'>You swipe the card through [src], but nothing happens.</span>")
/obj/machinery/photocopier/faxmachine/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "faxmachine.tmpl", "Fax Machine UI", 540, 450)
ui.open()
/obj/machinery/photocopier/faxmachine/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
var/data[0]
var/is_authenticated = is_authenticated(user)
if(scan)
data["scan_name"] = scan.name
else
data["scan_name"] = "-----"
data["authenticated"] = is_authenticated
if(!is_authenticated)
data["network"] = "Disconnected"
else if(!emagged)
data["network"] = fax_network
else
data["network"] = "ERR*?*%!*"
if(copyitem)
data["paper"] = copyitem.name
data["paperinserted"] = 1
else
data["paper"] = "-----"
data["paperinserted"] = 0
data["destination"] = destination
data["cooldown"] = sendcooldown
if((destination in GLOB.admin_departments) || (destination in GLOB.hidden_admin_departments))
data["respectcooldown"] = 1
else
data["respectcooldown"] = 0
return data
/obj/machinery/photocopier/faxmachine/proc/is_authenticated(mob/user)
if(authenticated)
return TRUE
@@ -123,87 +95,134 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
return TRUE
return FALSE
/obj/machinery/photocopier/faxmachine/Topic(href, href_list)
if(..())
return 1
/obj/machinery/photocopier/faxmachine/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "FaxMachine", name, 540, 300, master_ui, state)
ui.open()
/obj/machinery/photocopier/faxmachine/tgui_data(mob/user)
var/list/data = list()
data["authenticated"] = is_authenticated(user)
data["scan_name"] = scan ? scan.name : FALSE
if(!data["authenticated"])
data["network"] = "Disconnected"
else if(!emagged)
data["network"] = fax_network
else
data["network"] = "ERR*?*%!*"
data["paper"] = copyitem ? copyitem.name : FALSE
data["paperinserted"] = copyitem ? TRUE : FALSE
data["destination"] = destination ? destination : FALSE
data["sendError"] = FALSE
if(stat & (BROKEN|NOPOWER))
data["sendError"] = "No Power"
else if(!data["authenticated"])
data["sendError"] = "Not Logged In"
else if(!data["paper"])
data["sendError"] = "Nothing Inserted"
else if(!data["destination"])
data["sendError"] = "Destination Not Set"
else if((destination in GLOB.admin_departments) || (destination in GLOB.hidden_admin_departments))
var/cooldown_seconds = cooldown_seconds()
if(cooldown_seconds)
data["sendError"] = "Re-aligning in [cooldown_seconds] seconds..."
return data
/obj/machinery/photocopier/faxmachine/tgui_act(action, params)
if(..())
return
var/is_authenticated = is_authenticated(usr)
if(href_list["send"])
if(copyitem && is_authenticated)
. = TRUE
switch(action)
if("scan") // insert/remove your ID card
scan()
if("auth") // log in/out
if(!is_authenticated && scan)
if(scan.registered_name in GLOB.fax_blacklist)
to_chat(usr, "<span class='warning'>Login rejected: individual is blacklisted from fax network.</span>")
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
. = FALSE
else if(check_access(scan))
authenticated = TRUE
else // ID doesn't have access to this machine
to_chat(usr, "<span class='warning'>Login rejected: ID card does not have required access.</span>")
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
. = FALSE
else if(is_authenticated)
authenticated = FALSE
if("paper") // insert/eject paper/paperbundle/photo
if(copyitem)
copyitem.forceMove(get_turf(src))
if(ishuman(usr))
if(!usr.get_active_hand() && Adjacent(usr))
usr.put_in_hands(copyitem)
to_chat(usr, "<span class='notice'>You eject [copyitem] from [src].</span>")
copyitem = null
else
var/obj/item/I = usr.get_active_hand()
if(istype(I, /obj/item/paper) || istype(I, /obj/item/photo) || istype(I, /obj/item/paper_bundle))
usr.drop_item()
copyitem = I
I.forceMove(src)
to_chat(usr, "<span class='notice'>You insert [I] into [src].</span>")
flick(insert_anim, src)
else
to_chat(usr, "<span class='warning'>[src] only accepts paper, paper bundles, and photos.</span>")
. = FALSE
if("rename") // rename the item that is currently in the fax machine
if(copyitem)
var/n_name = sanitize(copytext(input(usr, "What would you like to label the fax?", "Fax Labelling", copyitem.name) as text, 1, MAX_MESSAGE_LEN))
if((copyitem && copyitem.loc == src && usr.stat == 0))
if(istype(copyitem, /obj/item/paper))
copyitem.name = "[(n_name ? text("[n_name]") : initial(copyitem.name))]"
copyitem.desc = "This is a paper titled '" + copyitem.name + "'."
else if(istype(copyitem, /obj/item/photo))
copyitem.name = "[(n_name ? text("[n_name]") : "photo")]"
else if(istype(copyitem, /obj/item/paper_bundle))
copyitem.name = "[(n_name ? text("[n_name]") : "paper")]"
else
. = FALSE
else
. = FALSE
else
. = FALSE
if("dept") // choose which department receives the fax
if(is_authenticated)
var/lastdestination = destination
var/list/combineddepartments = GLOB.alldepartments.Copy()
if(long_range_enabled)
combineddepartments += GLOB.admin_departments.Copy()
if(emagged)
combineddepartments += GLOB.hidden_admin_departments.Copy()
combineddepartments += GLOB.hidden_departments.Copy()
if(syndie_restricted)
combineddepartments = GLOB.hidden_admin_departments.Copy()
combineddepartments += GLOB.hidden_departments.Copy()
for(var/obj/machinery/photocopier/faxmachine/F in GLOB.allfaxes)
if(F.emagged)//we can contact emagged faxes on the station
combineddepartments |= F.department
destination = input(usr, "To which department?", "Choose a department", "") as null|anything in combineddepartments
if(!destination)
destination = lastdestination
if("send") // actually send the fax
if(!copyitem || !is_authenticated || !destination)
return
if(stat & (BROKEN|NOPOWER))
return
if((destination in GLOB.admin_departments) || (destination in GLOB.hidden_admin_departments))
var/cooldown_seconds = cooldown_seconds()
if(cooldown_seconds > 0)
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
to_chat(usr, "<span class='warning'>[src] is not ready for another [cooldown_seconds] seconds.</span>")
return
send_admin_fax(usr, destination)
sendcooldown = world.time + cooldown_time
else
sendfax(destination, usr)
if(sendcooldown)
spawn(sendcooldown) // cooldown time
sendcooldown = 0
SSnanoui.update_uis(src)
if(href_list["paper"])
if(copyitem)
copyitem.forceMove(get_turf(src))
if(ishuman(usr))
if(!usr.get_active_hand() && Adjacent(usr))
usr.put_in_hands(copyitem)
to_chat(usr, "<span class='notice'>You eject \the [copyitem] from \the [src].</span>")
copyitem = null
else
var/obj/item/I = usr.get_active_hand()
if(istype(I, /obj/item/paper) || istype(I, /obj/item/photo) || istype(I, /obj/item/paper_bundle))
usr.drop_item()
copyitem = I
I.forceMove(src)
to_chat(usr, "<span class='notice'>You insert \the [I] into \the [src].</span>")
flick(insert_anim, src)
if(href_list["scan"])
scan()
if(href_list["dept"])
if(is_authenticated)
var/lastdestination = destination
var/list/combineddepartments = GLOB.alldepartments.Copy()
if(long_range_enabled)
combineddepartments += GLOB.admin_departments.Copy()
if(emagged)
combineddepartments += GLOB.hidden_admin_departments.Copy()
combineddepartments += GLOB.hidden_departments.Copy()
if(syndie_restricted)
combineddepartments = GLOB.hidden_admin_departments.Copy()
combineddepartments += GLOB.hidden_departments.Copy()
for(var/obj/machinery/photocopier/faxmachine/F in GLOB.allfaxes)
if(F.emagged)//we can contact emagged faxes on the station
combineddepartments |= F.department
destination = input(usr, "To which department?", "Choose a department", "") as null|anything in combineddepartments
if(!destination)
destination = lastdestination
if(href_list["auth"])
if(!is_authenticated && scan)
if(scan.registered_name in GLOB.fax_blacklist)
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
else if(check_access(scan))
authenticated = 1
else if(is_authenticated)
authenticated = 0
if(href_list["rename"])
if(copyitem)
var/n_name = sanitize(copytext(input(usr, "What would you like to label the fax?", "Fax Labelling", copyitem.name) as text, 1, MAX_MESSAGE_LEN))
if((copyitem && copyitem.loc == src && usr.stat == 0))
if(istype(copyitem, /obj/item/paper))
copyitem.name = "[(n_name ? text("[n_name]") : initial(copyitem.name))]"
copyitem.desc = "This is a paper titled '" + copyitem.name + "'."
else if(istype(copyitem, /obj/item/photo))
copyitem.name = "[(n_name ? text("[n_name]") : "photo")]"
else if(istype(copyitem, /obj/item/paper_bundle))
copyitem.name = "[(n_name ? text("[n_name]") : "paper")]"
SSnanoui.update_uis(src)
if(.)
add_fingerprint(usr)
/obj/machinery/photocopier/faxmachine/proc/scan(var/obj/item/card/id/card = null)
if(scan) // Card is in machine
@@ -226,7 +245,7 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
usr.drop_item()
card.forceMove(src)
scan = card
SSnanoui.update_uis(src)
SStgui.update_uis(src)
/obj/machinery/photocopier/faxmachine/verb/eject_id()
set category = null
@@ -237,25 +256,20 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
return
if(scan)
to_chat(usr, "You remove \the [scan] from \the [src].")
to_chat(usr, "You remove [scan] from [src].")
scan.forceMove(get_turf(src))
if(!usr.get_active_hand() && Adjacent(usr))
usr.put_in_hands(scan)
scan = null
else
to_chat(usr, "There is nothing to remove from \the [src].")
to_chat(usr, "There is nothing to remove from [src].")
/obj/machinery/photocopier/faxmachine/proc/sendfax(var/destination,var/mob/sender)
if(stat & (BROKEN|NOPOWER))
return
use_power(200)
use_power(active_power_usage)
var/success = 0
for(var/obj/machinery/photocopier/faxmachine/F in GLOB.allfaxes)
if(F.department == destination)
success = F.receivefax(copyitem)
if(success)
var/datum/fax/F = new /datum/fax()
F.name = copyitem.name
@@ -272,10 +286,10 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
/obj/machinery/photocopier/faxmachine/proc/receivefax(var/obj/item/incoming)
if(stat & (BROKEN|NOPOWER))
return 0
return FALSE
if(department == "Unknown")
return 0 //You can't send faxes to "Unknown"
return FALSE //You can't send faxes to "Unknown"
flick("faxreceive", src)
@@ -291,19 +305,13 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
else if(istype(incoming, /obj/item/paper_bundle))
bundlecopy(incoming)
else
return 0
return FALSE
use_power(active_power_usage)
return 1
return TRUE
/obj/machinery/photocopier/faxmachine/proc/send_admin_fax(var/mob/sender, var/destination)
if(stat & (BROKEN|NOPOWER))
return
if(sendcooldown)
return
use_power(200)
use_power(active_power_usage)
if(!(istype(copyitem, /obj/item/paper) || istype(copyitem, /obj/item/paper_bundle) || istype(copyitem, /obj/item/photo)))
visible_message("[src] beeps, \"Error transmitting message.\"")
@@ -327,10 +335,12 @@ GLOBAL_LIST_EMPTY(fax_blacklist)
for(var/obj/machinery/photocopier/faxmachine/F in GLOB.allfaxes)
if(F.department == destination)
F.receivefax(copyitem)
sendcooldown = cooldown_time
spawn(50)
visible_message("[src] beeps, \"Message transmitted successfully.\"")
visible_message("[src] beeps, \"Message transmitted successfully.\"")
/obj/machinery/photocopier/faxmachine/proc/cooldown_seconds()
if(sendcooldown < world.time)
return 0
return round((sendcooldown - world.time) / 10)
/obj/machinery/photocopier/faxmachine/proc/message_admins(var/mob/sender, var/faxname, var/faxtype, var/obj/item/sent, font_colour="#9A04D1")
var/msg = "<span class='boldnotice'><font color='[font_colour]'>[faxname]: </font> [key_name_admin(sender)] | REPLY: (<A HREF='?_src_=holder;[faxname == "SYNDICATE FAX" ? "SyndicateReply" : "CentcommReply"]=[sender.UID()]'>RADIO</A>) (<a href='?_src_=holder;AdminFaxCreate=\ref[sender];originfax=\ref[src];faxtype=[faxtype];replyto=\ref[sent]'>FAX</a>) ([ADMIN_SM(sender,"SM")]) | REJECT: (<A HREF='?_src_=holder;FaxReplyTemplate=[sender.UID()];originfax=\ref[src]'>TEMPLATE</A>) ([ADMIN_BSA(sender,"BSA")]) (<A HREF='?_src_=holder;EvilFax=[sender.UID()];originfax=\ref[src]'>EVILFAX</A>) </span>: Receiving '[sent.name]' via secure connection... <a href='?_src_=holder;AdminFaxView=\ref[sent]'>view message</a>"
+140 -179
View File
@@ -25,6 +25,11 @@
#define APC_UPDATE_ICON_COOLDOWN 200 // 20 seconds
// main_status var
#define APC_EXTERNAL_POWER_NOTCONNECTED 0
#define APC_EXTERNAL_POWER_NOENERGY 1
#define APC_EXTERNAL_POWER_GOOD 2
// APC malf status
#define APC_MALF_NOT_HACKED 1
#define APC_MALF_HACKED 2 // APC hacked by user, and user is in its core.
@@ -75,7 +80,7 @@
var/lastused_equip = 0
var/lastused_environ = 0
var/lastused_total = 0
var/main_status = 0
var/main_status = APC_EXTERNAL_POWER_NOTCONNECTED
powernet = 0 // set so that APCs aren't found as powernet nodes //Hackish, Horrible, was like this before I changed it :(
var/malfhack = 0 //New var for my changes to AI malf. --NeoFite
var/mob/living/silicon/ai/malfai = null //See above --NeoFite
@@ -170,6 +175,7 @@
addtimer(CALLBACK(src, .proc/update), 5)
/obj/machinery/power/apc/Destroy()
SStgui.close_uis(wires)
GLOB.apcs -= src
if(malfai && operating)
malfai.malf_picker.processing_time = clamp(malfai.malf_picker.processing_time - 10,0,1000)
@@ -212,12 +218,11 @@
if(isarea(A))
area = A
// no-op, keep the name
else if(isarea(A) && src.areastring == null)
else if(isarea(A) && !areastring)
area = A
name = "\improper [area.name] APC"
else
area = get_area_name(areastring)
name = "\improper [area.name] APC"
name = "\improper [get_area_name(area, TRUE)] APC"
area.apc |= src
update_icon()
@@ -429,8 +434,8 @@
//attack with an item - open/close cover, insert cell, or (un)lock interface
/obj/machinery/power/apc/attackby(obj/item/W, mob/living/user, params)
if(issilicon(user) && get_dist(src,user)>1)
return src.attack_hand(user)
if(issilicon(user) && get_dist(src, user) > 1)
return attack_hand(user)
else if (istype(W, /obj/item/stock_parts/cell) && opened) // trying to put a cell inside
if(cell)
@@ -445,7 +450,7 @@
W.forceMove(src)
cell = W
user.visible_message(\
"[user.name] has inserted the power cell to [src.name]!",\
"[user.name] has inserted the power cell to [name]!",\
"<span class='notice'>You insert the power cell.</span>")
chargecount = 0
update_icon()
@@ -474,7 +479,7 @@
return
user.visible_message("[user.name] adds cables to the APC frame.", \
"<span class='notice'>You start adding cables to the APC frame...</span>")
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
if(do_after(user, 20, target = src))
if(C.get_amount() < 10 || !C)
return
@@ -499,10 +504,10 @@
user.visible_message("[user.name] inserts the power control board into [src].", \
"<span class='notice'>You start to insert the power control board into the frame...</span>")
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
if(do_after(user, 10, target = src))
if(has_electronics==0)
has_electronics = 1
if(!has_electronics)
has_electronics = TRUE
locked = FALSE
to_chat(user, "<span class='notice'>You place the power control board inside the frame.</span>")
qdel(W)
@@ -549,11 +554,11 @@
return
to_chat(user, "<span class='notice'>You are trying to remove the power control board...</span>" )
if(I.use_tool(src, user, 50, volume = I.tool_volume))
if(has_electronics==1)
has_electronics = 0
if(has_electronics)
has_electronics = FALSE
if(stat & BROKEN)
user.visible_message(\
"[user.name] has broken the power control board inside [src.name]!",
"[user.name] has broken the power control board inside [name]!",
"<span class='notice'>You break the charred power control board and remove the remains.</span>",
"<span class='italics'>You hear a crack.</span>")
return
@@ -561,19 +566,19 @@
else if(emagged) // We emag board, not APC's frame
emagged = FALSE
user.visible_message(
"[user.name] has discarded emaged power control board from [src.name]!",
"<span class='notice'>You discarded shorten board.</span>")
"[user.name] has discarded the shorted power control board from [name]!",
"<span class='notice'>You discarded the shorted board.</span>")
return
else if(malfhack) // AI hacks board, not APC's frame
user.visible_message(\
"[user.name] has discarded strangely programmed power control board from [src.name]!",
"<span class='notice'>You discarded strangely programmed board.</span>")
"[user.name] has discarded strangely the programmed power control board from [name]!",
"<span class='notice'>You discarded the strangely programmed board.</span>")
malfai = null
malfhack = 0
return
else
user.visible_message(\
"[user.name] has removed the power control board from [src.name]!",
"[user.name] has removed the power control board from [name]!",
"<span class='notice'>You remove the power control board.</span>")
new /obj/item/apc_electronics(loc)
return
@@ -648,7 +653,7 @@
else if(stat & (BROKEN|MAINT))
to_chat(user, "<span class='warning'>Nothing happens!</span>")
else
if(allowed(usr) && !isWireCut(APC_WIRE_IDSCAN) && !malfhack)
if(allowed(usr) && !wires.is_cut(WIRE_IDSCAN) && !malfhack)
locked = !locked
to_chat(user, "<span class='notice'>You [ locked ? "lock" : "unlock"] the APC interface.</span>")
update_icon()
@@ -711,36 +716,29 @@
// attack with hand - remove cell (if cover open) or interact with the APC
/obj/machinery/power/apc/attack_hand(mob/user)
// if(!can_use(user)) This already gets called in interact() and in topic()
// return
if(!user)
return
src.add_fingerprint(user)
add_fingerprint(user)
if(usr == user && opened && (!issilicon(user)))
if(usr == user && opened && !issilicon(user))
if(cell)
if(issilicon(user))
cell.loc=src.loc // Drop it, whoops.
else
user.put_in_hands(cell)
user.put_in_hands(cell)
cell.add_fingerprint(user)
cell.update_icon()
src.cell = null
user.visible_message("<span class='warning'>[user.name] removes the power cell from [src.name]!", "You remove the power cell.</span>")
// to_chat(user, "You remove the power cell.")
charging = 0
src.update_icon()
cell = null
user.visible_message("<span class='warning'>[user.name] removes [cell] from [src]!", "You remove the [cell].</span>")
charging = FALSE
update_icon()
return
if(stat & (BROKEN|MAINT))
return
src.interact(user)
interact(user)
/obj/machinery/power/apc/attack_ghost(mob/user)
if(panel_open)
wires.Interact(user)
return ui_interact(user)
return tgui_interact(user)
/obj/machinery/power/apc/interact(mob/user)
if(!user)
@@ -749,7 +747,7 @@
if(panel_open)
wires.Interact(user)
return ui_interact(user)
return tgui_interact(user)
/obj/machinery/power/apc/proc/get_malf_status(mob/living/silicon/ai/malf)
@@ -770,22 +768,16 @@
else
return APC_MALF_NOT_HACKED
/obj/machinery/power/apc/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
if(!user)
return
// update the ui if it exists, returns null if no ui is passed/found
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
/obj/machinery/power/apc/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
// the ui does not exist, so we'll create a new one
ui = new(user, src, ui_key, "apc.tmpl", "[area.name] - APC", 510, issilicon(user) ? 535 : 460)
ui = new(user, src, ui_key, "APC", name, 510, 460, master_ui, state)
ui.open()
// Auto update every Master Controller tick
ui.set_auto_update(1)
/obj/machinery/power/apc/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
var/data[0]
/obj/machinery/power/apc/tgui_data(mob/user)
var/list/data = list()
data["locked"] = is_locked(user)
data["normallyLocked"] = locked
data["isOperating"] = operating
data["externalPower"] = main_status
data["powerCellStatus"] = cell ? cell.percent() : null
@@ -853,10 +845,6 @@
// to_chat(world, "[area.power_equip]")
area.power_change()
/obj/machinery/power/apc/proc/isWireCut(var/wireIndex)
return wires.IsIndexCut(wireIndex)
/obj/machinery/power/apc/proc/can_use(var/mob/user, var/loud = 0) //used by attack_hand() and Topic()
if(user.can_admin_interact())
return 1
@@ -866,7 +854,7 @@
var/mob/living/silicon/ai/AI = user
var/mob/living/silicon/robot/robot = user
if( \
src.aidisabled || \
aidisabled || \
malfhack && istype(malfai) && \
( \
(istype(AI) && (malfai!=AI && malfai != AI.parent)) || \
@@ -877,21 +865,21 @@
to_chat(user, "<span class='danger'>\The [src] has AI control disabled!</span>")
user << browse(null, "window=apc")
user.unset_machine()
return 0
return FALSE
else
if((!in_range(src, user) || !istype(src.loc, /turf)))
return 0
if((!in_range(src, user) || !istype(loc, /turf)))
return FALSE
var/mob/living/carbon/human/H = user
if(istype(H))
if(H.getBrainLoss() >= 60)
for(var/mob/M in viewers(src, null))
to_chat(M, "<span class='danger'>[H] stares cluelessly at [src] and drools.</span>")
return 0
return FALSE
else if(prob(H.getBrainLoss()))
to_chat(user, "<span class='danger'>You momentarily forget how to use [src].</span>")
return 0
return 1
return FALSE
return TRUE
/obj/machinery/power/apc/proc/is_authenticated(mob/user as mob)
if(user.can_admin_interact())
@@ -909,104 +897,59 @@
else
return locked
/obj/machinery/power/apc/Topic(href, href_list, var/usingUI = 1)
if(..())
return 1
if(!can_use(usr, 1))
return 1
if(href_list["lock"])
if(!is_authenticated(usr))
return
coverlocked = !coverlocked
else if(href_list["breaker"])
if(!is_authenticated(usr))
return
toggle_breaker()
else if(href_list["toggle_nightshift"])
if(!is_authenticated(usr))
return
if(last_nightshift_switch > world.time + 100) // don't spam...
to_chat(usr, "<span class='warning'>[src]'s night lighting circuit breaker is still cycling!</span>")
return
last_nightshift_switch = world.time
set_nightshift(!nightshift_lights)
else if(href_list["cmode"])
if(!is_authenticated(usr))
return
chargemode = !chargemode
if(!chargemode)
charging = 0
update_icon()
else if(href_list["eqp"])
if(!is_authenticated(usr))
return
var/val = text2num(href_list["eqp"])
equipment = setsubsystem(val)
update_icon()
update()
else if(href_list["lgt"])
if(!is_authenticated(usr))
return
var/val = text2num(href_list["lgt"])
lighting = setsubsystem(val)
update_icon()
update()
else if(href_list["env"])
if(!is_authenticated(usr))
return
var/val = text2num(href_list["env"])
environ = setsubsystem(val)
update_icon()
update()
else if( href_list["close"] )
SSnanoui.close_user_uis(usr, src)
return 0
else if(href_list["close2"])
usr << browse(null, "window=apcwires")
return 0
else if(href_list["overload"])
if(issilicon(usr) && !aidisabled)
overload_lighting()
else if(href_list["malfhack"])
if(get_malf_status(usr))
malfhack(usr)
else if(href_list["occupyapc"])
if(get_malf_status(usr))
malfoccupy(usr)
else if(href_list["deoccupyapc"])
if(get_malf_status(usr))
malfvacate()
else if(href_list["toggleaccess"])
if(istype(usr, /mob/living/silicon))
if(emagged || aidisabled || (stat & (BROKEN|MAINT)))
to_chat(usr, "The APC does not respond to the command.")
/obj/machinery/power/apc/tgui_act(action, params)
if(..() || !can_use(usr, TRUE) || (locked && !usr.has_unlimited_silicon_privilege && (action != "toggle_nightshift") && !usr.can_admin_interact()))
return
. = TRUE
switch(action)
if("lock")
if(usr.has_unlimited_silicon_privilege)
if(emagged || stat & BROKEN)
to_chat(usr, "<span class='warning'>The APC does not respond to the command!</span>")
return FALSE
else
locked = !locked
update_icon()
else
locked = !locked
to_chat(usr, "<span class='warning'>Access Denied!</span>")
return FALSE
if("cover")
coverlocked = !coverlocked
if("breaker")
toggle_breaker(usr)
if("toggle_nightshift")
if(last_nightshift_switch > world.time + 100) // don't spam...
to_chat(usr, "<span class='warning'>[src]'s night lighting circuit breaker is still cycling!</span>")
return FALSE
last_nightshift_switch = world.time
set_nightshift(!nightshift_lights)
if("charge")
chargemode = !chargemode
if("channel")
if(params["eqp"])
equipment = setsubsystem(text2num(params["eqp"]))
update_icon()
return 0
update()
else if(params["lgt"])
lighting = setsubsystem(text2num(params["lgt"]))
update_icon()
update()
else if(params["env"])
environ = setsubsystem(text2num(params["env"]))
update_icon()
update()
if("overload")
if(usr.has_unlimited_silicon_privilege)
overload_lighting()
if("hack")
if(get_malf_status(usr))
malfhack(usr)
if("occupy")
if(get_malf_status(usr))
malfoccupy(usr)
if("deoccupy")
if(get_malf_status(usr))
malfvacate()
/obj/machinery/power/apc/proc/toggle_breaker()
operating = !operating
@@ -1037,7 +980,7 @@
if(!malf.can_shunt)
to_chat(malf, "<span class='warning'>You cannot shunt!</span>")
return
if(!is_station_level(src.z))
if(!is_station_level(z))
return
occupier = new /mob/living/silicon/ai(src,malf.laws,null,1)
occupier.adjustOxyLoss(malf.getOxyLoss())
@@ -1084,21 +1027,21 @@
/obj/machinery/power/apc/proc/ion_act()
//intended to be exactly the same as an AI malf attack
if(!src.malfhack && is_station_level(src.z))
if(!malfhack && is_station_level(z))
if(prob(3))
src.locked = 1
if(src.cell.charge > 0)
src.cell.charge = 0
locked = TRUE
if(cell.charge > 0)
cell.charge = 0
cell.corrupt()
src.malfhack = 1
malfhack = TRUE
update_icon()
var/datum/effect_system/smoke_spread/smoke = new
smoke.set_up(3, 0, src.loc)
smoke.set_up(3, 0, loc)
smoke.attach(src)
smoke.start()
do_sparks(3, 1, src)
for(var/mob/M in viewers(src))
M.show_message("<span class='danger'>The [src.name] suddenly lets out a blast of smoke and some sparks!", 3, "<span class='danger'>You hear sizzling electronics.</span>", 2)
M.show_message("<span class='danger'>The [name] suddenly lets out a blast of smoke and some sparks!", 3, "<span class='danger'>You hear sizzling electronics.</span>", 2)
/obj/machinery/power/apc/surplus()
@@ -1141,12 +1084,12 @@
var/excess = surplus()
if(!src.avail())
main_status = 0
if(!avail())
main_status = APC_EXTERNAL_POWER_NOTCONNECTED
else if(excess < 0)
main_status = 1
main_status = APC_EXTERNAL_POWER_NOENERGY
else
main_status = 2
main_status = APC_EXTERNAL_POWER_GOOD
if(debug)
log_debug("Status: [main_status] - Excess: [excess] - Last Equip: [lastused_equip] - Last Light: [lastused_light] - Longterm: [longtermpower]")
@@ -1192,31 +1135,31 @@
lighting = autoset(lighting, 1)
environ = autoset(environ, 1)
autoflag = 3
if(report_power_alarm && is_station_contact(z))
SSalarms.power_alarm.clearAlarm(loc, src)
if(report_power_alarm)
area.poweralert(TRUE, src)
else if(cell.charge < 1250 && cell.charge > 750 && longtermpower < 0) // <30%, turn off equipment
if(autoflag != 2)
equipment = autoset(equipment, 2)
lighting = autoset(lighting, 1)
environ = autoset(environ, 1)
if(report_power_alarm && is_station_contact(z))
SSalarms.power_alarm.triggerAlarm(loc, src)
if(report_power_alarm)
area.poweralert(FALSE, src)
autoflag = 2
else if(cell.charge < 750 && cell.charge > 10) // <15%, turn off lighting & equipment
if((autoflag > 1 && longtermpower < 0) || (autoflag > 1 && longtermpower >= 0))
equipment = autoset(equipment, 2)
lighting = autoset(lighting, 2)
environ = autoset(environ, 1)
if(report_power_alarm && is_station_contact(z))
SSalarms.power_alarm.triggerAlarm(loc, src)
if(report_power_alarm)
area.poweralert(FALSE, src)
autoflag = 1
else if(cell.charge <= 0) // zero charge, turn all off
if(autoflag != 0)
equipment = autoset(equipment, 0)
lighting = autoset(lighting, 0)
environ = autoset(environ, 0)
if(report_power_alarm && is_station_contact(z))
SSalarms.power_alarm.triggerAlarm(loc, src)
if(report_power_alarm)
area.poweralert(FALSE, src)
autoflag = 0
// now trickle-charge the cell
@@ -1261,7 +1204,7 @@
if(shock_mobs.len)
var/mob/living/L = pick(shock_mobs)
L.electrocute_act(rand(5, 25), "electrical arc")
playsound(get_turf(L), 'sound/effects/eleczap.ogg', 75, 1)
playsound(get_turf(L), 'sound/effects/eleczap.ogg', 75, TRUE)
Beam(L, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5)
else // no cell, switch everything off
@@ -1271,8 +1214,8 @@
equipment = autoset(equipment, 0)
lighting = autoset(lighting, 0)
environ = autoset(environ, 0)
if(report_power_alarm && is_station_contact(z))
SSalarms.power_alarm.triggerAlarm(loc, src)
if(report_power_alarm)
area.poweralert(FALSE, src)
autoflag = 0
// update icon & area power if anything changed
@@ -1372,4 +1315,22 @@
L.update(FALSE)
CHECK_TICK
/obj/machinery/power/apc/proc/relock_callback()
locked = TRUE
updateDialog()
/obj/machinery/power/apc/proc/check_main_power_callback()
if(!wires.is_cut(WIRE_MAIN_POWER1) && !wires.is_cut(WIRE_MAIN_POWER2))
shorted = FALSE
updateDialog()
/obj/machinery/power/apc/proc/check_ai_control_callback()
if(!wires.is_cut(WIRE_AI_CONTROL))
aidisabled = FALSE
updateDialog()
#undef APC_UPDATE_ICON_COOLDOWN
#undef APC_EXTERNAL_POWER_NOTCONNECTED
#undef APC_EXTERNAL_POWER_NOENERGY
#undef APC_EXTERNAL_POWER_GOOD
+16 -13
View File
@@ -229,14 +229,15 @@ By design, d1 is the smallest direction and d2 is the highest
if(current_size >= STAGE_FIVE)
deconstruct()
obj/structure/cable/proc/cable_color(var/colorC)
if(colorC)
if(colorC == "rainbow")
color = color_rainbow()
else
color = colorC
obj/structure/cable/proc/cable_color(colorC)
if(!colorC)
color = COLOR_RED
else if(colorC == "rainbow")
color = color_rainbow()
else if(colorC == "orange") //byond only knows 16 colors by name, and orange isn't one of them
color = COLOR_ORANGE
else
color = "#DD0000"
color = colorC
/obj/structure/cable/proc/color_rainbow()
color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
@@ -845,13 +846,15 @@ GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe("cable restrai
color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
..()
/obj/item/stack/cable_coil/proc/cable_color(var/colorC)
if(colorC)
if(colorC == "rainbow")
colorC = color_rainbow()
color = colorC
else
/obj/item/stack/cable_coil/proc/cable_color(colorC)
if(!colorC)
color = COLOR_RED
else if(colorC == "rainbow")
color = color_rainbow()
else if(colorC == "orange") //byond only knows 16 colors by name, and orange isn't one of them
color = COLOR_ORANGE
else
color = colorC
/obj/item/stack/cable_coil/proc/color_rainbow()
color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
+21 -5
View File
@@ -177,6 +177,8 @@
var/nightshift_light_power = 0.45
var/nightshift_light_color = "#FFDDCC"
var/bulb_emergency_colour = "#FF3232" // determines the colour of the light while it's in emergency mode
// the smaller bulb light fixture
/obj/machinery/light/small
@@ -238,7 +240,11 @@
switch(status) // set icon_states
if(LIGHT_OK)
icon_state = "[base_state][on]"
var/area/A = get_area(src)
if(A && A.fire)
icon_state = "[base_state]_emergency"
else
icon_state = "[base_state][on]"
if(LIGHT_EMPTY)
icon_state = "[base_state]-empty"
on = FALSE
@@ -260,10 +266,20 @@
on = FALSE
update_icon()
if(on)
var/BR = nightshift_enabled ? nightshift_light_range : brightness_range
var/PO = nightshift_enabled ? nightshift_light_power : brightness_power
var/CO = nightshift_enabled ? nightshift_light_color : brightness_color
var/matching = light_range == BR && light_power == PO && light_color == CO
var/BR = brightness_range
var/PO = brightness_power
var/CO = brightness_color
if(color)
CO = color
var/area/A = get_area(src)
if(A && A.fire)
CO = bulb_emergency_colour
else if(nightshift_enabled)
BR = nightshift_light_range
PO = nightshift_light_power
if(!color)
CO = nightshift_light_color
var/matching = light && BR == light.light_range && PO == light.light_power && CO == light.light_color
if(!matching)
switchcount++
if(rigged)
@@ -310,16 +310,25 @@ field_generator power level display
//This is here to help fight the "hurr durr, release singulo cos nobody will notice before the
//singulo eats the evidence". It's not fool-proof but better than nothing.
//I want to avoid using global variables.
spawn(1)
var/temp = 1 //stops spam
for(var/thing in GLOB.singularities)
var/obj/singularity/O = thing
if(O.last_warning && temp)
if((world.time - O.last_warning) > 50) //to stop message-spam
temp = 0
message_admins("A singulo exists and a containment field has failed. Location: [get_area(src)] (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[x];Y=[y];Z=[z]'>JMP</A>)",1)
investigate_log("has <font color='red'>failed</font> whilst a singulo exists.","singulo")
O.last_warning = world.time
INVOKE_ASYNC(src, .proc/admin_alert)
/obj/machinery/field/generator/proc/admin_alert()
var/temp = TRUE //stops spam
for(var/thing in GLOB.singularities)
var/obj/singularity/O = thing
if(O.last_warning && temp && atoms_share_level(O, src))
if((world.time - O.last_warning) > 50) //to stop message-spam
temp = FALSE
// To the person who asks "Hey affected, why are you using this massive operator when you can use AREACOORD?" Well, ill tell you
// get_area_name is fucking broken and uses a for(x in world) search
// It doesnt even work, is expensive, and returns 0
// Im not refactoring one thing which could risk breaking all admin location logs
// Fight me
// [src ? "[get_location_name(src, TRUE)] [COORD(src)]" : "nonexistent location"] [ADMIN_JMP(src)] works much better and actually works at all
// Oh and yes, this exact comment was pasted from the exact same thing I did to tcomms code. Dont at me.
message_admins("A singularity exists and a containment field has failed on the same Z-Level. Singulo location: [O ? "[get_location_name(O, TRUE)] [COORD(O)]" : "nonexistent location"] [ADMIN_JMP(O)] | Field generator location: [src ? "[get_location_name(src, TRUE)] [COORD(src)]" : "nonexistent location"] [ADMIN_JMP(src)]")
investigate_log("has <font color='red'>failed</font> whilst a singulo exists.","singulo")
O.last_warning = world.time
/obj/machinery/field/generator/shock_field(mob/living/user)
if(fields.len)
@@ -1,4 +1,4 @@
/area/engine/engineering/power_alert(var/alarming)
if(alarming)
investigate_log("has a power alarm!","singulo")
/area/engine/engineering/poweralert(state, source)
if(state != poweralm)
investigate_log("has a power alarm!", "singulo")
..()
@@ -27,6 +27,7 @@
use_log = list()
/obj/machinery/particle_accelerator/control_box/Destroy()
SStgui.close_uis(wires)
if(active)
toggle_power()
QDEL_NULL(wires)
@@ -41,6 +42,11 @@
else if(construction_state == 2) // Wires exposed
wires.Interact(user)
/obj/machinery/particle_accelerator/control_box/multitool_act(mob/living/user, obj/item/I)
if(construction_state == 2) // Wires exposed
wires.Interact(user)
return TRUE
/obj/machinery/particle_accelerator/control_box/update_state()
if(construction_state < 3)
use_power = NO_POWER_USE
@@ -93,18 +99,18 @@
usr.unset_machine()
return
if(href_list["togglep"])
if(!wires.IsIndexCut(PARTICLE_TOGGLE_WIRE))
if(!wires.is_cut(WIRE_PARTICLE_POWER))
toggle_power()
else if(href_list["scan"])
part_scan()
else if(href_list["strengthup"])
if(!wires.IsIndexCut(PARTICLE_STRENGTH_WIRE))
if(!wires.is_cut(WIRE_PARTICLE_STRENGTH))
add_strength()
else if(href_list["strengthdown"])
if(!wires.IsIndexCut(PARTICLE_STRENGTH_WIRE))
if(!wires.is_cut(WIRE_PARTICLE_STRENGTH))
remove_strength()
updateDialog()
+76 -72
View File
@@ -227,8 +227,8 @@
if(terminal)
terminal.master = null
terminal = null
return 1
return 0
return TRUE
return FALSE
/obj/machinery/power/smes/proc/make_terminal(user, tempDir, tempLoc)
// create a terminal object at the same position as original turf loc
@@ -339,89 +339,91 @@
/obj/machinery/power/smes/attack_ai(mob/user)
add_hiddenprint(user)
ui_interact(user)
tgui_interact(user)
/obj/machinery/power/smes/attack_ghost(mob/user)
ui_interact(user)
tgui_interact(user)
/obj/machinery/power/smes/attack_hand(mob/user)
add_fingerprint(user)
ui_interact(user)
tgui_interact(user)
/obj/machinery/power/smes/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
/obj/machinery/power/smes/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
if(stat & BROKEN)
return
// update the ui if it exists, returns null if no ui is passed/found
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
// the ui does not exist, so we'll create a new() one
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
ui = new(user, src, ui_key, "smes.tmpl", "SMES Power Storage Unit", 540, 380)
// open the new ui window
ui = new(user, src, ui_key, "Smes", name, 340, 350, master_ui, state)
ui.open()
// auto update every Master Controller tick
ui.set_auto_update(1)
/obj/machinery/power/smes/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
var/data[0]
data["nameTag"] = name_tag
data["storedCapacity"] = round(100.0*charge/capacity, 0.1)
data["charging"] = inputting
data["chargeMode"] = input_attempt
data["chargeLevel"] = input_level
data["chargeMax"] = input_level_max
data["outputOnline"] = output_attempt
data["outputLevel"] = output_level
data["outputMax"] = output_level_max
data["outputLoad"] = round(output_used)
if(outputting)
data["outputting"] = 2 // smes is outputting
else if(!outputting && output_attempt)
data["outputting"] = 1 // smes is online but not outputting because it's charge level is too low
else
data["outputting"] = 0 // smes is not outputting
/obj/machinery/power/smes/tgui_data(mob/user)
var/list/data = list(
"capacity" = capacity,
"capacityPercent" = round(100*charge/capacity, 0.1),
"charge" = charge,
"inputAttempt" = input_attempt,
"inputting" = inputting,
"inputLevel" = input_level,
"inputLevel_text" = DisplayPower(input_level),
"inputLevelMax" = input_level_max,
"inputAvailable" = input_available,
"outputAttempt" = output_attempt,
"outputting" = outputting,
"outputLevel" = output_level,
"outputLevel_text" = DisplayPower(output_level),
"outputLevelMax" = output_level_max,
"outputUsed" = round(output_used),
)
return data
/obj/machinery/power/smes/Topic(href, href_list)
/obj/machinery/power/smes/tgui_act(action, params)
if(..())
return 1
return
. = TRUE
switch(action)
if("tryinput")
inputting(!input_attempt)
update_icon()
if("tryoutput")
outputting(!output_attempt)
update_icon()
if("input")
var/target = params["target"]
var/adjust = text2num(params["adjust"])
if(target == "min")
target = 0
else if(target == "max")
target = input_level_max
else if(adjust)
target = input_level + adjust
else if(text2num(target) != null)
target = text2num(target)
else
. = FALSE
if(.)
input_level = clamp(target, 0, input_level_max)
if("output")
var/target = params["target"]
var/adjust = text2num(params["adjust"])
if(target == "min")
target = 0
else if(target == "max")
target = output_level_max
else if(adjust)
target = output_level + adjust
else if(text2num(target) != null)
target = text2num(target)
else
. = FALSE
if(.)
output_level = clamp(target, 0, output_level_max)
else
. = FALSE
if(.)
log_smes(usr)
if( href_list["cmode"] )
inputting(!input_attempt)
update_icon()
else if( href_list["online"] )
outputting(!output_attempt)
update_icon()
else if( href_list["input"] )
switch( href_list["input"] )
if("min")
input_level = 0
if("max")
input_level = input_level_max
if("set")
input_level = input(usr, "Enter new input level (0-[input_level_max])", "SMES Input Power Control", input_level) as num
input_level = max(0, min(input_level_max, input_level)) // clamp to range
else if( href_list["output"] )
switch( href_list["output"] )
if("min")
output_level = 0
if("max")
output_level = output_level_max
if("set")
output_level = input(usr, "Enter new output level (0-[output_level_max])", "SMES Output Power Control", output_level) as num
output_level = max(0, min(output_level_max, output_level)) // clamp to range
investigate_log("input/output; [input_level>output_level?"<font color='green'>":"<font color='red'>"][input_level]/[output_level]</font> | Output-mode: [output_attempt?"<font color='green'>on</font>":"<font color='red'>off</font>"] | Input-mode: [input_attempt?"<font color='green'>auto</font>":"<font color='red'>off</font>"] by [usr.key]","singulo")
return 1
/obj/machinery/power/smes/proc/log_smes(mob/user)
investigate_log("input/output; [input_level>output_level?"<font color='green'>":"<font color='red'>"][input_level]/[output_level]</font> | Charge: [charge] | Output-mode: [output_attempt?"<font color='green'>on</font>":"<font color='red'>off</font>"] | Input-mode: [input_attempt?"<font color='green'>auto</font>":"<font color='red'>off</font>"] by [user ? key_name(user) : "outside forces"]", "singulo")
/obj/machinery/power/smes/proc/ion_act()
if(is_station_level(src.z))
@@ -448,6 +450,7 @@
smoke.attach(src)
smoke.start()
/obj/machinery/power/smes/proc/inputting(var/do_input)
input_attempt = do_input
if(!input_attempt)
@@ -459,14 +462,15 @@
outputting = 0
/obj/machinery/power/smes/emp_act(severity)
inputting(rand(0,1))
outputting(rand(0,1))
inputting(rand(0, 1))
outputting(rand(0, 1))
output_level = rand(0, output_level_max)
input_level = rand(0, input_level_max)
charge -= 1e6/severity
if(charge < 0)
charge = 0
update_icon()
log_smes()
..()
/obj/machinery/power/smes/engineering
+1
View File
@@ -21,6 +21,7 @@
RefreshParts()
/obj/machinery/power/tesla_coil/Destroy()
SStgui.close_uis(wires)
QDEL_NULL(wires)
return ..()
+1
View File
@@ -123,6 +123,7 @@
if(keep)
stored_ammo.Insert(1,b)
update_mat_value()
update_icon()
return b
/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0)
@@ -133,6 +133,9 @@
/obj/item/ammo_box/magazine/internal/shot/riot/short
max_ammo = 3
/obj/item/ammo_box/magazine/internal/shot/riot/buckshot
ammo_type = /obj/item/ammo_casing/shotgun/buckshot
/obj/item/ammo_box/magazine/internal/grenadelauncher
name = "grenade launcher internal magazine"
ammo_type = /obj/item/ammo_casing/a40mm
@@ -380,10 +383,7 @@
origin_tech = "combat=3;syndicate=1"
caliber = "shotgun"
max_ammo = 8
/obj/item/ammo_box/magazine/m12g/update_icon()
..()
icon_state = "[initial(icon_state)]-[CEILING(ammo_count(0)/8, 1)*8]"
multiple_sprites = 2
/obj/item/ammo_box/magazine/m12g/buckshot
name = "shotgun magazine (12g buckshot slugs)"
@@ -411,6 +411,24 @@
icon_state = "m12gbc"
ammo_type = /obj/item/ammo_casing/shotgun/breaching
/obj/item/ammo_box/magazine/m12g/XtrLrg
name = "\improper XL shotgun magazine (12g slugs)"
desc = "An extra large drum magazine."
icon_state = "m12gXlSl"
w_class = WEIGHT_CLASS_NORMAL
ammo_type = /obj/item/ammo_casing/shotgun
max_ammo = 16
/obj/item/ammo_box/magazine/m12g/XtrLrg/buckshot
name = "\improper XL shotgun magazine (12g buckshot)"
icon_state = "m12gXlBs"
ammo_type = /obj/item/ammo_casing/shotgun/buckshot
/obj/item/ammo_box/magazine/m12g/XtrLrg/dragon
name = "\improper XL shotgun magazine (12g dragon's breath)"
icon_state = "m12gXlDb"
ammo_type = /obj/item/ammo_casing/shotgun/incendiary/dragonsbreath
/obj/item/ammo_box/magazine/toy
name = "foam force META magazine"
ammo_type = /obj/item/ammo_casing/caseless/foam_dart
@@ -95,6 +95,7 @@
if(!user.unEquip(A))
return
to_chat(user, "<span class='notice'>You screw [S] onto [src].</span>")
playsound(src, 'sound/items/screwdriver.ogg', 40, 1)
suppressed = A
S.oldsound = fire_sound
S.initial_w_class = w_class
@@ -120,6 +121,7 @@
..()
return
to_chat(user, "<span class='notice'>You unscrew [suppressed] from [src].</span>")
playsound(src, 'sound/items/screwdriver.ogg', 40, 1)
user.put_in_hands(suppressed)
fire_sound = S.oldsound
w_class = S.initial_w_class
@@ -275,13 +275,27 @@
if(magazine)
overlays.Cut()
overlays += "[magazine.icon_state]"
return
if(istype(magazine, /obj/item/ammo_box/magazine/m12g/XtrLrg))
w_class = WEIGHT_CLASS_BULKY
else
w_class = WEIGHT_CLASS_NORMAL
else
w_class = WEIGHT_CLASS_NORMAL
/obj/item/gun/projectile/automatic/shotgun/bulldog/update_icon()
overlays.Cut()
update_magazine()
icon_state = "bulldog[chambered ? "" : "-e"]"
/obj/item/gun/projectile/automatic/shotgun/bulldog/attackby(var/obj/item/A as obj, mob/user as mob, params)
if(istype(A, /obj/item/ammo_box/magazine/m12g/XtrLrg))
if(istype(loc, /obj/item/storage)) // To prevent inventory exploits
var/obj/item/storage/Strg = loc
if(Strg.max_w_class < WEIGHT_CLASS_BULKY)
to_chat(user, "<span class='warning'>You can't reload [src], with a XL mag, while it's in a normal bag.</span>")
return
return ..()
/obj/item/gun/projectile/automatic/shotgun/bulldog/afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag)
..()
empty_alarm()
@@ -197,6 +197,8 @@
..()
post_sawoff()
/obj/item/gun/projectile/shotgun/riot/buckshot //comes pre-loaded with buckshot rather than rubber
mag_type = /obj/item/ammo_box/magazine/internal/shot/riot/buckshot
///////////////////////

Some files were not shown because too many files have changed in this diff Show More