mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2025-12-26 01:52:29 +00:00
Merged with dev.
This commit is contained in:
@@ -292,7 +292,7 @@
|
||||
if("monkey") M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob )
|
||||
if("robot") M.change_mob_type( /mob/living/silicon/robot , null, null, delmob )
|
||||
if("cat") M.change_mob_type( /mob/living/simple_animal/cat , null, null, delmob )
|
||||
if("runtime") M.change_mob_type( /mob/living/simple_animal/cat/Runtime , null, null, delmob )
|
||||
if("runtime") M.change_mob_type( /mob/living/simple_animal/cat/fluff/Runtime , null, null, delmob )
|
||||
if("corgi") M.change_mob_type( /mob/living/simple_animal/corgi , null, null, delmob )
|
||||
if("ian") M.change_mob_type( /mob/living/simple_animal/corgi/Ian , null, null, delmob )
|
||||
if("crab") M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob )
|
||||
|
||||
138
code/modules/alarm/alarm.dm
Normal file
138
code/modules/alarm/alarm.dm
Normal file
@@ -0,0 +1,138 @@
|
||||
#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/proc/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 = SecondsToTicks(duration)
|
||||
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.master
|
||||
|
||||
/area/get_alarm_area()
|
||||
return src.master
|
||||
|
||||
/atom/proc/get_alarm_name()
|
||||
var/area/A = get_area(src)
|
||||
return A.master.name
|
||||
|
||||
/area/get_alarm_name()
|
||||
return master.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()
|
||||
|
||||
#undef ALARM_LOSS_DELAY
|
||||
99
code/modules/alarm/alarm_handler.dm
Normal file
99
code/modules/alarm/alarm_handler.dm
Normal file
@@ -0,0 +1,99 @@
|
||||
#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/proc/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/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)
|
||||
invalidateCameraCache()
|
||||
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.master // 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
code/modules/alarm/atmosphere_alarm.dm
Normal file
19
code/modules/alarm/atmosphere_alarm.dm
Normal file
@@ -0,0 +1,19 @@
|
||||
/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
code/modules/alarm/camera_alarm.dm
Normal file
2
code/modules/alarm/camera_alarm.dm
Normal file
@@ -0,0 +1,2 @@
|
||||
/datum/alarm_handler/camera
|
||||
category = "Camera Alarms"
|
||||
11
code/modules/alarm/fire_alarm.dm
Normal file
11
code/modules/alarm/fire_alarm.dm
Normal file
@@ -0,0 +1,11 @@
|
||||
/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
code/modules/alarm/motion_alarm.dm
Normal file
2
code/modules/alarm/motion_alarm.dm
Normal file
@@ -0,0 +1,2 @@
|
||||
/datum/alarm_handler/motion
|
||||
category = "Motion Alarms"
|
||||
10
code/modules/alarm/power_alarm.dm
Normal file
10
code/modules/alarm/power_alarm.dm
Normal file
@@ -0,0 +1,10 @@
|
||||
/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)
|
||||
@@ -24,4 +24,11 @@
|
||||
/obj/item/rig_module/electrowarfare_suite,
|
||||
/obj/item/rig_module/chem_dispenser/combat,
|
||||
/obj/item/rig_module/fabricator/energy_net
|
||||
)
|
||||
|
||||
//Has most of the modules removed
|
||||
/obj/item/weapon/rig/merc/empty
|
||||
initial_modules = list(
|
||||
/obj/item/rig_module/ai_container,
|
||||
/obj/item/rig_module/electrowarfare_suite, //might as well
|
||||
)
|
||||
@@ -1412,12 +1412,6 @@
|
||||
|
||||
////////////////////////////// Foxler - Erstatz Vryroxes /////////////////////////////////////////////////
|
||||
|
||||
/obj/item/weapon/holder/cat/fluff/bones
|
||||
name = "Bones"
|
||||
desc = "It's Bones! Meow."
|
||||
gender = MALE
|
||||
icon_state = "cat3"
|
||||
|
||||
//Use this subtype for spawning in the custom item.
|
||||
/obj/item/weapon/holder/cat/fluff/bones/custom_item
|
||||
|
||||
@@ -1426,6 +1420,12 @@
|
||||
new/mob/living/simple_animal/cat/fluff/bones (src)
|
||||
..()
|
||||
|
||||
/obj/item/weapon/holder/cat/fluff/bones
|
||||
name = "Bones"
|
||||
desc = "It's Bones! Meow."
|
||||
gender = MALE
|
||||
icon_state = "cat3"
|
||||
|
||||
/mob/living/simple_animal/cat/fluff/bones
|
||||
name = "Bones"
|
||||
desc = "That's Bones the cat. He's a laid back, black cat. Meow."
|
||||
@@ -1434,61 +1434,12 @@
|
||||
icon_living = "cat3"
|
||||
icon_dead = "cat3_dead"
|
||||
holder_type = /obj/item/weapon/holder/cat/fluff/bones
|
||||
bff_name = "Erstatz Vryroxes"
|
||||
var/friend_name = "Erstatz Vryroxes"
|
||||
|
||||
/mob/living/simple_animal/cat/fluff
|
||||
var/bff_name
|
||||
var/mob/living/carbon/human/bff
|
||||
|
||||
/mob/living/simple_animal/cat/fluff/handle_movement_target()
|
||||
if (!bff)
|
||||
/mob/living/simple_animal/cat/fluff/bones/handle_movement_target()
|
||||
if (!friend)
|
||||
for (var/mob/living/carbon/human/M in player_list)
|
||||
if (M.real_name == bff_name)
|
||||
bff = M
|
||||
if (M.real_name == friend_name)
|
||||
friend = M
|
||||
break
|
||||
|
||||
if (bff)
|
||||
var/follow_dist = 5
|
||||
if (bff.stat >= DEAD || bff.health <= config.health_threshold_softcrit) //danger
|
||||
follow_dist = 1
|
||||
else if (bff.stat || bff.health <= 50) //danger or just sleeping
|
||||
follow_dist = 2
|
||||
var/near_dist = max(follow_dist - 3, 1)
|
||||
var/current_dist = get_dist(src, bff)
|
||||
|
||||
if (movement_target != bff)
|
||||
if (current_dist > follow_dist && !istype(movement_target, /mob/living/simple_animal/mouse) && (bff in oview(src)))
|
||||
//stop existing movement
|
||||
walk_to(src,0)
|
||||
turns_since_scan = 0
|
||||
|
||||
//walk to bff
|
||||
stop_automated_movement = 1
|
||||
movement_target = bff
|
||||
walk_to(src, movement_target, near_dist, 4)
|
||||
|
||||
//already following and close enough, stop
|
||||
else if (current_dist <= near_dist)
|
||||
walk_to(src,0)
|
||||
movement_target = null
|
||||
stop_automated_movement = 0
|
||||
|
||||
if (!(bff && movement_target == bff))
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/cat/fluff/Life()
|
||||
..()
|
||||
if (stat || !bff)
|
||||
return
|
||||
if (get_dist(src, bff) <= 1)
|
||||
if (bff.stat >= DEAD || bff.health <= config.health_threshold_softcrit)
|
||||
if (prob((bff.stat < DEAD)? 50 : 15))
|
||||
audible_emote(pick("meows in distress.", "meows anxiously."))
|
||||
else
|
||||
if (prob(5))
|
||||
visible_emote(pick("nuzzles [bff].",
|
||||
"brushes against [bff].",
|
||||
"rubs against [bff].",
|
||||
"purrs."))
|
||||
else if (bff.health <= 50)
|
||||
if (prob(10)) audible_emote("meows anxiously.")
|
||||
|
||||
166
code/modules/examine/descriptions/atmospherics.dm
Normal file
166
code/modules/examine/descriptions/atmospherics.dm
Normal file
@@ -0,0 +1,166 @@
|
||||
/obj/machinery/atmospherics/pipe
|
||||
description_info = "This pipe, and all other pipes, can be connected or disconnected by a wrench. The internal pressure of the pipe must \
|
||||
be below 300 kPa to do this. More pipes can be obtained from the pipe dispenser."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/New() //This is needed or else 20+ lines of copypasta to dance around inheritence.
|
||||
..()
|
||||
description_info += "<br>Most pipes and atmospheric devices can be connected or disconnected with a wrench. The pipe's pressure must not be too high, \
|
||||
or if it is a device, it must be turned off first."
|
||||
|
||||
//HE pipes
|
||||
/obj/machinery/atmospherics/pipe/simple/heat_exchanging
|
||||
description_info = "This radiates heat from the pipe's gas to space, cooling it down."
|
||||
|
||||
//Supply/Scrubber pipes
|
||||
/obj/machinery/atmospherics/pipe/simple/visible/scrubbers
|
||||
description_info = "This is a special 'scrubber' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/simple/visible/supply
|
||||
description_info = "This is a special 'supply' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/simple/hidden/supply
|
||||
description_info = "This is a special 'supply' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers
|
||||
description_info = "This is a special 'scrubber' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
//Universal adapters
|
||||
/obj/machinery/atmospherics/pipe/simple/visible/universal
|
||||
description_info = "This allows you to connect 'normal' pipes, red 'scrubber' pipes, and blue 'supply' pipes."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/simple/hidden/universal
|
||||
description_info = "This allows you to connect 'normal' pipes, red 'scrubber' pipes, and blue 'supply' pipes."
|
||||
|
||||
//Three way manifolds
|
||||
/obj/machinery/atmospherics/pipe/manifold
|
||||
description_info = "A normal pipe with three ends to connect to."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold/visible/scrubbers
|
||||
description_info = "This is a special 'scrubber' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold/visible/supply
|
||||
description_info = "This is a special 'supply' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers
|
||||
description_info = "This is a special 'scrubber' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold/hidden/supply
|
||||
description_info = "This is a special 'supply' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
//Insulated pipes
|
||||
/obj/machinery/atmospherics/pipe/simple/insulated
|
||||
description_info = "This is completely useless, use a normal pipe." //Sorry, but it's true.
|
||||
|
||||
//Four way manifolds
|
||||
/obj/machinery/atmospherics/pipe/manifold4w
|
||||
description_info = "This is a four-way pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold4w/visible/scrubbers
|
||||
description_info = "This is a special 'scrubber' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold4w/hidden/supply
|
||||
description_info = "This is a special 'supply' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
/obj/machinery/atmospherics/pipe/manifold4w/hidden/scrubbers
|
||||
description_info = "This is a special 'scrubber' pipe, which does not connect to 'normal' pipes. If you want to connect it, use \
|
||||
a Universal Adapter pipe."
|
||||
|
||||
//Endcaps
|
||||
/obj/machinery/atmospherics/pipe/cap
|
||||
description_info = "This is a cosmetic attachment, as pipes currently do not spill their contents into the air."
|
||||
|
||||
//T-shaped valves
|
||||
/obj/machinery/atmospherics/tvalve
|
||||
description_info = "Click this to toggle the mode. The direction with the green light is where the gas will flow."
|
||||
|
||||
//Normal valves
|
||||
/obj/machinery/atmospherics/valve
|
||||
description_info = "Click this to turn the valve. If red, the pipes on each end are seperated. Otherwise, they are connected."
|
||||
|
||||
//TEG ports
|
||||
/obj/machinery/atmospherics/binary/circulator
|
||||
description_info = "This generates electricity, depending on the difference in temperature between each side of the machine. The meter in \
|
||||
the center of the machine gives an indicator of how much elecrtricity is being generated."
|
||||
|
||||
//Passive gates
|
||||
/obj/machinery/atmospherics/binary/passive_gate
|
||||
description_info = "This is a one-way regulator, allowing gas to flow only at a specific pressure and flow rate. If the light is green, it is flowing."
|
||||
|
||||
//Normal pumps (high power one inherits from this)
|
||||
/obj/machinery/atmospherics/binary/pump
|
||||
description_info = "This moves gas from one pipe to another. A higher target pressure demands more energy. The side with the red end is the output."
|
||||
|
||||
//Vents
|
||||
/obj/machinery/atmospherics/unary/vent_pump
|
||||
description_info = "This pumps the contents of the attached pipe out into the atmosphere, if needed. It can be controlled from an Air Alarm."
|
||||
|
||||
//Freezers
|
||||
/obj/machinery/atmospherics/unary/freezer
|
||||
description_info = "Cools down the gas of the pipe it is connected to. It uses massive amounts of electricity while on. \
|
||||
It can be upgraded by replacing the capacitors, manipulators, and matter bins. It can be deconstructed by screwing the maintenance panel open with a \
|
||||
screwdriver, and then using a crowbar."
|
||||
|
||||
//Heaters
|
||||
/obj/machinery/atmospherics/unary/heater
|
||||
description_info = "Heats up the gas of the pipe it is connected to. It uses massive amounts of electricity while on. \
|
||||
It can be upgraded by replacing the capacitors, manipulators, and matter bins. It can be deconstructed by screwing the maintenance panel open with a \
|
||||
screwdriver, and then using a crowbar."
|
||||
|
||||
//Gas injectors
|
||||
/obj/machinery/atmospherics/unary/outlet_injector
|
||||
description_info = "Outputs the pipe's gas into the atmosphere, similar to an airvent. It can be controlled by a nearby atmospherics computer. \
|
||||
A green light on it means it is on."
|
||||
|
||||
//Scrubbers
|
||||
/obj/machinery/atmospherics/unary/vent_scrubber
|
||||
description_info = "This filters the atmosphere of harmful gas. Filtered gas goes to the pipes connected to it, typically a scrubber pipe. \
|
||||
It can be controlled from an Air Alarm. It can be configured to drain all air rapidly with a 'panic syphon' from an air alarm."
|
||||
|
||||
//Omni filters
|
||||
/obj/machinery/atmospherics/omni/filter
|
||||
description_info = "Filters gas from a custom input direction, with up to two filtered outputs and a 'everything else' \
|
||||
output. The filtered output's arrows glow orange."
|
||||
|
||||
//Omni mixers
|
||||
/obj/machinery/atmospherics/omni/mixer
|
||||
description_info = "Combines gas from custom input and output directions. The percentage of combined gas can be defined."
|
||||
|
||||
//Canisters
|
||||
/obj/machinery/portable_atmospherics/canister
|
||||
description_info = "The canister can be connected to a connector port with a wrench. Tanks of gas (the kind you can hold in your hand) \
|
||||
can be filled by the canister, by using the tank on the canister, increasing the release pressure, then opening the valve until it is full, and then close it. \
|
||||
*DO NOT* remove the tank until the valve is closed. A gas analyzer can be used to check the contents of the canister."
|
||||
|
||||
description_antag = "Canisters can be damaged, spilling their contents into the air, or you can just leave the release valve open."
|
||||
|
||||
//Portable pumps
|
||||
/obj/machinery/portable_atmospherics/powered/pump
|
||||
description_info = "Invaluable for filling air in a room rapidly after a breach repair. The internal gas container can be filled by \
|
||||
connecting it to a connector port. The pump can pump the air in (sucking) or out (blowing), at a specific target pressure. The powercell inside can be \
|
||||
replaced by using a screwdriver, and then adding a new cell. A tank of gas can also be attached to the air pump."
|
||||
|
||||
//Portable scrubbers
|
||||
/obj/machinery/portable_atmospherics/powered/scrubber
|
||||
description_info = "Filters the air, placing harmful gases into the internal gas container. The container can be emptied by \
|
||||
connecting it to a connector port. The pump can pump the air in (sucking) or out (blowing), at a specific target pressure. The powercell inside can be \
|
||||
replaced by using a screwdriver, and then adding a new cell. A tank of gas can also be attached to the scrubber. "
|
||||
|
||||
//Meters
|
||||
/obj/machinery/meter
|
||||
description_info = "Measures the volume and temperature of the pipe under the meter."
|
||||
|
||||
//Pipe dispensers
|
||||
/obj/machinery/pipedispenser
|
||||
description_info = "This can be moved by using a wrench. You will need to wrench it again when you want to use it. You can put \
|
||||
excess (atmospheric) pipes into the dispenser, as well. The dispenser requires electricity to function."
|
||||
|
||||
35
code/modules/examine/descriptions/engineering.dm
Normal file
35
code/modules/examine/descriptions/engineering.dm
Normal file
@@ -0,0 +1,35 @@
|
||||
/obj/machinery/power/supermatter
|
||||
description_info = "When energized by a laser (or something hitting it), it emits radiation and heat. If the heat reaches above 7000 kelvin, it will send an alert and start taking damage. \
|
||||
After integrity falls to zero percent, it will delaminate, causing a massive explosion, station-wide radiation spikes, and hallucinations. \
|
||||
Supermatter reacts badly to oxygen in the atmosphere. It'll also heat up really quick if it is in vacuum.<br>\
|
||||
<br>\
|
||||
Supermatter cores are extremely dangerous to be close to, and requires protection to handle properly. The protection you will need is:<br>\
|
||||
Optical meson scanners on your eyes, to prevent hallucinations when looking at the supermatter.<br>\
|
||||
Radiation helmet and suit, as the supermatter is radioactive.<br>\
|
||||
<br>\
|
||||
Touching the supermatter will result in *instant death*, with no corpse left behind! You can drag the supermatter, but anything else will kill you. \
|
||||
It is advised to obtain a genetic backup before trying to drag it."
|
||||
|
||||
description_antag = "Exposing the supermatter to oxygen or vaccum will cause it to start rapidly heating up. Sabotaging the supermatter and making it explode will \
|
||||
cause a period of lag as the explosion is processed by the server, as well as irradiating the entire station and causing hallucinations to happen. \
|
||||
Wearing radiation equipment will protect you from most of the delamination effects sans explosion."
|
||||
|
||||
/obj/machinery/power/apc
|
||||
description_info = "An APC (Area Power Controller) regulates and supplies backup power for the area they are in. Their power channels are divided \
|
||||
out into 'environmental' (Items that manipulate airflow and temperature), 'lighting' (the lights), and 'equipment' (Everything else that consumes power). \
|
||||
Power consumption and backup power cell charge can be seen from the interface, further controls (turning a specific channel on, off or automatic, \
|
||||
toggling the APC's ability to charge the backup cell, or toggling power for the entire area via master breaker) first requires the interface to be unlocked \
|
||||
with an ID with Engineering access or by one of the station's robots or the artificial intelligence."
|
||||
|
||||
description_antag = "This can be emagged to unlock it. It will cause the APC to have a blue error screen. \
|
||||
Wires can be pulsed remotely with a signaler attached to it. A powersink will also drain any APCs connected to the same wire the powersink is on."
|
||||
|
||||
/obj/item/inflatable
|
||||
description_info = "Inflate by using it in your hand. The inflatable barrier will inflate on your tile. To deflate it, use the 'deflate' verb."
|
||||
|
||||
/obj/structure/inflatable
|
||||
description_info = "To remove these safely, use the 'deflate' verb. Hitting these with any objects will probably puncture and break it forever."
|
||||
|
||||
/obj/structure/inflatable/door
|
||||
description_info = "Click the door to open or close it. It only stops air while closed.<br>\
|
||||
To remove these safely, use the 'deflate' verb. Hitting these with any objects will probably puncture and break it forever."
|
||||
8
code/modules/examine/descriptions/mobs.dm
Normal file
8
code/modules/examine/descriptions/mobs.dm
Normal file
@@ -0,0 +1,8 @@
|
||||
/mob/living/silicon/robot/drone
|
||||
description_info = "Drones are player-controlled synthetics which are lawed to maintain the station and not \
|
||||
interact with anyone else, except for other drones. They hold a wide array of tools to build, repair, maintain, and clean. \
|
||||
They fuction similarly to other synthetics, in that they require recharging regularly, have laws, and are resilient to many hazards, \
|
||||
such as fire, radiation, vacuum, and more. Ghosts can join the round as a maintenance drone by using the appropriate verb in the 'ghost' tab. \
|
||||
An inactive drone can be rebooted by swiping an ID card on it with engineering or robotics access."
|
||||
|
||||
description_antag = "An Electromagnetic Sequencer can be used to subvert the drone to your cause."
|
||||
24
code/modules/examine/descriptions/stacks.dm
Normal file
24
code/modules/examine/descriptions/stacks.dm
Normal file
@@ -0,0 +1,24 @@
|
||||
/obj/item/stack/rods
|
||||
description_info = "Made from metal sheets. You can build a grille by using it in your hand. \
|
||||
Clicking on a floor without any tiles will reinforce the floor. You can make reinforced glass by combining rods and normal glass sheets."
|
||||
|
||||
/obj/item/stack/sheet/glass
|
||||
description_info = "Use in your hand to build a window. Can be upgraded to reinforced glass by adding metal rods, which are made from metal sheets."
|
||||
|
||||
/obj/item/stack/sheet/glass/cyborg
|
||||
description_info = "Use in your hand to build a window. Can be upgraded to reinforced glass by adding metal rods, which are made from metal sheets.<br>\
|
||||
As a synthetic, you can acquire more sheets of glass by recharging."
|
||||
|
||||
/obj/item/stack/sheet/glass/reinforced
|
||||
description_info = "Use in your hand to build a window. Reinforced glass is much stronger against damage."
|
||||
|
||||
/obj/item/stack/sheet/glass/reinforced/cyborg
|
||||
description_info = "Use in your hand to build a window. Reinforced glass is much stronger against damage.<br>\
|
||||
As a synthetic, you can gain more reinforced glass by recharging."
|
||||
|
||||
/obj/item/stack/sheet/metal/cyborg
|
||||
description_info = "Use in your hand to bring up the recipe menu. If you have enough sheets, click on something on the list to build it.<br>\
|
||||
You can replenish your supply of metal as a synthetic by recharging."
|
||||
|
||||
/obj/item/stack/sheet
|
||||
description_info = "Use in your hand to bring up the recipe menu. If you have enough sheets, click on something on the list to build it."
|
||||
15
code/modules/examine/descriptions/structures.dm
Normal file
15
code/modules/examine/descriptions/structures.dm
Normal file
@@ -0,0 +1,15 @@
|
||||
/obj/structure/girder
|
||||
description_info = "Use metal sheets on this to build a normal wall. Adding plasteel instead will make a reinforced wall.<br>\
|
||||
A false wall can be made by using a crowbar on this girder, and then adding metal or plasteel.<br>\
|
||||
You can dismantle the grider with a wrench."
|
||||
|
||||
/obj/structure/girder/reinforced
|
||||
description_info = "Add another sheet of plasteel to finish."
|
||||
|
||||
/obj/structure/grille
|
||||
description_info = "A powered and knotted wire underneath this will cause the grille to shock anyone not wearing insulated gloves.<br>\
|
||||
Wirecutters will turn the grille into metal rods instantly. Grilles are made with metal rods."
|
||||
|
||||
/obj/structure/lattice
|
||||
description_info = "Add a metal floor tile to build a floor on top of the lattice.<br>\
|
||||
Lattices can be made by applying metal rods to a space tile."
|
||||
3
code/modules/examine/descriptions/turfs.dm
Normal file
3
code/modules/examine/descriptions/turfs.dm
Normal file
@@ -0,0 +1,3 @@
|
||||
/turf/simulated/wall
|
||||
description_info = "You can deconstruct this by welding it, and then wrenching the girder.<br>\
|
||||
You can build a wall by using metal sheets and making a girder, then adding more metal or plasteel."
|
||||
71
code/modules/examine/examine.dm
Normal file
71
code/modules/examine/examine.dm
Normal file
@@ -0,0 +1,71 @@
|
||||
/* This code is responsible for the examine tab. When someone examines something, it copies the examined object's description_info,
|
||||
description_fluff, and description_antag, and shows it in a new tab.
|
||||
|
||||
In this file, some atom and mob stuff is defined here. It is defined here instead of in the normal files, to keep the whole system self-contained.
|
||||
This means that this file can be unchecked, along with the other examine files, and can be removed entirely with no effort.
|
||||
*/
|
||||
|
||||
|
||||
/atom/
|
||||
var/description_info = null //Helpful blue text.
|
||||
var/description_fluff = null //Green text about the atom's fluff, if any exists.
|
||||
var/description_antag = null //Malicious red text, for the antags.
|
||||
|
||||
/atom/examine(mob/user)
|
||||
..()
|
||||
user.description_holders["info"] = get_description_info()
|
||||
user.description_holders["fluff"] = get_description_fluff()
|
||||
if(user.mind && user.mind.special_role || isobserver(user)) //Runtime prevention, as ghosts don't have minds.
|
||||
user.description_holders["antag"] = get_description_antag()
|
||||
|
||||
if(name) //This shouldn't be needed but I'm paranoid.
|
||||
user.description_holders["name"] = "[src.name]" //\icon[src]
|
||||
|
||||
user.description_holders["icon"] = "\icon[src]"
|
||||
|
||||
if(desc)
|
||||
user << desc
|
||||
user.description_holders["desc"] = src.desc
|
||||
else
|
||||
user.description_holders["desc"] = null //This is needed, or else if you examine one thing with a desc, then another without, the panel will retain the first examined's desc.
|
||||
|
||||
//Override these if you need special behaviour for a specific type.
|
||||
|
||||
/atom/proc/get_description_info()
|
||||
if(description_info)
|
||||
return description_info
|
||||
return
|
||||
|
||||
/atom/proc/get_description_fluff()
|
||||
if(description_fluff)
|
||||
return description_fluff
|
||||
return
|
||||
|
||||
/atom/proc/get_description_antag()
|
||||
if(description_antag)
|
||||
return description_antag
|
||||
return
|
||||
|
||||
/mob/
|
||||
var/description_holders[0]
|
||||
|
||||
/mob/Stat()
|
||||
..()
|
||||
if(statpanel("Examine"))
|
||||
stat(null,"[description_holders["icon"]] <font size='5'>[description_holders["name"]]</font>") //The name, written in big letters.
|
||||
stat(null,"[description_holders["desc"]]") //the default examine text.
|
||||
if(description_holders["info"])
|
||||
stat(null,"<font color='#084B8A'><b>[description_holders["info"]]</b></font>") //Blue, informative text.
|
||||
if(description_holders["fluff"])
|
||||
stat(null,"<font color='#298A08'><b>[description_holders["fluff"]]</b></font>") //Yellow, fluff-related text.
|
||||
if(description_holders["antag"])
|
||||
stat(null,"<font color='#8A0808'><b>[description_holders["antag"]]</b></font>") //Red, malicious antag-related text
|
||||
|
||||
/mob/living/get_description_fluff()
|
||||
if(flavor_text) //Get flavor text for the green text.
|
||||
return flavor_text
|
||||
else //No flavor text? Try for hardcoded fluff instead.
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/get_description_fluff()
|
||||
return print_flavor_text(0)
|
||||
@@ -1280,9 +1280,6 @@
|
||||
else
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/get_descriptions_fluff()
|
||||
return print_flavor_text(0)
|
||||
|
||||
/mob/living/carbon/human/getDNA()
|
||||
if(species.flags & NO_SCAN)
|
||||
return null
|
||||
|
||||
@@ -1240,7 +1240,7 @@
|
||||
if(!druggy) see_invisible = SEE_INVISIBLE_LEVEL_TWO
|
||||
if(healths) healths.icon_state = "health7" //DEAD healthmeter
|
||||
if(client)
|
||||
if(client.view != world.view) // If mob moves while zoomed in with device, unzoom them.
|
||||
if(client.view != world.view) // If mob dies while zoomed in with device, unzoom them.
|
||||
for(var/obj/item/item in contents)
|
||||
if(item.zoom)
|
||||
item.zoom()
|
||||
|
||||
@@ -171,13 +171,6 @@
|
||||
|
||||
// ++++ROCKDTBEN++++ MOB PROCS //END
|
||||
|
||||
|
||||
/mob/living/get_descriptions_fluff()
|
||||
if(flavor_text) //Get flavor text for the green text.
|
||||
return flavor_text
|
||||
else //No flavor text? Try for hardcoded fluff instead.
|
||||
return ..()
|
||||
|
||||
/mob/proc/get_contents()
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
var/list/ai_list = list()
|
||||
var/list/ai_verbs_default = list(
|
||||
/mob/living/silicon/ai/proc/ai_alerts,
|
||||
/mob/living/silicon/ai/proc/ai_announcement,
|
||||
/mob/living/silicon/ai/proc/ai_call_shuttle,
|
||||
// /mob/living/silicon/ai/proc/ai_recall_shuttle,
|
||||
@@ -23,10 +22,7 @@ var/list/ai_verbs_default = list(
|
||||
/mob/living/silicon/ai/proc/sensor_mode,
|
||||
/mob/living/silicon/ai/proc/show_laws_verb,
|
||||
/mob/living/silicon/ai/proc/toggle_acceleration,
|
||||
/mob/living/silicon/ai/proc/toggle_camera_light,
|
||||
/mob/living/silicon/ai/proc/nano_rcon,
|
||||
/mob/living/silicon/ai/proc/nano_crew_monitor,
|
||||
/mob/living/silicon/ai/proc/nano_power_monitor
|
||||
/mob/living/silicon/ai/proc/toggle_camera_light
|
||||
)
|
||||
|
||||
//Not sure why this is necessary...
|
||||
@@ -83,9 +79,11 @@ var/list/ai_verbs_default = list(
|
||||
|
||||
/mob/living/silicon/ai/proc/add_ai_verbs()
|
||||
src.verbs |= ai_verbs_default
|
||||
src.verbs |= ai_verbs_subsystems
|
||||
|
||||
/mob/living/silicon/ai/proc/remove_ai_verbs()
|
||||
src.verbs -= ai_verbs_default
|
||||
src.verbs -= ai_verbs_subsystems
|
||||
|
||||
/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/device/mmi/B, var/safety = 0)
|
||||
announcement = new()
|
||||
@@ -166,8 +164,6 @@ var/list/ai_verbs_default = list(
|
||||
hud_list[IMPTRACK_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
|
||||
hud_list[SPECIALROLE_HUD] = image('icons/mob/hud.dmi', src, "hudblank")
|
||||
|
||||
init_subsystems()
|
||||
|
||||
ai_list += src
|
||||
..()
|
||||
return
|
||||
@@ -325,36 +321,6 @@ var/list/ai_verbs_default = list(
|
||||
if(malf && malf.apcs >= 3)
|
||||
stat(null, "Time until station control secured: [max(malf.AI_win_timeleft/(malf.apcs/3), 0)] seconds")
|
||||
|
||||
/mob/living/silicon/ai/proc/ai_alerts()
|
||||
set category = "AI Commands"
|
||||
set name = "Show Alerts"
|
||||
|
||||
var/dat = "<HEAD><TITLE>Current Station Alerts</TITLE><META HTTP-EQUIV='Refresh' CONTENT='10'></HEAD><BODY>\n"
|
||||
dat += "<A HREF='?src=\ref[src];mach_close=aialerts'>Close</A><BR><BR>"
|
||||
for (var/cat in alarms)
|
||||
dat += text("<B>[]</B><BR>\n", cat)
|
||||
var/list/alarmlist = alarms[cat]
|
||||
if (alarmlist.len)
|
||||
for (var/area_name in alarmlist)
|
||||
var/datum/alarm/alarm = alarmlist[area_name]
|
||||
dat += "<NOBR>"
|
||||
|
||||
var/cameratext = ""
|
||||
if (alarm.cameras)
|
||||
for (var/obj/machinery/camera/I in alarm.cameras)
|
||||
cameratext += text("[]<A HREF=?src=\ref[];switchcamera=\ref[]>[]</A>", (cameratext=="") ? "" : " | ", src, I, I.c_tag)
|
||||
dat += text("-- [] ([])", alarm.area.name, (cameratext)? cameratext : "No Camera")
|
||||
|
||||
if (alarm.sources.len > 1)
|
||||
dat += text(" - [] sources", alarm.sources.len)
|
||||
dat += "</NOBR><BR>\n"
|
||||
else
|
||||
dat += "-- All Systems Nominal<BR>\n"
|
||||
dat += "<BR>\n"
|
||||
|
||||
viewalerts = 1
|
||||
src << browse(dat, "window=aialerts&can_close=0")
|
||||
|
||||
// this verb lets the ai see the stations manifest
|
||||
/mob/living/silicon/ai/proc/ai_roster()
|
||||
set category = "AI Commands"
|
||||
@@ -451,7 +417,7 @@ var/list/ai_verbs_default = list(
|
||||
if (href_list["switchcamera"])
|
||||
switchCamera(locate(href_list["switchcamera"])) in cameranet.cameras
|
||||
if (href_list["showalerts"])
|
||||
ai_alerts()
|
||||
subsystem_alarm_monitor()
|
||||
//Carn: holopad requests
|
||||
if (href_list["jumptoholopad"])
|
||||
var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"])
|
||||
@@ -526,29 +492,6 @@ var/list/ai_verbs_default = list(
|
||||
|
||||
return 1
|
||||
|
||||
/mob/living/silicon/ai/triggerAlarm(var/class, area/A, list/cameralist, var/source)
|
||||
if (stat == 2)
|
||||
return 1
|
||||
|
||||
..()
|
||||
|
||||
var/cameratext = ""
|
||||
for (var/obj/machinery/camera/C in cameralist)
|
||||
cameratext += "[(cameratext == "")? "" : "|"]<A HREF=?src=\ref[src];switchcamera=\ref[C]>[C.c_tag]</A>"
|
||||
|
||||
queueAlarm("--- [class] alarm detected in [A.name]! ([(cameratext)? cameratext : "No Camera"])", class)
|
||||
|
||||
if (viewalerts) ai_alerts()
|
||||
|
||||
/mob/living/silicon/ai/cancelAlarm(var/class, area/A as area, var/source)
|
||||
var/has_alarm = ..()
|
||||
|
||||
if (!has_alarm)
|
||||
queueAlarm(text("--- [] alarm in [] has been cleared.", class, A.name), class, 0)
|
||||
if (viewalerts) ai_alerts()
|
||||
|
||||
return has_alarm
|
||||
|
||||
/mob/living/silicon/ai/cancel_camera()
|
||||
set category = "AI Commands"
|
||||
set name = "Cancel Camera View"
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
sleep(50)
|
||||
theAPC = null
|
||||
|
||||
process_queued_alarms()
|
||||
regular_hud_updates()
|
||||
switch(src.sensor_mode)
|
||||
if (SEC_HUD)
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
var/obj/nano_module/crew_monitor/crew_monitor
|
||||
var/obj/nano_module/rcon/rcon
|
||||
var/obj/nano_module/power_monitor/power_monitor
|
||||
|
||||
/mob/living/silicon/ai/proc/init_subsystems()
|
||||
crew_monitor = new(src)
|
||||
rcon = new(src)
|
||||
power_monitor = new(src)
|
||||
|
||||
/mob/living/silicon/ai/proc/nano_crew_monitor()
|
||||
set category = "AI Subystems"
|
||||
set name = "Crew Monitor"
|
||||
|
||||
crew_monitor.ui_interact(usr)
|
||||
|
||||
/mob/living/silicon/ai/proc/nano_power_monitor()
|
||||
set category = "AI Subystems"
|
||||
set name = "Power Monitor"
|
||||
|
||||
power_monitor.ui_interact(usr)
|
||||
|
||||
|
||||
/mob/living/silicon/ai/proc/nano_rcon()
|
||||
set category = "AI Subystems"
|
||||
set name = "RCON"
|
||||
|
||||
rcon.ui_interact(usr)
|
||||
44
code/modules/mob/living/silicon/ai/subsystems.dm
Normal file
44
code/modules/mob/living/silicon/ai/subsystems.dm
Normal file
@@ -0,0 +1,44 @@
|
||||
var/list/ai_verbs_subsystems = list(
|
||||
/mob/living/silicon/ai/proc/subsystem_alarm_monitor,
|
||||
/mob/living/silicon/ai/proc/subsystem_crew_monitor,
|
||||
/mob/living/silicon/ai/proc/subsystem_power_monitor,
|
||||
/mob/living/silicon/ai/proc/subsystem_rcon
|
||||
)
|
||||
|
||||
/mob/living/silicon/ai
|
||||
var/
|
||||
var/obj/nano_module/crew_monitor/crew_monitor
|
||||
var/obj/nano_module/rcon/rcon
|
||||
var/obj/nano_module/power_monitor/power_monitor
|
||||
|
||||
/mob/living/silicon/ai/init_subsystems()
|
||||
..()
|
||||
del(alarm_monitor)
|
||||
alarm_monitor = new/obj/nano_module/alarm_monitor/ai(src)
|
||||
crew_monitor = new(src)
|
||||
rcon = new(src)
|
||||
power_monitor = new(src)
|
||||
|
||||
/mob/living/silicon/ai/proc/subsystem_alarm_monitor()
|
||||
set name = "Alarm Monitor"
|
||||
set category = "AI Subystems"
|
||||
|
||||
alarm_monitor.ui_interact(usr)
|
||||
|
||||
/mob/living/silicon/ai/proc/subsystem_crew_monitor()
|
||||
set category = "AI Subystems"
|
||||
set name = "Crew Monitor"
|
||||
|
||||
crew_monitor.ui_interact(usr)
|
||||
|
||||
/mob/living/silicon/ai/proc/subsystem_power_monitor()
|
||||
set category = "AI Subystems"
|
||||
set name = "Power Monitor"
|
||||
|
||||
power_monitor.ui_interact(usr)
|
||||
|
||||
/mob/living/silicon/ai/proc/subsystem_rcon()
|
||||
set category = "AI Subystems"
|
||||
set name = "RCON"
|
||||
|
||||
rcon.ui_interact(usr)
|
||||
@@ -1,111 +0,0 @@
|
||||
/datum/alarm
|
||||
var/area/area //the area associated with the alarm. Used to identify the alarm
|
||||
var/list/sources //list of things triggering the alarm. Used to determine when the alarm should be cleared.
|
||||
var/list/cameras //list of cameras that can be switched to, if the player has that capability.
|
||||
|
||||
/datum/alarm/New(area/A, list/sourcelist=list(), list/cameralist=list())
|
||||
area = A
|
||||
sources = sourcelist
|
||||
cameras = cameralist
|
||||
|
||||
/mob/living/silicon
|
||||
var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list()) //each sublist stores alarms keyed by the area name
|
||||
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)
|
||||
|
||||
/mob/living/silicon/proc/triggerAlarm(var/class, area/A, list/cameralist, var/source)
|
||||
var/list/alarmlist = alarms[class]
|
||||
|
||||
//see if there is already an alarm of this class for this area
|
||||
if (A.name in alarmlist)
|
||||
var/datum/alarm/existing = alarmlist[A.name]
|
||||
existing.sources += source
|
||||
existing.cameras |= cameralist
|
||||
else
|
||||
alarmlist[A.name] = new /datum/alarm(A, list(source), cameralist)
|
||||
|
||||
/mob/living/silicon/proc/cancelAlarm(var/class, area/A as area, var/source)
|
||||
var/cleared = 0
|
||||
var/list/alarmlist = alarms[class]
|
||||
|
||||
if (A.name in alarmlist)
|
||||
var/datum/alarm/alarm = alarmlist[A.name]
|
||||
alarm.sources -= source
|
||||
|
||||
if (!(alarm.sources.len))
|
||||
cleared = 1
|
||||
alarmlist -= A.name
|
||||
|
||||
return !cleared
|
||||
|
||||
/mob/living/silicon/proc/queueAlarm(var/message, var/type, var/incoming = 1)
|
||||
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)
|
||||
spawn(10 * 10) // 10 seconds
|
||||
|
||||
if(alarms_to_show.len < 5)
|
||||
for(var/msg in alarms_to_show)
|
||||
src << msg
|
||||
else if(alarms_to_show.len)
|
||||
|
||||
var/msg = "--- "
|
||||
|
||||
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["Power"]] alarms detected. - "
|
||||
|
||||
msg += "<A href=?src=\ref[src];showalerts=1'>\[Show Alerts\]</a>"
|
||||
src << msg
|
||||
|
||||
if(alarms_to_clear.len < 3)
|
||||
for(var/msg in alarms_to_clear)
|
||||
src << msg
|
||||
|
||||
else if(alarms_to_clear.len)
|
||||
var/msg = "--- "
|
||||
|
||||
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_show["Power"]] alarms detected. - "
|
||||
|
||||
msg += "<A href=?src=\ref[src];showalerts=1'>\[Show Alerts\]</a>"
|
||||
src << msg
|
||||
|
||||
|
||||
alarms_to_show = list()
|
||||
alarms_to_clear = list()
|
||||
for(var/i = 1; i < alarm_types_show.len; i++)
|
||||
alarm_types_show[i] = 0
|
||||
for(var/i = 1; i < alarm_types_clear.len; i++)
|
||||
alarm_types_clear[i] = 0
|
||||
@@ -3,13 +3,6 @@
|
||||
real_name = "drone"
|
||||
icon = 'icons/mob/robots.dmi'
|
||||
icon_state = "repairbot"
|
||||
descriptions = new/datum/descriptions("Drones are player-controlled synthetics which are lawed to maintain the station and not \
|
||||
interact with anyone else, except for other drones. They hold a wide array of tools to build, repair, maintain, and clean. \
|
||||
They fuction similarly to other synthetics, in that they require recharging regularly, have laws, and are resilient to many hazards, \
|
||||
such as fire, radiation, vacuum, and more. Ghosts can join the round as a maintenance drone by using the appropriate verb in the 'ghost' tab. \
|
||||
An inactive drone can be rebooted by swiping an ID card on it with engineering or robotics access.",\
|
||||
,"An <u>Electromagnetic Sequencer</u> can be used to subvert the drone to your cause.")
|
||||
//desc_fluff is already provided with flavor_text.
|
||||
maxHealth = 35
|
||||
health = 35
|
||||
universal_speak = 0
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
use_power()
|
||||
process_killswitch()
|
||||
process_locks()
|
||||
process_queued_alarms()
|
||||
update_canmove()
|
||||
|
||||
/mob/living/silicon/robot/proc/clamp_values()
|
||||
|
||||
@@ -437,39 +437,12 @@ var/list/robot_verbs_default = list(
|
||||
updatename()
|
||||
updateicon()
|
||||
|
||||
/mob/living/silicon/robot/verb/cmd_robot_alerts()
|
||||
set category = "Robot Commands"
|
||||
set name = "Show Alerts"
|
||||
robot_alerts()
|
||||
|
||||
// this verb lets cyborgs see the stations manifest
|
||||
/mob/living/silicon/robot/verb/cmd_station_manifest()
|
||||
set category = "Robot Commands"
|
||||
set name = "Show Crew Manifest"
|
||||
show_station_manifest()
|
||||
|
||||
|
||||
/mob/living/silicon/robot/proc/robot_alerts()
|
||||
var/dat = "<HEAD><TITLE>Current Station Alerts</TITLE><META HTTP-EQUIV='Refresh' CONTENT='10'></HEAD><BODY>\n"
|
||||
dat += "<A HREF='?src=\ref[src];mach_close=robotalerts'>Close</A><BR><BR>"
|
||||
for (var/cat in alarms)
|
||||
dat += text("<B>[cat]</B><BR>\n")
|
||||
var/list/alarmlist = alarms[cat]
|
||||
if (alarmlist.len)
|
||||
for (var/area_name in alarmlist)
|
||||
var/datum/alarm/alarm = alarmlist[area_name]
|
||||
dat += "<NOBR>"
|
||||
dat += text("-- [area_name]")
|
||||
if (alarm.sources.len > 1)
|
||||
dat += text("- [alarm.sources.len] sources")
|
||||
dat += "</NOBR><BR>\n"
|
||||
else
|
||||
dat += "-- All Systems Nominal<BR>\n"
|
||||
dat += "<BR>\n"
|
||||
|
||||
viewalerts = 1
|
||||
src << browse(dat, "window=robotalerts&can_close=0")
|
||||
|
||||
/mob/living/silicon/robot/proc/self_diagnosis()
|
||||
if(!is_component_functioning("diagnosis unit"))
|
||||
return null
|
||||
@@ -639,25 +612,6 @@ var/list/robot_verbs_default = list(
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
/mob/living/silicon/robot/triggerAlarm(var/class, area/A, list/cameralist, var/source)
|
||||
if (stat == 2)
|
||||
return 1
|
||||
|
||||
..()
|
||||
|
||||
queueAlarm(text("--- [class] alarm detected in [A.name]!"), class)
|
||||
|
||||
|
||||
/mob/living/silicon/robot/cancelAlarm(var/class, area/A as area, obj/origin)
|
||||
var/has_alarm = ..()
|
||||
|
||||
if (!has_alarm)
|
||||
queueAlarm(text("--- [class] alarm in [A.name] has been cleared."), class, 0)
|
||||
// if (viewalerts) robot_alerts()
|
||||
return has_alarm
|
||||
|
||||
|
||||
/mob/living/silicon/robot/attackby(obj/item/weapon/W as obj, mob/user as mob)
|
||||
if (istype(W, /obj/item/weapon/handcuffs)) // fuck i don't even know why isrobot() in handcuff code isn't working so this will have to do
|
||||
return
|
||||
@@ -1043,30 +997,31 @@ var/list/robot_verbs_default = list(
|
||||
|
||||
/mob/living/silicon/robot/Topic(href, href_list)
|
||||
if(..())
|
||||
return
|
||||
return 1
|
||||
if(usr != src)
|
||||
return
|
||||
return 1
|
||||
|
||||
if (href_list["showalerts"])
|
||||
robot_alerts()
|
||||
return
|
||||
subsystem_alarm_monitor()
|
||||
return 1
|
||||
|
||||
if (href_list["mod"])
|
||||
var/obj/item/O = locate(href_list["mod"])
|
||||
if (istype(O) && (O.loc == src))
|
||||
O.attack_self(src)
|
||||
return 1
|
||||
|
||||
if (href_list["act"])
|
||||
var/obj/item/O = locate(href_list["act"])
|
||||
if (!istype(O))
|
||||
return
|
||||
return 1
|
||||
|
||||
if(!((O in src.module.modules) || (O == src.module.emag)))
|
||||
return
|
||||
return 1
|
||||
|
||||
if(activated(O))
|
||||
src << "Already activated"
|
||||
return
|
||||
return 1
|
||||
if(!module_state_1)
|
||||
module_state_1 = O
|
||||
O.layer = 20
|
||||
@@ -1088,6 +1043,7 @@ var/list/robot_verbs_default = list(
|
||||
else
|
||||
src << "You need to disable a module first!"
|
||||
installed_modules()
|
||||
return 1
|
||||
|
||||
if (href_list["deact"])
|
||||
var/obj/item/O = locate(href_list["deact"])
|
||||
@@ -1106,6 +1062,7 @@ var/list/robot_verbs_default = list(
|
||||
else
|
||||
src << "Module isn't activated"
|
||||
installed_modules()
|
||||
return 1
|
||||
|
||||
if (href_list["lawc"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite
|
||||
var/L = text2num(href_list["lawc"])
|
||||
@@ -1114,6 +1071,7 @@ var/list/robot_verbs_default = list(
|
||||
if ("No") lawcheck[L+1] = "Yes"
|
||||
// src << text ("Switching Law [L]'s report status to []", lawcheck[L+1])
|
||||
checklaws()
|
||||
return 1
|
||||
|
||||
if (href_list["lawi"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite
|
||||
var/L = text2num(href_list["lawi"])
|
||||
@@ -1122,9 +1080,11 @@ var/list/robot_verbs_default = list(
|
||||
if ("No") ioncheck[L] = "Yes"
|
||||
// src << text ("Switching Law [L]'s report status to []", lawcheck[L+1])
|
||||
checklaws()
|
||||
return 1
|
||||
|
||||
if (href_list["laws"]) // With how my law selection code works, I changed statelaws from a verb to a proc, and call it through my law selection panel. --NeoFite
|
||||
statelaws()
|
||||
return 1
|
||||
return
|
||||
|
||||
/mob/living/silicon/robot/proc/radio_menu()
|
||||
@@ -1257,9 +1217,11 @@ var/list/robot_verbs_default = list(
|
||||
|
||||
/mob/living/silicon/robot/proc/add_robot_verbs()
|
||||
src.verbs |= robot_verbs_default
|
||||
src.verbs |= robot_verbs_subsystems
|
||||
|
||||
/mob/living/silicon/robot/proc/remove_robot_verbs()
|
||||
src.verbs -= robot_verbs_default
|
||||
src.verbs -= robot_verbs_subsystems
|
||||
|
||||
// Uses power from cyborg's cell. Returns 1 on success or 0 on failure.
|
||||
// Properly converts using CELLRATE now! Amount is in Joules.
|
||||
|
||||
9
code/modules/mob/living/silicon/robot/subsystems.dm
Normal file
9
code/modules/mob/living/silicon/robot/subsystems.dm
Normal file
@@ -0,0 +1,9 @@
|
||||
var/list/robot_verbs_subsystems = list(
|
||||
/mob/living/silicon/robot/proc/subsystem_alarm_monitor
|
||||
)
|
||||
|
||||
/mob/living/silicon/robot/proc/subsystem_alarm_monitor()
|
||||
set name = "Alarm Monitor"
|
||||
set category = "Robot Subystems"
|
||||
|
||||
alarm_monitor.ui_interact(usr)
|
||||
@@ -22,13 +22,26 @@
|
||||
var/obj/item/device/camera/siliconcam/aiCamera = null //photography
|
||||
var/local_transmit //If set, can only speak to others of the same type within a short range.
|
||||
|
||||
// Subsystems
|
||||
var/obj/nano_module/alarm_monitor = null
|
||||
|
||||
var/sensor_mode = 0 //Determines the current HUD.
|
||||
|
||||
var/next_alarm_notice
|
||||
var/list/datum/alarm/queued_alarms = new()
|
||||
|
||||
#define SEC_HUD 1 //Security HUD mode
|
||||
#define MED_HUD 2 //Medical HUD mode
|
||||
|
||||
/mob/living/silicon/New()
|
||||
..()
|
||||
add_language("Galactic Common")
|
||||
init_subsystems()
|
||||
|
||||
/mob/living/silicon/Del()
|
||||
for(var/datum/alarm_handler/AH in alarm_manager.all_handlers)
|
||||
AH.unregister(src)
|
||||
..()
|
||||
|
||||
/mob/living/silicon/proc/SetName(pickedName as text)
|
||||
real_name = pickedName
|
||||
@@ -243,7 +256,8 @@
|
||||
return 1
|
||||
|
||||
/mob/living/silicon/Topic(href, href_list)
|
||||
..()
|
||||
if(..())
|
||||
return 1
|
||||
|
||||
if (href_list["lawr"]) // Selects on which channel to state laws
|
||||
var/list/channels = list(MAIN_CHANNEL)
|
||||
@@ -282,3 +296,62 @@
|
||||
adjustBruteLoss(30)
|
||||
|
||||
updatehealth()
|
||||
|
||||
/mob/living/silicon/proc/init_subsystems()
|
||||
alarm_monitor = new/obj/nano_module/alarm_monitor/borg(src)
|
||||
for(var/datum/alarm_handler/AH in alarm_manager.all_handlers)
|
||||
AH.register(src, /mob/living/silicon/proc/receive_alarm)
|
||||
queued_alarms[AH] = list() // Makes sure alarms remain listed in consistent order
|
||||
|
||||
/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 + SecondsToTicks(10)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
src << "<span class='notice'>--- [AH.category] Cleared ---</span>"
|
||||
src << "\The [A.alarm_name()]."
|
||||
|
||||
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)
|
||||
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=\ref[src];switchcamera=\ref[C]>[C.c_tag]</A>"
|
||||
src << "[A.alarm_name()]! ([(cameratext)? cameratext : "No Camera"])"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
response_harm = "kicks"
|
||||
var/turns_since_scan = 0
|
||||
var/mob/living/simple_animal/mouse/movement_target
|
||||
var/mob/flee_target
|
||||
min_oxy = 16 //Require atleast 16kPA oxygen
|
||||
minbodytemp = 223 //Below -50 Degrees Celcius
|
||||
maxbodytemp = 323 //Above 50 Degrees Celcius
|
||||
@@ -44,27 +45,71 @@
|
||||
break
|
||||
|
||||
if(!stat && !resting && !buckled)
|
||||
handle_movement_target()
|
||||
turns_since_scan++
|
||||
if (turns_since_scan > 5)
|
||||
walk_to(src,0)
|
||||
turns_since_scan = 0
|
||||
|
||||
if (flee_target) //fleeing takes precendence
|
||||
handle_flee_target()
|
||||
else
|
||||
handle_movement_target()
|
||||
|
||||
/mob/living/simple_animal/cat/proc/handle_movement_target()
|
||||
turns_since_scan++
|
||||
if(turns_since_scan > 5)
|
||||
walk_to(src,0)
|
||||
turns_since_scan = 0
|
||||
/mob/living/simple_animal/cat/proc/handle_movement_target()
|
||||
//if our target is neither inside a turf or inside a human(???), stop
|
||||
if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) ))
|
||||
movement_target = null
|
||||
stop_automated_movement = 0
|
||||
//if we have no target or our current one is out of sight/too far away
|
||||
if( !movement_target || !(movement_target.loc in oview(src, 4)) )
|
||||
movement_target = null
|
||||
stop_automated_movement = 0
|
||||
for(var/mob/living/simple_animal/mouse/snack in oview(src)) //search for a new target
|
||||
if(isturf(snack.loc) && !snack.stat)
|
||||
movement_target = snack
|
||||
break
|
||||
|
||||
if(movement_target)
|
||||
stop_automated_movement = 1
|
||||
walk_to(src,movement_target,0,3)
|
||||
|
||||
if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) ))
|
||||
movement_target = null
|
||||
stop_automated_movement = 0
|
||||
if( !movement_target || !(movement_target.loc in oview(src, 4)) )
|
||||
movement_target = null
|
||||
stop_automated_movement = 0
|
||||
for(var/mob/living/simple_animal/mouse/snack in oview(src))
|
||||
if(isturf(snack.loc) && !snack.stat)
|
||||
movement_target = snack
|
||||
break
|
||||
if(movement_target)
|
||||
stop_automated_movement = 1
|
||||
walk_to(src,movement_target,0,3)
|
||||
/mob/living/simple_animal/cat/proc/handle_flee_target()
|
||||
//see if we should stop fleeing
|
||||
if (flee_target && !(flee_target.loc in view(src)))
|
||||
flee_target = null
|
||||
stop_automated_movement = 0
|
||||
|
||||
if (flee_target)
|
||||
if(prob(25)) say("HSSSSS")
|
||||
stop_automated_movement = 1
|
||||
walk_away(src, flee_target, 7, 2)
|
||||
|
||||
/mob/living/simple_animal/cat/proc/set_flee_target(atom/A)
|
||||
if(A)
|
||||
flee_target = A
|
||||
turns_since_scan = 5
|
||||
|
||||
/mob/living/simple_animal/cat/attackby(var/obj/item/O, var/mob/user)
|
||||
. = ..()
|
||||
if(O.force)
|
||||
set_flee_target(user? user : src.loc)
|
||||
|
||||
/mob/living/simple_animal/cat/attack_hand(mob/living/carbon/human/M as mob)
|
||||
. = ..()
|
||||
if(M.a_intent == "hurt")
|
||||
set_flee_target(M)
|
||||
|
||||
/mob/living/simple_animal/cat/ex_act()
|
||||
. = ..()
|
||||
set_flee_target(src.loc)
|
||||
|
||||
/mob/living/simple_animal/cat/bullet_act(var/obj/item/projectile/proj)
|
||||
. = ..()
|
||||
set_flee_target(proj.firer? proj.firer : src.loc)
|
||||
|
||||
/mob/living/simple_animal/cat/hitby(atom/movable/AM)
|
||||
. = ..()
|
||||
set_flee_target(AM.thrower? AM.thrower : src.loc)
|
||||
|
||||
/mob/living/simple_animal/cat/MouseDrop(atom/over_object)
|
||||
|
||||
@@ -82,14 +127,91 @@
|
||||
return //since the holder icon looks like a living cat
|
||||
..()
|
||||
|
||||
//Basic friend AI
|
||||
/mob/living/simple_animal/cat/fluff
|
||||
var/mob/living/carbon/human/friend
|
||||
var/befriend_job = null
|
||||
|
||||
/mob/living/simple_animal/cat/fluff/handle_movement_target()
|
||||
if (friend)
|
||||
var/follow_dist = 5
|
||||
if (friend.stat >= DEAD || friend.health <= config.health_threshold_softcrit) //danger
|
||||
follow_dist = 1
|
||||
else if (friend.stat || friend.health <= 50) //danger or just sleeping
|
||||
follow_dist = 2
|
||||
var/near_dist = max(follow_dist - 2, 1)
|
||||
var/current_dist = get_dist(src, friend)
|
||||
|
||||
if (movement_target != friend)
|
||||
if (current_dist > follow_dist && !istype(movement_target, /mob/living/simple_animal/mouse) && (friend in oview(src)))
|
||||
//stop existing movement
|
||||
walk_to(src,0)
|
||||
turns_since_scan = 0
|
||||
|
||||
//walk to friend
|
||||
stop_automated_movement = 1
|
||||
movement_target = friend
|
||||
walk_to(src, movement_target, near_dist, 4)
|
||||
|
||||
//already following and close enough, stop
|
||||
else if (current_dist <= near_dist)
|
||||
walk_to(src,0)
|
||||
movement_target = null
|
||||
stop_automated_movement = 0
|
||||
if (prob(10))
|
||||
say("Meow!")
|
||||
|
||||
if (!friend || movement_target != friend)
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/cat/fluff/Life()
|
||||
..()
|
||||
if (stat || !friend)
|
||||
return
|
||||
if (get_dist(src, friend) <= 1)
|
||||
if (friend.stat >= DEAD || friend.health <= config.health_threshold_softcrit)
|
||||
if (prob((friend.stat < DEAD)? 50 : 15))
|
||||
var/verb = pick("meows", "mews", "mrowls")
|
||||
audible_emote(pick("[verb] in distress.", "[verb] anxiously."))
|
||||
else
|
||||
if (prob(5))
|
||||
visible_emote(pick("nuzzles [friend].",
|
||||
"brushes against [friend].",
|
||||
"rubs against [friend].",
|
||||
"purrs."))
|
||||
else if (friend.health <= 50)
|
||||
if (prob(10))
|
||||
var/verb = pick("meows", "mews", "mrowls")
|
||||
audible_emote("[verb] anxiously.")
|
||||
|
||||
/mob/living/simple_animal/cat/fluff/verb/friend()
|
||||
set name = "Become Friends"
|
||||
set category = "IC"
|
||||
set src in view(1)
|
||||
|
||||
if(friend && usr == friend)
|
||||
set_dir(get_dir(src, friend))
|
||||
say("Meow!")
|
||||
return
|
||||
|
||||
if (!(ishuman(usr) && befriend_job && usr.job == befriend_job))
|
||||
usr << "<span class='notice'>[src] ignores you.</span>"
|
||||
return
|
||||
|
||||
friend = usr
|
||||
|
||||
set_dir(get_dir(src, friend))
|
||||
say("Meow!")
|
||||
|
||||
//RUNTIME IS ALIVE! SQUEEEEEEEE~
|
||||
/mob/living/simple_animal/cat/Runtime
|
||||
/mob/living/simple_animal/cat/fluff/Runtime
|
||||
name = "Runtime"
|
||||
desc = "Her fur has the look and feel of velvet, and her tail quivers occasionally."
|
||||
gender = FEMALE
|
||||
icon_state = "cat"
|
||||
icon_living = "cat"
|
||||
icon_dead = "cat_dead"
|
||||
befriend_job = "Chief Medical Officer"
|
||||
|
||||
/mob/living/simple_animal/cat/kitten
|
||||
name = "kitten"
|
||||
@@ -97,4 +219,8 @@
|
||||
icon_state = "kitten"
|
||||
icon_living = "kitten"
|
||||
icon_dead = "kitten_dead"
|
||||
gender = NEUTER
|
||||
gender = NEUTER
|
||||
|
||||
/mob/living/simple_animal/cat/kitten/New()
|
||||
gender = pick(MALE, FEMALE)
|
||||
..()
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
adjustBruteLoss(-medical_pack.heal_brute)
|
||||
visible_message("<span class='warning'>\The [user] applies the [medical_pack] to \the [src].</span>")
|
||||
else
|
||||
user << "<span class='warning'>\The [src] cannot benefit from medical items in its current state.</span>"
|
||||
user << "<span class='warning'>\The [src] cannot benefit from medical items in \his current state.</span>"
|
||||
return
|
||||
|
||||
else if(istype(O, /obj/item/weapon/kitchenknife) || istype(O, /obj/item/weapon/butch))
|
||||
|
||||
@@ -792,26 +792,25 @@ note dizziness decrements automatically in the mob's Life() proc.
|
||||
/mob/Stat()
|
||||
..()
|
||||
|
||||
if(statpanel("MC")) //not looking at that panel
|
||||
|
||||
if(client && client.holder)
|
||||
if(client && client.holder)
|
||||
if(statpanel("Status"))
|
||||
stat(null,"Location:\t([x], [y], [z])")
|
||||
stat(null,"CPU:\t[world.cpu]")
|
||||
stat(null,"Instances:\t[world.contents.len]")
|
||||
|
||||
if(master_controller)
|
||||
stat(null,"MasterController-[last_tick_duration] ([master_controller.processing?"On":"Off"]-[controller_iteration])")
|
||||
stat(null,"Air-[master_controller.air_cost]\tSun-[master_controller.sun_cost]")
|
||||
stat(null,"Mob-[master_controller.mobs_cost]\t#[mob_list.len]")
|
||||
stat(null,"Dis-[master_controller.diseases_cost]\t#[active_diseases.len]")
|
||||
stat(null,"Mch-[master_controller.machines_cost]\t#[machines.len]")
|
||||
stat(null,"Obj-[master_controller.objects_cost]\t#[processing_objects.len]")
|
||||
stat(null,"Net-[master_controller.networks_cost]\tPnet-[master_controller.powernets_cost]")
|
||||
stat(null,"NanoUI-[master_controller.nano_cost]\t#[nanomanager.processing_uis.len]")
|
||||
stat(null,"Events-[master_controller.events_cost]\t#[event_manager.active_events.len]")
|
||||
stat(null,"Tick-[master_controller.ticker_cost]\tALL-[master_controller.total_cost]")
|
||||
else
|
||||
stat(null,"MasterController-ERROR")
|
||||
if(statpanel("MC") && master_controller)
|
||||
stat(null,"MasterController-[last_tick_duration] ([master_controller.processing?"On":"Off"]-[controller_iteration])")
|
||||
stat(null,"Air-[master_controller.air_cost]\tSun-[master_controller.sun_cost]")
|
||||
stat(null,"Mob-[master_controller.mobs_cost]\t#[mob_list.len]")
|
||||
stat(null,"Dis-[master_controller.diseases_cost]\t#[active_diseases.len]")
|
||||
stat(null,"Mch-[master_controller.machines_cost]\t#[machines.len]")
|
||||
stat(null,"Obj-[master_controller.objects_cost]\t#[processing_objects.len]")
|
||||
stat(null,"Net-[master_controller.networks_cost]\tPnet-[master_controller.powernets_cost]")
|
||||
stat(null,"NanoUI-[master_controller.nano_cost]\t#[nanomanager.processing_uis.len]")
|
||||
stat(null,"Event-[master_controller.events_cost]\t#[event_manager.active_events.len]")
|
||||
alarm_manager.stat_entry()
|
||||
stat(null,"Tick-[master_controller.ticker_cost]\tALL-[master_controller.total_cost]")
|
||||
else
|
||||
stat(null,"MasterController-ERROR")
|
||||
|
||||
if(listed_turf && client)
|
||||
if(!TurfAdjacent(listed_turf))
|
||||
@@ -834,16 +833,6 @@ note dizziness decrements automatically in the mob's Life() proc.
|
||||
statpanel("Spells","[S.charge_counter]/[S.charge_max]",S)
|
||||
if("holdervar")
|
||||
statpanel("Spells","[S.holder_var_type] [S.holder_var_amount]",S)
|
||||
if(statpanel("Examine"))
|
||||
stat(null,"[description_holders["icon"]] <font size='5'>[description_holders["name"]]</font>") //The name, written in big letters.
|
||||
stat(null,"[description_holders["desc"]]") //the default examine text.
|
||||
if(description_holders["info"])
|
||||
stat(null,"<font color='#084B8A'><b>[description_holders["info"]]</b></font>") //Blue, informative text.
|
||||
if(description_holders["fluff"])
|
||||
stat(null,"<font color='#298A08'><b>[description_holders["fluff"]]</b></font>") //Yellow, fluff-related text.
|
||||
if(mind.special_role)
|
||||
if(description_holders["antag"])
|
||||
stat(null,"<font color='#8A0808'><b>[description_holders["antag"]]</b></font>") //Red, malicious antag-related text
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -224,7 +224,4 @@
|
||||
|
||||
var/list/active_genes=list()
|
||||
|
||||
//Examine tab vars
|
||||
//These hold the descriptions and other info, to relay to the actual tab.
|
||||
var/description_holders[0]
|
||||
|
||||
|
||||
@@ -559,3 +559,16 @@ proc/is_blind(A)
|
||||
say_dead_direct("The ghost of <span class='name'>[name]</span> now [pick("skulks","lurks","prowls","creeps","stalks")] among the dead. [message]")
|
||||
else
|
||||
say_dead_direct("<span class='name'>[name]</span> no longer [pick("skulks","lurks","prowls","creeps","stalks")] in the realm of the dead. [message]")
|
||||
|
||||
/mob/proc/switch_to_camera(var/obj/machinery/camera/C)
|
||||
if (!C.can_use() || stat || (get_dist(C, src) > 1 || machine != src || blinded || !canmove))
|
||||
return 0
|
||||
check_eye(src)
|
||||
return 1
|
||||
|
||||
/mob/living/silicon/ai/switch_to_camera(var/obj/machinery/camera/C)
|
||||
if(!C.can_use() || !is_in_chassis())
|
||||
return 0
|
||||
|
||||
eyeobj.setLoc(C)
|
||||
return 1
|
||||
|
||||
83
code/modules/nano/modules/alarm_monitor.dm
Normal file
83
code/modules/nano/modules/alarm_monitor.dm
Normal file
@@ -0,0 +1,83 @@
|
||||
/obj/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.
|
||||
|
||||
/obj/nano_module/alarm_monitor/ai
|
||||
list_cameras = 1
|
||||
|
||||
/obj/nano_module/alarm_monitor/ai/New()
|
||||
..()
|
||||
alarm_handlers = alarm_manager.all_handlers
|
||||
|
||||
/obj/nano_module/alarm_monitor/borg/New()
|
||||
..()
|
||||
alarm_handlers = alarm_manager.all_handlers
|
||||
|
||||
/obj/nano_module/alarm_monitor/engineering/New()
|
||||
..()
|
||||
alarm_handlers = list(atmosphere_alarm, fire_alarm, power_alarm)
|
||||
|
||||
/obj/nano_module/alarm_monitor/security/New()
|
||||
..()
|
||||
alarm_handlers = list(camera_alarm, motion_alarm)
|
||||
|
||||
/obj/nano_module/alarm_monitor/proc/register(var/object, var/procName)
|
||||
for(var/datum/alarm_handler/AH in alarm_handlers)
|
||||
AH.register(object, procName)
|
||||
|
||||
/obj/nano_module/alarm_monitor/proc/unregister(var/object)
|
||||
for(var/datum/alarm_handler/AH in alarm_handlers)
|
||||
AH.unregister(object)
|
||||
|
||||
/obj/nano_module/alarm_monitor/proc/active_alarms()
|
||||
var/list/all_alarms = new()
|
||||
for(var/datum/alarm_handler/AH in alarm_handlers)
|
||||
var/list/alarms = AH.alarms
|
||||
all_alarms += alarms
|
||||
|
||||
return all_alarms
|
||||
|
||||
/obj/nano_module/alarm_monitor/ai/Topic(ref, href_list)
|
||||
if(..())
|
||||
return 1
|
||||
if(href_list["switchTo"])
|
||||
var/obj/machinery/camera/C = locate(href_list["switchTo"]) in cameranet.cameras
|
||||
if(!C)
|
||||
|
||||
return
|
||||
|
||||
usr.switch_to_camera(C)
|
||||
return 1
|
||||
|
||||
/obj/nano_module/alarm_monitor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
|
||||
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(list_cameras)
|
||||
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" = sanitize(english_list(lost_sources, nothing_text = "", and_text = ", "))))
|
||||
data["categories"] = categories
|
||||
|
||||
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if (!ui)
|
||||
ui = new(user, src, ui_key, "alarm_monitor.tmpl", "Alarm Monitoring Console", 800, 800)
|
||||
ui.set_initial_data(data)
|
||||
ui.open()
|
||||
ui.set_auto_update(1)
|
||||
@@ -1,5 +1,5 @@
|
||||
/obj/nano_module/rcon
|
||||
name = "RCON interface"
|
||||
name = "Power RCON"
|
||||
|
||||
var/list/known_SMESs = null
|
||||
var/list/known_breakers = null
|
||||
|
||||
@@ -78,6 +78,10 @@
|
||||
return STATUS_UPDATE // update only (orange visibility)
|
||||
return STATUS_INTERACTIVE
|
||||
|
||||
//Some atoms such as vehicles might have special rules for how mobs inside them interact with NanoUI.
|
||||
/atom/proc/contents_nano_distance(var/src_object, var/mob/living/user)
|
||||
return user.shared_living_nano_distance(src_object)
|
||||
|
||||
/mob/living/proc/shared_living_nano_distance(var/atom/movable/src_object)
|
||||
if(!isturf(src_object.loc))
|
||||
if(src_object.loc == src) // Item in the inventory
|
||||
@@ -100,7 +104,10 @@
|
||||
/mob/living/can_use_topic(var/src_object, var/datum/topic_state/custom_state)
|
||||
. = shared_living_nano_interaction(src_object)
|
||||
if(. == STATUS_INTERACTIVE && !(custom_state.flags & NANO_IGNORE_DISTANCE))
|
||||
. = shared_living_nano_distance(src_object)
|
||||
if(loc)
|
||||
. = loc.contents_nano_distance(src_object, src)
|
||||
else
|
||||
. = shared_living_nano_distance(src_object)
|
||||
if(STATUS_INTERACTIVE)
|
||||
return STATUS_UPDATE
|
||||
|
||||
|
||||
@@ -497,10 +497,11 @@ Note that amputating the affected organ does in fact remove the infection from t
|
||||
H = owner
|
||||
|
||||
for(var/datum/wound/W in wounds)
|
||||
if(W.damage_type == CUT || W.damage_type == BRUISE)
|
||||
brute_dam += W.damage
|
||||
else if(W.damage_type == BURN)
|
||||
burn_dam += W.damage
|
||||
if(!W.internal) //so IB doesn't count towards crit/paincrit
|
||||
if(W.damage_type == CUT || W.damage_type == BRUISE)
|
||||
brute_dam += W.damage
|
||||
else if(W.damage_type == BURN)
|
||||
burn_dam += W.damage
|
||||
|
||||
if(!(status & ORGAN_ROBOT) && W.bleeding() && (H && !(H.species.flags & NO_BLOOD)))
|
||||
W.bleed_timer--
|
||||
|
||||
@@ -183,9 +183,6 @@
|
||||
if(terminal)
|
||||
disconnect_terminal()
|
||||
|
||||
//If there's no more APC then there shouldn't be a cause for alarm I guess
|
||||
area.poweralert(1, src) //so that alarms don't go on forever
|
||||
|
||||
..()
|
||||
|
||||
/obj/machinery/power/apc/proc/make_terminal()
|
||||
@@ -725,7 +722,7 @@
|
||||
src.interact(user)
|
||||
|
||||
/obj/machinery/power/apc/attack_ghost(user as mob)
|
||||
if(stat & (BROKEN|MAINT))
|
||||
if(stat & (BROKEN|MAINT))
|
||||
return
|
||||
return ui_interact(user)
|
||||
|
||||
@@ -1164,29 +1161,27 @@
|
||||
lighting = autoset(lighting, 1)
|
||||
environ = autoset(environ, 1)
|
||||
autoflag = 3
|
||||
area.poweralert(1, src)
|
||||
if(cell.charge >= 4000)
|
||||
area.poweralert(1, src)
|
||||
power_alarm.clearAlarm(loc, src)
|
||||
else if((cell.percent() <= 30) && (cell.percent() > 15) && longtermpower < 0) // <30%, turn off equipment
|
||||
if(autoflag != 2)
|
||||
equipment = autoset(equipment, 2)
|
||||
lighting = autoset(lighting, 1)
|
||||
environ = autoset(environ, 1)
|
||||
area.poweralert(0, src)
|
||||
power_alarm.triggerAlarm(loc, src)
|
||||
autoflag = 2
|
||||
else if(cell.percent() <= 15) // <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)
|
||||
area.poweralert(0, src)
|
||||
power_alarm.triggerAlarm(loc, src)
|
||||
autoflag = 1
|
||||
else // zero charge, turn all off
|
||||
if(autoflag != 0)
|
||||
equipment = autoset(equipment, 0)
|
||||
lighting = autoset(lighting, 0)
|
||||
environ = autoset(environ, 0)
|
||||
area.poweralert(0, src)
|
||||
power_alarm.triggerAlarm(loc, src)
|
||||
autoflag = 0
|
||||
|
||||
// now trickle-charge the cell
|
||||
@@ -1233,7 +1228,7 @@
|
||||
equipment = autoset(equipment, 0)
|
||||
lighting = autoset(lighting, 0)
|
||||
environ = autoset(environ, 0)
|
||||
area.poweralert(0, src)
|
||||
power_alarm.triggerAlarm(loc, src)
|
||||
autoflag = 0
|
||||
|
||||
// update icon & area power if anything changed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/area/engineering/poweralert(var/state, var/source)
|
||||
if (state != poweralm)
|
||||
/area/engineering/power_alert(var/alarming)
|
||||
if (alarming)
|
||||
investigate_log("has a power alarm!","singulo")
|
||||
..()
|
||||
@@ -76,7 +76,13 @@
|
||||
/obj/item/weapon/gun/energy/update_icon()
|
||||
if(charge_meter)
|
||||
var/ratio = power_supply.charge / power_supply.maxcharge
|
||||
ratio = round(ratio, 0.25) * 100
|
||||
|
||||
//make sure that rounding down will not give us the empty state even if we have charge for a shot left.
|
||||
if(power_supply.charge < charge_cost)
|
||||
ratio = 0
|
||||
else
|
||||
ratio = max(round(ratio, 0.25) * 100, 25)
|
||||
|
||||
if(modifystate)
|
||||
icon_state = "[modifystate][ratio]"
|
||||
else
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
name = "ion rifle"
|
||||
desc = "A man portable anti-armor weapon designed to disable mechanical threats"
|
||||
icon_state = "ionrifle"
|
||||
item_state = "ionrifle"
|
||||
fire_sound = 'sound/weapons/Laser.ogg'
|
||||
origin_tech = "combat=2;magnets=4"
|
||||
w_class = 4.0
|
||||
w_class = 4
|
||||
force = 10
|
||||
flags = CONDUCT
|
||||
slot_flags = SLOT_BACK
|
||||
@@ -16,6 +17,13 @@
|
||||
return //so it doesn't EMP itself, I guess
|
||||
..()
|
||||
|
||||
/obj/item/weapon/gun/energy/ionrifle/update_icon()
|
||||
..()
|
||||
if(power_supply.charge < charge_cost)
|
||||
item_state = "ionrifle-empty"
|
||||
else
|
||||
item_state = initial(item_state)
|
||||
|
||||
/obj/item/weapon/gun/energy/decloner
|
||||
name = "biological demolecularisor"
|
||||
desc = "A gun that discharges high amounts of controlled radiation to slowly break a target into component elements."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/obj/item/weapon/gun/projectile/heavysniper
|
||||
name = "\improper PTRS-7 rifle"
|
||||
desc = "A portable anti-armour rifle fitted with a scope. Originally designed to used against armoured exosuits, it is capable of punching through non-reinforced walls with ease. Fires armor piercing 14.5mm shells."
|
||||
desc = "A portable anti-armour rifle fitted with a scope. Originally designed to used against armoured exosuits, it is capable of punching through windows and non-reinforced walls with ease. Fires armor piercing 14.5mm shells."
|
||||
icon_state = "heavysniper"
|
||||
item_state = "shotgun"
|
||||
w_class = 4
|
||||
|
||||
@@ -43,10 +43,10 @@
|
||||
var/chance = 0
|
||||
if(istype(A, /turf/simulated/wall))
|
||||
var/turf/simulated/wall/W = A
|
||||
chance = round(damage/W.damage_cap*180)
|
||||
chance = round(damage/W.damage_cap*250)
|
||||
else if(istype(A, /obj/machinery/door))
|
||||
var/obj/machinery/door/D = A
|
||||
chance = round(damage/D.maxhealth*100)
|
||||
chance = round(damage/D.maxhealth*150)
|
||||
else if(istype(A, /obj/structure/girder) || istype(A, /obj/structure/cultgirder))
|
||||
chance = 100
|
||||
else if(istype(A, /obj/machinery) || istype(A, /obj/structure))
|
||||
@@ -141,13 +141,13 @@
|
||||
penetrating = 1
|
||||
|
||||
/obj/item/projectile/bullet/rifle/a145
|
||||
damage = 80
|
||||
damage = 60
|
||||
stun = 3
|
||||
weaken = 3
|
||||
penetrating = 5
|
||||
|
||||
/obj/item/projectile/bullet/rifle/a556
|
||||
damage = 50
|
||||
damage = 40
|
||||
penetrating = 1
|
||||
|
||||
/* Miscellaneous */
|
||||
|
||||
Reference in New Issue
Block a user