Files
Bubberstation/code/modules/mob/living/basic/drone/_drone.dm
tattle 66300fbe8f Removes the shy component(s), mostly frees drones (#92027)
## About The Pull Request
Removes the shy component, which is exclusively used by drones. 

![image](https://github.com/user-attachments/assets/e515c357-2046-47d6-9803-e1f5a66c8fda)
Inspired by trying to use soap to clean a stain in atmos and getting
told by the game to kick rocks.

## Why It's Good For The Game
We have had the drone restrictions for 4 years, and every time I choose
to play drone I am forced to engage with the CBT of `this might break
your laws!` `if you face a direction while a mime is in the next room
you might break your laws!!`.

Drones have [individual
names](https://github.com/tgstation/tgstation/pull/78573), our logging
improves regularly, and station drones do not spawn without player
intervention.

This ALSO allows drones to use machinery like lathes, but retains drone
pacifism and dislike of interacting with mobs.

## Changelog

🆑 tattle
del: Removed the shy component, allowing drones to once again use a wide
array of items and perform actions near mobs
/🆑

---------

Co-authored-by: tattle <article.disaster@gmail.com>
2025-07-14 11:05:41 +12:00

298 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
can_be_held = TRUE
worn_slot_flags = ITEM_SLOT_HEAD
/// `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)
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
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))
/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)
adjustBruteLoss(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 ..()