Files
san7890andGitHub cb535cdfa6 Refactor var/can_be_held to an element (#96917)
## About The Pull Request

We were doing a lot of mental gymnastics in a bunch of other places, so
let's change this wonky var to a streamlined element that will rely on
the same signals that a lot of stuff was already using/accounting for in
its own signal handling pathways, instead of being a weird coverage gap.
This patch should also make the whole "checking if someone is attempting
to pick a mob up" thing make a lot more sense and use a unified proc
instead of spot-checking whatever random things it wants to spot-check.
## Why It's Good For The Game

I didn't know this was a thing until I looked at #96873 and it made me
sad because literally everything else involving mob drag-and-drop is
already signal-based except this weird stinker that relied on proc
overrides. Never mind that now, let's use nice traits to avoid
typecasting and elements to avoid duplicating code. It should also be
much cleaner to add holdability to a mob isntead of having to do
`can_be_held = FALSE` as a weird behavior (at least one instance had
this non-necessarily). All this really is is just middleware on the
extant /mob/living code but still making it in proper lockstep with the
other signalling procs.

I did port over raptor code faithfully but I'm not 100% sure if it was
meant to be like this? Regardless, that's how it is.
## Changelog
🆑
refactor: Picking up mobs has been altered a bit, please report any bugs
or glitches.
/🆑
2026-07-13 16:57:47 +02:00

303 lines
11 KiB
Plaintext

/**
* Maintenance Drone
*
* Small player controlled fixer-upper
*
* The maintenace drone is a ghost role with the objective to repair and
* maintain the station.
*
* Featuring two dexterous hands, and a built in toolbox stocked with
* tools.
*
* They have laws to prevent them from doing anything else.
*
*/
/mob/living/basic/drone
name = "Drone"
desc = "A maintenance drone, an expendable robot built to perform station repairs."
icon = 'icons/mob/silicon/drone.dmi'
icon_state = "drone_maint_grey"
icon_living = "drone_maint_grey"
icon_dead = "drone_maint_dead"
health = 45
maxHealth = 45
unsuitable_atmos_damage = 0
unsuitable_cold_damage = 0
unsuitable_heat_damage = 0
speed = 0
density = FALSE
pass_flags = PASSTABLE | PASSMOB
sight = SEE_TURFS | SEE_OBJS
status_flags = (CANPUSH | CANSTUN | CANKNOCKDOWN)
gender = NEUTER
mob_biotypes = MOB_ROBOTIC
speak_emote = list("chirps")
speech_span = SPAN_ROBOT
bubble_icon = "machine"
initial_language_holder = /datum/language_holder/drone
mob_size = MOB_SIZE_SMALL
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, STAMINA = 0, OXY = 0)
hud_possible = list(DIAG_STAT_HUD, DIAG_HUD, ANTAG_HUD)
unique_name = TRUE
faction = list(FACTION_NEUTRAL,FACTION_SILICON,FACTION_TURRET)
hud_type = /datum/hud/dextrous/drone
// Going for a sort of pale green here
lighting_cutoff_red = 30
lighting_cutoff_green = 35
lighting_cutoff_blue = 25
worn_slot_flags = ITEM_SLOT_HEAD
inhand_holder_type = /obj/item/mob_holder/drone
/// `TRUE` if we have picked our visual appearance, `FALSE` otherwise (default)
var/picked = FALSE
/// Stored drone color, restored when unhacked
var/colour = "grey"
var/list/drone_overlays[DRONE_TOTAL_LAYERS]
/// Drone laws announced on spawn
var/laws = \
"1. You may not involve yourself in the matters of another being, even if such matters conflict with Law Two or Law Three, unless the other being is another Drone.\n"+\
"2. You may not harm any being, regardless of intent or circumstance.\n"+\
"3. Your goals are to actively build, maintain, repair, improve, and provide power to the best of your abilities within the facility that housed your activation." //for derelict drones so they don't go to station.
/// Amount of damage sustained if hit by a heavy EMP pulse
var/heavy_emp_damage = 25
///Alarm listener datum, handes caring about alarm events and such
var/datum/alarm_listener/listener
/// Internal storage slot. Fits any item
var/obj/item/internal_storage
/// Headwear slot
var/obj/item/head
/// Default [/mob/living/basic/drone/var/internal_storage] item
var/obj/item/default_storage = /obj/item/storage/drone_tools
/// Default [/mob/living/basic/drone/var/head] item
var/obj/item/default_headwear
///The camera built into the drone which allows it to be seen through cameras.
var/obj/machinery/camera/silicon/built_in_camera
/**
* icon_state of drone from icons/mobs/drone.dmi
*
* Possible states are:
*
* - [MAINTDRONE]
* - [REPAIRDRONE]
* - [SCOUTDRONE]
* - [CLOCKDRONE]
*/
var/visualAppearance = MAINTDRONE
/// Hacked state, see [/mob/living/basic/drone/proc/update_drone_hack]
var/hacked = FALSE
/// Whether this drone can be un-hacked. Used for subtypes that cannot be meaningfully "fixed".
var/can_unhack = TRUE
/// If we have laws to minimize bothering others. Enables or disables drone laws enforcement components (use [/mob/living/basic/drone/proc/set_shy] to set)
var/shy = TRUE
/// Flavor text announced to drones on [/mob/proc/Login]
var/flavortext = \
"\n<big><span class='warning'>DO NOT INTERFERE WITH THE ROUND AS A DRONE OR YOU WILL BE DRONE BANNED</span></big>\n"+\
"<span class='notice'>Drones are a ghost role that are allowed to fix the station and build things. Interfering with the round as a drone is against the rules.</span>\n"+\
"<span class='notice'>Actions that constitute interference include, but are not limited to:</span>\n"+\
"<span class='notice'> - Interacting with round critical objects (IDs, weapons, contraband, powersinks, bombs, etc.)</span>\n"+\
"<span class='notice'> - Interacting with living beings (communication, attacking, healing, etc.)</span>\n"+\
"<span class='notice'> - Interacting with non-living beings (dragging bodies, looting bodies, etc.)</span>\n"+\
"<span class='warning'>These rules are at admin discretion and will be heavily enforced.</span>\n"+\
"<span class='warning'><u>If you do not have the regular drone laws, follow your laws to the best of your ability.</u></span>\n"+\
"<span class='notice'>Prefix your message with :b to speak in Drone Chat.</span>\n"
/mob/living/basic/drone/Initialize(mapload)
. = ..()
GLOB.drones_list += src
AddElement(/datum/element/dextrous, hud_type = hud_type)
AddComponent(/datum/component/basic_inhands, y_offset = getItemPixelShiftY())
AddComponent(/datum/component/simple_access, SSid_access.get_region_access_list(list(REGION_ALL_GLOBAL)))
AddComponent(/datum/component/personal_crafting) // Kind of hard to be a drone and not be able to make tiles
LoadComponent(/datum/component/bloodysoles/bot)
// Only station drones get a camera.
if(is_station_level(src.loc.z))
built_in_camera = new(src)
built_in_camera.c_tag = real_name
built_in_camera.network = list(CAMERANET_NETWORK_SS13)
if(default_storage)
var/obj/item/storage = new default_storage(src)
equip_to_slot_or_del(storage, ITEM_SLOT_DEX_STORAGE)
for(var/holiday_name in GLOB.holidays)
var/datum/holiday/holiday_today = GLOB.holidays[holiday_name]
var/obj/item/potential_hat = holiday_today.holiday_hat
if(!isnull(potential_hat) && isnull(default_headwear)) //If our drone type doesn't start with a hat, we take the holiday one.
default_headwear = potential_hat
if(default_headwear)
var/obj/item/new_hat = new default_headwear(src)
equip_to_slot_or_del(new_hat, ITEM_SLOT_HEAD)
shy_update()
alert_drones(DRONE_NET_CONNECT)
var/datum/atom_hud/data/diagnostic/diag_hud = GLOB.huds[DATA_HUD_DIAGNOSTIC]
diag_hud.add_atom_to_hud(src)
add_traits(list(
TRAIT_VENTCRAWLER_ALWAYS,
TRAIT_NEGATES_GRAVITY,
TRAIT_LITERATE,
TRAIT_KNOW_ENGI_WIRES,
TRAIT_ADVANCEDTOOLUSER,
TRAIT_SILICON_ACCESS,
TRAIT_REAGENT_SCANNER,
TRAIT_UNOBSERVANT,
TRAIT_SILICON_EMOTES_ALLOWED,
), INNATE_TRAIT)
listener = new(list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER), list(z))
RegisterSignal(listener, COMSIG_ALARM_LISTENER_TRIGGERED, PROC_REF(alarm_triggered))
RegisterSignal(listener, COMSIG_ALARM_LISTENER_CLEARED, PROC_REF(alarm_cleared))
listener.RegisterSignal(src, COMSIG_LIVING_DEATH, TYPE_PROC_REF(/datum/alarm_listener, prevent_alarm_changes))
listener.RegisterSignal(src, COMSIG_LIVING_REVIVE, TYPE_PROC_REF(/datum/alarm_listener, allow_alarm_changes))
AddElement(/datum/element/can_be_held)
/mob/living/basic/drone/med_hud_set_health()
set_hud_image_state(DIAG_HUD, "huddiag[RoundDiagBar(health/maxHealth)]")
/mob/living/basic/drone/med_hud_set_status()
if(stat == DEAD)
set_hud_image_state(DIAG_STAT_HUD, "huddead2")
return
if(incapacitated)
set_hud_image_state(DIAG_STAT_HUD, "hudoffline")
return
set_hud_image_state(DIAG_STAT_HUD, "hudstat")
/mob/living/basic/drone/Destroy()
GLOB.drones_list -= src
QDEL_NULL(listener)
QDEL_NULL(built_in_camera)
return ..()
/mob/living/basic/drone/Login()
. = ..()
if(!. || !client)
return FALSE
check_laws()
if(flavortext)
to_chat(src, "[flavortext]")
if(!picked)
pickVisualAppearance()
/mob/living/basic/drone/auto_deadmin_on_login()
if(!client?.holder)
return TRUE
if(CONFIG_GET(flag/auto_deadmin_silicons) || (client.prefs?.toggles & DEADMIN_POSITION_SILICON))
return client.holder.auto_deadmin()
return ..()
/mob/living/basic/drone/death(gibbed)
..(gibbed)
if(internal_storage)
dropItemToGround(internal_storage)
if(head)
dropItemToGround(head)
alert_drones(DRONE_NET_DISCONNECT)
/mob/living/basic/drone/gib()
dust()
/mob/living/basic/drone/get_butt_sprite()
return icon('icons/mob/butts.dmi', BUTT_SPRITE_DRONE)
/mob/living/basic/drone/examine(mob/user)
. = list()
//Hands
for(var/obj/item/held_thing in held_items)
if((held_thing.item_flags & (ABSTRACT|HAND_ITEM)) || HAS_TRAIT(held_thing, TRAIT_EXAMINE_SKIP))
continue
. += "It has [held_thing.examine_title(user)] in its [get_held_index_name(get_held_index_of_item(held_thing))]."
//Internal storage
if(internal_storage && !(internal_storage.item_flags & ABSTRACT))
. += "It is holding [internal_storage.examine_title(user)] in its internal storage."
//Cosmetic hat - provides no function other than looks
if(head && !(head.item_flags & ABSTRACT))
. += "It is wearing [head.examine_title(user)] on its head."
//Braindead
if(!client && stat != DEAD)
. += "Its status LED is blinking at a steady rate."
//Hacked
if(hacked)
. += span_warning("Its display is glowing red!")
//Damaged
if(health != maxHealth)
if(health > maxHealth * 0.33) //Between maxHealth and about a third of maxHealth, between 30 and 10 for normal drones
. += span_warning("Its screws are slightly loose.")
else //otherwise, below about 33%
. += span_boldwarning("Its screws are very loose!")
//Dead
if(stat == DEAD)
if(client)
. += span_deadsay("A message repeatedly flashes on its display: \"REBOOT -- REQUIRED\".")
else
. += span_deadsay("A message repeatedly flashes on its display: \"ERROR -- OFFLINE\".")
/mob/living/basic/drone/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //Secbots won't hunt maintenance drones.
return -10
/mob/living/basic/drone/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
Stun(70)
to_chat(src, span_danger("<b>ER@%R: MME^RY CO#RU9T!</b> R&$b@0tin)..."))
if(severity == 1)
adjust_brute_loss(heavy_emp_damage)
to_chat(src, span_userdanger("HeAV% DA%^MMA+G TO I/O CIR!%UUT!"))
/mob/living/basic/drone/proc/alarm_triggered(datum/source, alarm_type, area/source_area)
SIGNAL_HANDLER
to_chat(src, "--- [alarm_type] alarm detected in [source_area.name]!")
/mob/living/basic/drone/proc/alarm_cleared(datum/source, alarm_type, area/source_area)
SIGNAL_HANDLER
to_chat(src, "--- [alarm_type] alarm in [source_area.name] has been cleared.")
/mob/living/basic/drone/proc/set_shy(new_shy)
shy = new_shy
shy_update()
/mob/living/basic/drone/proc/shy_update()
if(shy)
REMOVE_TRAIT(src, TRAIT_CAN_STRIP, DRONE_SHY_TRAIT) // To shy to touch someone elses hat
ADD_TRAIT(src, TRAIT_PACIFISM, DRONE_SHY_TRAIT)
else
ADD_TRAIT(src, TRAIT_CAN_STRIP, DRONE_SHY_TRAIT) // ...I wonder if I can ware pants like a hat
REMOVE_TRAIT(src, TRAIT_PACIFISM, DRONE_SHY_TRAIT)
/mob/living/basic/drone/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25)
if(affect_silicon)
return ..()
/mob/living/basic/drone/bee_friendly()
// Why would bees pay attention to drones?
return TRUE
/mob/living/basic/drone/electrocute_act(shock_damage, source, siemens_coeff, flags = NONE)
return FALSE //So they don't die trying to fix wiring
/mob/living/basic/drone/can_track(mob/living/user)
if(built_in_camera?.can_use())
return TRUE
return ..()
/mob/living/basic/drone/hypnosis_vulnerable()
return FALSE //It obeys its laws