No roundstart playable MULEs / Trampling requires hacking (#76837)

## About The Pull Request

Prevailing feedback has been:
- The player base cannot be trusted to control MULEbots.
- It should be clearer what bots can and can't do.

The former is easy to fix.
The latter is sort of a matter for policy but I'm going to investigate
giving bots a rudimentary laws system. Plus that sounds much more
controversial than this so I am going to atomise this outside of that
PR.

MULEbots can still be set to allow sentience by cargo technicians, but
don't start that way.

ADDITIONALLY this PR just changes it so that MULEbots do not crush
people unless:
- They have been emagged.
- Their safety wire has been cut.
Either means works, so it's not too hard to access for nefarious
purposes, but hard to do to yourself.
Otherwise they just slow down for a few seconds instead.

Also fixed an unrelated name bug while I was there.

Closes #76926

## Why It's Good For The Game

Players would take them, not deliver any cargo, and repeatedly ask
people to lie down in front of them.
Plus Tram has 5 of the things which is frankly too many to be wandering
around the bar.

## Changelog

🆑
balance: You can't possess a MULE as soon as the round starts, someone
will have to give you permission.
balance: MULEbots no longer crush prone characters unless they have been
hacked (or emagged).
fix: Bots can put numbers in their names, what with being robots.
admin: Adds attack logging when certain wires are cut (for instance:
MULEbot safeties)
/🆑
This commit is contained in:
Jacquerel
2023-07-19 09:01:58 +01:00
committed by GitHub
parent 090c2348de
commit 14aef72a4a
22 changed files with 89 additions and 44 deletions

View File

@@ -88,6 +88,8 @@
#define BOT_MODE_REMOTE_ENABLED (1<<2)
///The Bot is allowed to have a ghost placed in control of it.
#define BOT_MODE_CAN_BE_SAPIENT (1<<3)
///The Bot is allowed to be possessed if it is present on mapload.
#define BOT_MODE_ROUNDSTART_POSSESSION (1<<4)
//Bot cover defines indicating the Bot's status
///The Bot's cover is open and can be modified/emagged by anyone.

View File

@@ -933,5 +933,27 @@
/datum/status_effect/teleport_madness/tick()
dump_in_space(owner)
/datum/status_effect/careful_driving
id = "careful_driving"
alert_type = /atom/movable/screen/alert/status_effect/careful_driving
duration = 5 SECONDS
status_type = STATUS_EFFECT_REPLACE
/datum/status_effect/careful_driving/on_apply()
. = ..()
owner.add_movespeed_modifier(/datum/movespeed_modifier/careful_driving, update = TRUE)
/datum/status_effect/careful_driving/on_remove()
. = ..()
owner.remove_movespeed_modifier(/datum/movespeed_modifier/careful_driving, update = TRUE)
/atom/movable/screen/alert/status_effect/careful_driving
name = "Careful Driving"
desc = "That was close! You almost ran that one over!"
icon_state = "paralysis"
/datum/movespeed_modifier/careful_driving
multiplicative_slowdown = 3
#undef HEALING_SLEEP_DEFAULT
#undef HEALING_SLEEP_ORGAN_MULTIPLIER

View File

@@ -153,25 +153,25 @@
/datum/wires/proc/is_dud_color(color)
return is_dud(get_wire(color))
/datum/wires/proc/cut(wire)
/datum/wires/proc/cut(wire, source)
if(is_cut(wire))
cut_wires -= wire
SEND_SIGNAL(src, COMSIG_MEND_WIRE(wire), wire)
on_cut(wire, mend = TRUE)
on_cut(wire, mend = TRUE, source = source)
else
cut_wires += wire
SEND_SIGNAL(src, COMSIG_CUT_WIRE(wire), wire)
on_cut(wire, mend = FALSE)
on_cut(wire, mend = FALSE, source = source)
/datum/wires/proc/cut_color(color)
cut(get_wire(color))
/datum/wires/proc/cut_color(color, source)
cut(get_wire(color), source)
/datum/wires/proc/cut_random()
cut(wires[rand(1, wires.len)])
/datum/wires/proc/cut_random(source)
cut(wires[rand(1, wires.len)], source)
/datum/wires/proc/cut_all()
/datum/wires/proc/cut_all(source)
for(var/wire in wires)
cut(wire)
cut(wire, source)
/datum/wires/proc/pulse(wire, user, force=FALSE)
if(!force && is_cut(wire))
@@ -232,7 +232,7 @@
/datum/wires/proc/get_status()
return list()
/datum/wires/proc/on_cut(wire, mend = FALSE)
/datum/wires/proc/on_cut(wire, mend = FALSE, source = null)
return
/datum/wires/proc/on_pulse(wire, user)
@@ -329,7 +329,7 @@
if(I || isAdminGhostAI(usr))
if(I && holder)
I.play_tool_sound(holder, 20)
cut_color(target_wire)
cut_color(target_wire, source = L)
. = TRUE
else
to_chat(L, span_warning("You need wirecutters!"))

View File

@@ -52,7 +52,7 @@
A.danger_level = AIR_ALARM_ALERT_NONE
A.update_appearance()
/datum/wires/airalarm/on_cut(wire, mend)
/datum/wires/airalarm/on_cut(wire, mend, source)
var/obj/machinery/airalarm/A = holder
switch(wire)
if(WIRE_POWER) // Short out forever.

View File

@@ -157,7 +157,7 @@
else if(aiControlDisabled == AI_WIRE_HACKED)
aiControlDisabled = AI_WIRE_DISABLED_HACKED
/datum/wires/airlock/on_cut(wire, mend)
/datum/wires/airlock/on_cut(wire, mend, source)
var/obj/machinery/door/airlock/A = holder
switch(wire)
if(WIRE_POWER1, WIRE_POWER2) // Cut to lose power, repair all to gain power.
@@ -189,6 +189,8 @@
else if(A.aiControlDisabled == AI_WIRE_DISABLED_HACKED)
A.aiControlDisabled = AI_WIRE_HACKED
if(WIRE_SHOCK) // Cut to shock the door, mend to unshock.
if (!isnull(source))
log_combat(source, A, "[mend ? "disabled" : "enabled"] shocking for")
if(mend)
if(A.secondsElectrified)
A.set_electrified(MACHINE_NOT_ELECTRIFIED, usr)
@@ -198,6 +200,8 @@
A.shock(usr, 100)
if(WIRE_SAFETY) // Cut to disable safeties, mend to re-enable.
A.safe = mend
if (!isnull(source))
log_combat(source, A, "[mend ? "enabled" : "disabled"] door safeties for")
if(WIRE_TIMING) // Cut to disable auto-close, mend to re-enable.
A.autoclose = mend
if(A.autoclose && !A.density)

View File

@@ -40,7 +40,7 @@
A.aidisabled = TRUE
addtimer(CALLBACK(A, TYPE_PROC_REF(/obj/machinery/power/apc, reset), wire), 1 SECONDS)
/datum/wires/apc/on_cut(wire, mend)
/datum/wires/apc/on_cut(wire, mend, source)
var/obj/machinery/power/apc/A = holder
switch(wire)
if(WIRE_POWER1, WIRE_POWER2) // Short out.

View File

@@ -37,7 +37,7 @@
A.disabled = !A.disabled
addtimer(CALLBACK(A, TYPE_PROC_REF(/obj/machinery/autolathe, reset), wire), 60)
/datum/wires/autolathe/on_cut(wire, mend)
/datum/wires/autolathe/on_cut(wire, mend, source)
var/obj/machinery/autolathe/A = holder
switch(wire)
if(WIRE_HACK)

View File

@@ -12,6 +12,6 @@
our_sniffer.activate()
..()
/datum/wires/ecto_sniffer/on_cut(wire, mend)
/datum/wires/ecto_sniffer/on_cut(wire, mend, source)
var/obj/machinery/ecto_sniffer/our_sniffer = holder
our_sniffer.sensor_enabled = mend

View File

@@ -10,7 +10,9 @@
/datum/wires/explosive/on_pulse(index)
explode()
/datum/wires/explosive/on_cut(index, mend)
/datum/wires/explosive/on_cut(index, mend, source)
if (!isnull(source))
log_combat(source, holder, "cut the detonation wire for")
explode()
/datum/wires/explosive/proc/explode()
@@ -34,7 +36,7 @@
return
. = ..()
/datum/wires/explosive/chem_grenade/on_cut(index, mend)
/datum/wires/explosive/chem_grenade/on_cut(index, mend, source)
var/obj/item/grenade/chem_grenade/grenade = holder
if(grenade.stage != GRENADE_READY)
return
@@ -127,7 +129,7 @@
else // Boom
explode()
/datum/wires/explosive/pizza/on_cut(wire, mend)
/datum/wires/explosive/pizza/on_cut(wire, mend, source)
var/obj/item/pizzabox/P = holder
switch(wire)
if(WIRE_DISARM) // Disarm and untrap the box.
@@ -135,6 +137,8 @@
P.bomb_defused = TRUE
else
if(!mend && !P.bomb_defused)
if (!isnull(source))
log_combat(source, holder, "cut the detonation wire for")
explode()
/datum/wires/explosive/pizza/explode()

View File

@@ -38,7 +38,7 @@
if(WIRE_LOADCHECK)
machine.allow_exotic_faxes = !machine.allow_exotic_faxes
/datum/wires/fax/on_cut(wire, mend)
/datum/wires/fax/on_cut(wire, mend, source)
var/obj/machinery/fax/machine = holder
switch(wire)
if(WIRE_SHOCK)

View File

@@ -22,7 +22,7 @@
if(WIRE_ACTIVATE)
M.cook()
/datum/wires/microwave/on_cut(wire, mend)
/datum/wires/microwave/on_cut(wire, mend, source)
var/obj/machinery/microwave/M = holder
switch(wire)
if(WIRE_ACTIVATE)

View File

@@ -34,7 +34,7 @@
if(WIRE_INTERFACE)
mod.interface_break = !mod.interface_break
/datum/wires/mod/on_cut(wire, mend)
/datum/wires/mod/on_cut(wire, mend, source)
var/obj/item/mod/control/mod = holder
switch(wire)
if(WIRE_HACK)

View File

@@ -23,7 +23,7 @@
if(mule.bot_cover_flags & BOT_COVER_OPEN)
return TRUE
/datum/wires/mulebot/on_cut(wire, mend)
/datum/wires/mulebot/on_cut(wire, mend, source)
var/mob/living/simple_animal/bot/mulebot/mule = holder
switch(wire)
if(WIRE_MOTOR1, WIRE_MOTOR2)
@@ -38,6 +38,9 @@
mule.set_varspeed(AVERAGE_MOTOR_SPEED)
else
mule.set_varspeed(SLOW_MOTOR_SPEED)
if(WIRE_AVOIDANCE)
if (!isnull(source))
log_combat(source, mule, "[is_cut(WIRE_AVOIDANCE) ? "cut" : "mended"] the MULE safety wire of")
/datum/wires/mulebot/on_pulse(wire)
var/mob/living/simple_animal/bot/mulebot/mule = holder

View File

@@ -33,7 +33,7 @@
R.hacked = !R.hacked
if(WIRE_DISABLE)
R.disabled = !R.disabled
/datum/wires/rnd/on_cut(wire, mend)
/datum/wires/rnd/on_cut(wire, mend, source)
var/obj/machinery/rnd/R = holder
switch(wire)
if(WIRE_HACK)

View File

@@ -68,7 +68,7 @@
if(R.has_model())
R.visible_message(span_notice("[R]'s model servos twitch."), span_notice("Your model display flickers."))
/datum/wires/robot/on_cut(wire, mend)
/datum/wires/robot/on_cut(wire, mend, source)
var/mob/living/silicon/robot/R = holder
switch(wire)
if(WIRE_AI) // Cut the AI wire to reset AI control.

View File

@@ -51,7 +51,7 @@
R.audible_message(span_warning("Unauthorized prize vend detected! Locking down machine!"))
R.prize_theft(0.20)
/datum/wires/roulette/on_cut(wire, mend)
/datum/wires/roulette/on_cut(wire, mend, source)
var/obj/machinery/roulette/R = holder
switch(wire)
if(WIRE_SHOCK)

View File

@@ -35,7 +35,7 @@
if(usr)
SSU.shock(usr)
/datum/wires/suit_storage_unit/on_cut(wire, mend)
/datum/wires/suit_storage_unit/on_cut(wire, mend, source)
var/obj/machinery/suit_storage_unit/SSU = holder
switch(wire)
if(WIRE_HACK)

View File

@@ -69,7 +69,7 @@
B.detonation_timer += 100
B.delayedlittle = TRUE
/datum/wires/syndicatebomb/on_cut(wire, mend)
/datum/wires/syndicatebomb/on_cut(wire, mend, source)
var/obj/machinery/syndicatebomb/B = holder
switch(wire)
if(WIRE_BOOM,WIRE_BOOM2)
@@ -78,9 +78,9 @@
B.explode_now = TRUE
if(!istype(B.payload, /obj/machinery/syndicatebomb/training))
tell_admins(B)
// Cursed usr use but no easy way to get the cutter
if(isliving(usr))
add_memory_in_range(B, 7, /datum/memory/bomb_defuse_failure, protagonist = usr, antagonist = B)
if(isliving(source))
log_combat(source, holder, "cut the detonation wire for")
add_memory_in_range(B, 7, /datum/memory/bomb_defuse_failure, protagonist = source, antagonist = B)
if(WIRE_UNBOLT)
if(!mend && B.anchored)

View File

@@ -66,7 +66,7 @@
if(WIRE_AGELIMIT)
vending_machine.age_restrictions = !vending_machine.age_restrictions
/datum/wires/vending/on_cut(wire, mend)
/datum/wires/vending/on_cut(wire, mend, source)
var/obj/machinery/vending/vending_machine = holder
switch(wire)
if(WIRE_THROW)

View File

@@ -47,8 +47,8 @@
///All initial access this bot started with.
var/list/prev_access = list()
///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT
var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT
///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION
var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION
///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_COVER_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED
var/bot_cover_flags = BOT_COVER_LOCKED
@@ -192,7 +192,7 @@
if(HAS_TRAIT(SSstation, STATION_TRAIT_BOTS_GLITCHED))
randomize_language_if_on_station()
if(mapload && is_station_level(z) && (bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT))
if(mapload && is_station_level(z) && bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT && bot_mode_flags & BOT_MODE_ROUNDSTART_POSSESSION)
enable_possession(mapload = mapload)
/mob/living/simple_animal/bot/Destroy()
@@ -252,13 +252,16 @@
/// Allows renaming the bot to something else
/mob/living/simple_animal/bot/proc/rename(mob/user)
var/new_name = sanitize_name(reject_bad_text(tgui_input_text(
user = user,
message = "This machine is designated [real_name]. Would you like to update its registration?",
title = "Name change",
default = real_name,
max_length = MAX_NAME_LEN,
)))
var/new_name = sanitize_name(
reject_bad_text(tgui_input_text(
user = user,
message = "This machine is designated [real_name]. Would you like to update its registration?",
title = "Name change",
default = real_name,
max_length = MAX_NAME_LEN,
)),
allow_numbers = TRUE
)
if (isnull(new_name) || QDELETED(src))
return
if (key && user != src)

View File

@@ -24,6 +24,7 @@
buckle_lying = 0
mob_size = MOB_SIZE_LARGE
buckle_prevents_pull = TRUE // No pulling loaded shit
bot_mode_flags = ~BOT_MODE_ROUNDSTART_POSSESSION
maints_access_required = list(ACCESS_ROBOTICS, ACCESS_CARGO)
radio_key = /obj/item/encryptionkey/headset_cargo
@@ -232,7 +233,7 @@
unload(0)
if(prob(25))
visible_message(span_danger("Something shorts out inside [src]!"))
wires.cut_random()
wires.cut_random(source = Proj.firer)
/mob/living/simple_animal/bot/mulebot/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -671,6 +672,12 @@
// when mulebot is in the same loc
/mob/living/simple_animal/bot/mulebot/proc/run_over(mob/living/carbon/human/crushed)
if (!(bot_cover_flags & BOT_COVER_EMAGGED) && !wires.is_cut(WIRE_AVOIDANCE))
if (!has_status_effect(/datum/status_effect/careful_driving))
crushed.visible_message(span_notice("[src] slows down to avoid crushing [crushed]."))
apply_status_effect(/datum/status_effect/careful_driving)
return // Player mules must be emagged before they can trample
log_combat(src, crushed, "run over", addition = "(DAMTYPE: [uppertext(BRUTE)])")
crushed.visible_message(
span_danger("[src] drives over [crushed]!"),

View File

@@ -2,7 +2,7 @@
holder_type = /obj/machinery/door/airlock/shell
proper_name = "Circuit Airlock"
/datum/wires/airlock/shell/on_cut(wire, mend)
/datum/wires/airlock/shell/on_cut(wire, mend, source)
// Don't allow them to re-enable autoclose.
if(wire == WIRE_TIMING)
return