Merge branch 'master' into tgui-medical

This commit is contained in:
mochi
2020-09-15 12:46:30 +02:00
532 changed files with 13408 additions and 4181 deletions
-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
@@ -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)
@@ -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')
+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 ..()
+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
+74 -21
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))
@@ -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
@@ -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)
+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 =====
@@ -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
+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()
+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
///////////////////////
@@ -543,6 +543,14 @@
build_path = /obj/item/assembly/health
category = list("initial", "Medical")
/datum/design/stethoscope
name = "Stethoscope"
id = "stethoscope"
build_type = AUTOLATHE
materials = list(MAT_METAL = 500)
build_path = /obj/item/clothing/accessory/stethoscope
category = list("initial", "Medical")
/datum/design/timer
name = "Timer"
id = "timer"
@@ -272,6 +272,16 @@
build_path = /obj/item/circuitboard/solar_control
category = list("Computer Boards")
/datum/design/sm_monitor
name = "Console Board (Supermatter Monitoring)"
desc = "Allows for the construction of circuit boards used to build a supermatter monitoring console"
id = "sm_monitor"
req_tech = list("programming" = 2, "powerstorage" = 2)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000)
build_path = /obj/item/circuitboard/sm_monitor
category = list("Computer Boards")
/datum/design/spacepodlocator
name = "Console Board (Spacepod Locator)"
desc = "Allows for the construction of circuit boards used to build a space-pod locating console"
@@ -751,7 +751,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_mining_scanner
name = "Exosuit Engineering Equipement (Mining Scanner)"
name = "Exosuit Engineering Equipment (Mining Scanner)"
id = "mech_mscanner"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/mining_scanner
+1 -2
View File
@@ -291,8 +291,7 @@
for(var/turf/T in oview(1, src))
if(!T.density)
if(prob(EFFECT_PROB_VERYHIGH))
var/obj/effect/decal/cleanable/reagentdecal = new/obj/effect/decal/cleanable/greenglow(T)
reagentdecal.reagents.add_reagent("radium", 7)
new /obj/effect/decal/cleanable/greenglow(T)
if(prob(EFFECT_PROB_MEDIUM-badThingCoeff))
var/savedName = "[exp_on]"
ejectItem(TRUE)
+1 -1
View File
@@ -296,7 +296,7 @@
return
var/sound/S = sound(mysound)
S.wait = 0 //No queue
S.channel = open_sound_channel()
S.channel = SSsounds.random_available_channel()
S.volume = 50
for(var/mob/M in passengers | pilot)
M << S
+18 -24
View File
@@ -91,7 +91,7 @@
/obj/machinery/bsa/middle/proc/check_completion()
if(!front || !back)
return "No linked parts detected!"
return "No multitool-linked parts detected!"
if(!front.anchored || !back.anchored || !anchored)
return "Linked parts unwrenched!"
if(front.y != y || back.y != y || !(front.x > x && back.x < x || front.x < x && back.x > x) || front.z != z || back.z != z)
@@ -305,51 +305,45 @@
/obj/machinery/computer/bsa_control/attack_hand(mob/user)
if(..())
return 1
ui_interact(user)
tgui_interact(user)
/obj/machinery/computer/bsa_control/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)
/obj/machinery/computer/bsa_control/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)
ui = new(user, src, ui_key, "bsa.tmpl", name, 400, 305)
ui = new(user, src, ui_key, "BlueSpaceArtilleryControl", name, 400, 155, master_ui, state)
ui.open()
ui.set_auto_update(1)
/obj/machinery/computer/bsa_control/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
/obj/machinery/computer/bsa_control/tgui_data(mob/user)
var/list/data = list()
data["connected"] = cannon
data["notice"] = notice
if(target)
data["target"] = get_target_name()
if(cannon)
var/reload_cooldown = cannon.reload_cooldown
var/last_fire_time = cannon.last_fire_time
var/time_to_wait = max(0, round(reload_cooldown - ((world.time / 10) - last_fire_time)))
var/minutes = max(0, round(time_to_wait / 60))
var/seconds = max(0, time_to_wait - (60 * minutes))
data["reloadtime_mins"] = minutes
data["reloadtime_secs"] = (seconds < 10) ? "0[seconds]" : seconds
var/seconds2 = (seconds < 10) ? "0[seconds]" : seconds
data["reloadtime_text"] = "[minutes]:[seconds2]"
data["ready"] = minutes == 0 && seconds == 0
else
data["ready"] = FALSE
return data
/obj/machinery/computer/bsa_control/Topic(href, href_list)
/obj/machinery/computer/bsa_control/tgui_act(action, params)
if(..())
return 1
if(href_list["build"])
cannon = deploy()
. = TRUE
else if(href_list["fire"])
fire(usr)
. = TRUE
else if(href_list["recalibrate"])
calibrate(usr)
. = TRUE
return
switch(action)
if("build")
cannon = deploy()
if("fire")
fire(usr)
if("recalibrate")
calibrate(usr)
update_icon()
return TRUE
/obj/machinery/computer/bsa_control/proc/calibrate(mob/user)
var/list/gps_locators = list()
+1 -1
View File
@@ -7,7 +7,7 @@
#define VAULT_NOBREATH "Lung Enhancement"
#define VAULT_FIREPROOF "Thermal Regulation"
#define VAULT_STUNTIME "Neural Repathing"
#define VAULT_ARMOUR "Bone Reinforcement"
#define VAULT_ARMOUR "Hardened Skin"
#define VAULT_SPEED "Leg Muscle Stimulus"
#define VAULT_QUICK "Arm Muscle Stimulus"
+6
View File
@@ -111,6 +111,12 @@
typepath = /obj/item/instrument/eguitar
cost = 500
/datum/storeitem/banjo
name = "Banjo"
desc = "It's pretty much just a drum with a neck and strings."
typepath = /obj/item/instrument/banjo
cost = 500
/datum/storeitem/piano_synth
name = "Piano Synthesizer"
desc = "An advanced electronic synthesizer that can emulate various instruments."
+1 -1
View File
@@ -54,7 +54,7 @@
var/obj/item/organ/external/BP = X
var/brutedamage = BP.brute_dam
if(BP.is_robotic() && !ismachineperson(src))
if(BP.is_robotic())
continue
//We want an accurate reading of .len
+1 -1
View File
@@ -8,7 +8,7 @@
var/eye_colour = "#000000" // Should never be null
var/list/colourmatrix = null
var/list/colourblind_matrix = MATRIX_GREYSCALE //Special colourblindness parameters. By default, it's black-and-white.
var/list/replace_colours = LIST_GREYSCALE_REPLACE
var/list/replace_colours = GREYSCALE_COLOR_REPLACE
var/dependent_disabilities = list() //Gets set by eye-dependent disabilities such as colourblindness so the eyes can transfer the disability during transplantation.
var/weld_proof = null //If set, the eyes will not take damage during welding. eg. IPC optical sensors do not take damage when they weld things while all other eyes will.
-29
View File
@@ -236,35 +236,6 @@
status &= ~ORGAN_SPLINTED
status |= ORGAN_ROBOT
/obj/item/organ/external/emp_act(severity)
if(!is_robotic() || emp_proof)
return
if(tough)
switch(severity)
if(1)
receive_damage(0, 5.5)
if(owner)
owner.Stun(10)
if(2)
receive_damage(0, 2.8)
if(owner)
owner.Stun(5)
else
switch(severity)
if(1)
receive_damage(0, 20)
if(2)
receive_damage(0, 7)
/obj/item/organ/internal/emp_act(severity)
if(!is_robotic() || emp_proof)
return
switch(severity)
if(1)
receive_damage(20, 1)
if(2)
receive_damage(7, 1)
/obj/item/organ/proc/shock_organ(intensity)
return
@@ -41,6 +41,9 @@
var/list/obj/item/organ/external/children
var/list/convertable_children = list()
// Does the organ take reduce damage from EMPs? IPC limbs get this by default
var/emp_resistant = FALSE
// Internal organs of this body part
var/list/internal_organs = list()
@@ -256,6 +259,34 @@
return update_icon()
/obj/item/organ/external/emp_act(severity)
if(!is_robotic() || emp_proof)
return
if(tough) // Augmented limbs
switch(severity)
if(1)
receive_damage(0, 5.5)
if(owner)
owner.Stun(10)
if(2)
receive_damage(0, 2.8)
if(owner)
owner.Stun(5)
else if(emp_resistant) // IPC limbs
switch(severity)
if(1)
// 5.28 (9 * 0.66 burn_mod) burn damage, 65.34 damage with 11 limbs.
receive_damage(0, 9)
if(2)
// 3.63 (5 * 0.66 burn_mod) burn damage, 39.93 damage with 11 limbs.
receive_damage(0, 5.5)
else // Basic prosthetic limbs
switch(severity)
if(1)
receive_damage(0, 20)
if(2)
receive_damage(0, 7)
/*
This function completely restores a damaged organ to perfect condition.
*/
@@ -73,6 +73,15 @@
A.Remove(M)
return src
/obj/item/organ/internal/emp_act(severity)
if(!is_robotic() || emp_proof)
return
switch(severity)
if(1)
receive_damage(20, 1)
if(2)
receive_damage(7, 1)
/obj/item/organ/internal/replaced(var/mob/living/carbon/human/target)
insert(target)
@@ -5,6 +5,7 @@
min_broken_damage = 30
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/head/ipc/New(mob/living/carbon/holder, datum/species/species_override = null)
..(holder, /datum/species/machine) // IPC heads need to be explicitly set to this since you can print them
@@ -13,6 +14,7 @@
/obj/item/organ/external/chest/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/chest/ipc/New()
..()
@@ -21,6 +23,7 @@
/obj/item/organ/external/groin/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/groin/ipc/New()
..()
@@ -29,6 +32,7 @@
/obj/item/organ/external/arm/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/arm/ipc/New()
..()
@@ -37,6 +41,7 @@
/obj/item/organ/external/arm/right/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/arm/right/ipc/New()
..()
@@ -45,6 +50,7 @@
/obj/item/organ/external/leg/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/leg/ipc/New()
..()
@@ -53,6 +59,7 @@
/obj/item/organ/external/leg/right/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/leg/right/ipc/New()
..()
@@ -61,6 +68,7 @@
/obj/item/organ/external/foot/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/foot/ipc/New()
..()
@@ -69,6 +77,7 @@
/obj/item/organ/external/foot/right/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/foot/right/ipc/New()
..()
@@ -77,6 +86,7 @@
/obj/item/organ/external/hand/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/hand/ipc/New()
..()
@@ -85,6 +95,7 @@
/obj/item/organ/external/hand/right/ipc
encased = null
status = ORGAN_ROBOT
emp_resistant = TRUE
/obj/item/organ/external/hand/right/ipc/New()
..()
@@ -7,14 +7,14 @@
icon = 'icons/obj/species_organs/tajaran.dmi'
name = "tajaran eyeballs"
colourblind_matrix = MATRIX_TAJ_CBLIND //The colour matrix and darksight parameters that the mob will recieve when they get the disability.
replace_colours = LIST_TAJ_REPLACE
replace_colours = TRITANOPIA_COLOR_REPLACE
see_in_dark = 8
/obj/item/organ/internal/eyes/tajaran/farwa //Being the lesser form of Tajara, Farwas have an utterly incurable version of their colourblindness.
name = "farwa eyeballs"
colourmatrix = MATRIX_TAJ_CBLIND
see_in_dark = 8
replace_colours = LIST_TAJ_REPLACE
replace_colours = TRITANOPIA_COLOR_REPLACE
/obj/item/organ/internal/heart/tajaran
name = "tajaran heart"
@@ -7,14 +7,14 @@
name = "vulpkanin eyeballs"
icon = 'icons/obj/species_organs/vulpkanin.dmi'
colourblind_matrix = MATRIX_VULP_CBLIND //The colour matrix and darksight parameters that the mob will recieve when they get the disability.
replace_colours = LIST_VULP_REPLACE
replace_colours = PROTANOPIA_COLOR_REPLACE
see_in_dark = 8
/obj/item/organ/internal/eyes/vulpkanin/wolpin //Being the lesser form of Vulpkanin, Wolpins have an utterly incurable version of their colourblindness.
name = "wolpin eyeballs"
colourmatrix = MATRIX_VULP_CBLIND
see_in_dark = 8
replace_colours = LIST_VULP_REPLACE
replace_colours = PROTANOPIA_COLOR_REPLACE
/obj/item/organ/internal/heart/vulpkanin
name = "vulpkanin heart"