Files
Bubberstation/code/modules/power/floodlight.dm
_0Steven 25bec6190b Fixes issues with multitools on power objects (#82389)
## About The Pull Request

So at some point the power object's `multitool_act(...)` proc was set to
_always_ block, for what I could find to be no discernable reason.

### The Main Thing


d38f9385b8/code/modules/power/power.dm (L62-L74)
Now, of course, it shouldn't, because this cuts the entire chain short
and thus blocks any other multitool interactions. Like opening the wires
panel with a multitool, in this case. Even if `can_change_cable_layer`
were to be false and thus the object would never actually care about
having this interaction, it'd _still_ block it.

So we don't do that. But what _do_ we do?
I decided to just split off the actual cable changing part into its own
proc, `cable_layer_act(...)`.
```dm
/obj/machinery/power/proc/cable_layer_act(mob/living/user, obj/item/tool)
	var/choice = tgui_input_list(user, "Select Power Line For Operation", "Select Cable Layer", GLOB.cable_name_to_layer)
	if(isnull(choice))
		return ITEM_INTERACT_BLOCKING

	cable_layer = GLOB.cable_name_to_layer[choice]
	balloon_alert(user, "now operating on the [choice]")
	return ITEM_INTERACT_SUCCESS
```
Which is then called on `multitool_act(...)`, if
`can_change_cable_layer` is true.
```dm
/obj/machinery/power/multitool_act(mob/living/user, obj/item/tool)
	if(can_change_cable_layer)
		return cable_layer_act(user, tool)
```
Which continues with the chain if we can't change layers by default, and
otherwise lets `cable_layer_act(...)` work out whether we should block
or continue.
Notably, we've removed the `cable_layer_change_checks(...)` proc from
the equation, and just let inheritors override it to add their own
preconditions and what flags they should return.
On its own this fixes the APC wire panel interactions, but also lets us
just return `NONE` when we need to.
```dm
/obj/machinery/power/emitter/cable_layer_act(mob/living/user, obj/item/tool)
	if(panel_open)
		return NONE
	if(welded)
		balloon_alert(user, "unweld first!")
		return ITEM_INTERACT_BLOCKING
	return ..()
```

### The OTHER Things

While doing this I noticed there's actually very little sanity checks
after we close our input list.
```dm
var/choice = tgui_input_list(user, "Select Power Line For Operation", "Select Cable Layer", GLOB.cable_name_to_layer)
if(isnull(choice))
	return ITEM_INTERACT_BLOCKING
```
We only care about whether we made a choice!
Testing this, lo and behold, this can cause runtimes if the power object
gets qdeleted before you close the menu.
As a funny side, it _also_ doesn't care about whether you're on the
other side of the station, while your multitool is on a different
z-level, or just doesn't exist anymore.
So we just add a few basic sanity checks while we're at it.
```dm
var/choice = tgui_input_list(user, "Select Power Line For Operation", "Select Cable Layer", GLOB.cable_name_to_layer)
if(isnull(choice) || QDELETED(src) || QDELETED(user) || QDELETED(tool) || !user.Adjacent(src) || !user.is_holding(tool))
	return ITEM_INTERACT_BLOCKING
```
That's all. Having done some basic testing, I believe the behaviour is
otherwise unaffected.
## Why It's Good For The Game

It's annoying to need to swap to an empty hand or wirecutters to
interact with APC, emitter, or tesla coil wires.
This fixes that. (Fixes #81745.)
...and then a few other tidbits I realized existed.
## Changelog
🆑
fix: Fix using a multitool on a power object with wires not actually
opening the wires menu when it should.
fix: Fix a runtime from a power object being deleted before selecting
what cable layer to put it at.
fix: Fix power object cable changing not caring about whether you were
still adjacent, still holding your multitool, or whether it even still
existed after the selection menu was closed.
/🆑
2024-04-02 17:42:00 -06:00

324 lines
9.4 KiB
Plaintext

#define FLOODLIGHT_OFF 1
#define FLOODLIGHT_LOW 2
#define FLOODLIGHT_MED 3
#define FLOODLIGHT_HIGH 4
/obj/structure/floodlight_frame
name = "floodlight frame"
desc = "A metal frame that requires wiring and a light tube to become a flood light."
max_integrity = 100
icon = 'icons/obj/lighting.dmi'
icon_state = "floodlight_c1"
density = TRUE
var/state = FLOODLIGHT_NEEDS_WIRES
/obj/structure/floodlight_frame/Initialize(mapload)
. = ..()
register_context()
/obj/structure/floodlight_frame/add_context(
atom/source,
list/context,
obj/item/held_item,
mob/living/user,
)
if(isnull(held_item))
return NONE
var/message = null
if(state == FLOODLIGHT_NEEDS_WIRES)
if(istype(held_item, /obj/item/stack/cable_coil))
message = "Add cable"
else if(held_item.tool_behaviour == TOOL_WRENCH)
message = "Dismantle frame"
else if(state == FLOODLIGHT_NEEDS_SECURING)
if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
message = "Secure cable"
else if(held_item.tool_behaviour == TOOL_WIRECUTTER)
message = "Cut cable"
else if(state == FLOODLIGHT_NEEDS_LIGHTS)
if(istype(held_item, /obj/item/light/tube))
message = "Add light"
else if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
message = "Unscrew cable"
if(isnull(message))
return NONE
context[SCREENTIP_CONTEXT_LMB] = message
return CONTEXTUAL_SCREENTIP_SET
/obj/structure/floodlight_frame/examine(mob/user)
. = ..()
if(state == FLOODLIGHT_NEEDS_WIRES)
. += span_notice("It can be wired with [EXAMINE_HINT("5 cable pieces")].")
. += span_notice("The frame can be deconstructed by [EXAMINE_HINT("unwrenching")].")
else if(state == FLOODLIGHT_NEEDS_SECURING)
. += span_notice("The cable needs to be [EXAMINE_HINT("screwed")] on to the frame.")
. += span_notice("The hanging cable could be [EXAMINE_HINT("cut")] apart.")
else if(state == FLOODLIGHT_NEEDS_LIGHTS)
. += span_notice("It needs a [EXAMINE_HINT("light tube")] to finish it.")
. += span_notice("The cable could be [EXAMINE_HINT("unscrewed")] from the frame.")
/obj/structure/floodlight_frame/screwdriver_act(mob/living/user, obj/item/O)
. = ..()
if(state == FLOODLIGHT_NEEDS_SECURING)
icon_state = "floodlight_c3"
state = FLOODLIGHT_NEEDS_LIGHTS
return TRUE
else if(state == FLOODLIGHT_NEEDS_LIGHTS)
icon_state = "floodlight_c2"
state = FLOODLIGHT_NEEDS_SECURING
return TRUE
return FALSE
/obj/structure/floodlight_frame/wrench_act(mob/living/user, obj/item/tool)
if(state != FLOODLIGHT_NEEDS_WIRES)
return FALSE
if(!tool.use_tool(src, user, 30, volume=50))
return TRUE
new /obj/item/stack/sheet/iron(loc, 5)
qdel(src)
return TRUE
/obj/structure/floodlight_frame/wirecutter_act(mob/living/user, obj/item/tool)
if(state != FLOODLIGHT_NEEDS_SECURING)
return FALSE
icon_state = "floodlight_c1"
state = FLOODLIGHT_NEEDS_WIRES
new /obj/item/stack/cable_coil(loc, 5)
return TRUE
/obj/structure/floodlight_frame/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/stack/cable_coil) && state == FLOODLIGHT_NEEDS_WIRES)
var/obj/item/stack/S = O
if(S.use(5))
icon_state = "floodlight_c2"
state = FLOODLIGHT_NEEDS_SECURING
return
else
balloon_alert(user, "need 5 cable pieces!")
return
if(istype(O, /obj/item/light/tube))
var/obj/item/light/tube/L = O
if(state == FLOODLIGHT_NEEDS_LIGHTS && L.status != 2) //Ready for a light tube, and not broken.
new /obj/machinery/power/floodlight(loc)
qdel(src)
qdel(O)
return
else //A minute of silence for all the accidentally broken light tubes.
balloon_alert(user, "light tube is broken!")
return
..()
/obj/structure/floodlight_frame/completed
name = "floodlight frame"
desc = "A bare metal frame that looks like a floodlight. Requires a light tube to complete."
icon_state = "floodlight_c3"
state = FLOODLIGHT_NEEDS_LIGHTS
/obj/machinery/power/floodlight
name = "floodlight"
desc = "A pole with powerful mounted lights on it. Due to its high power draw, it must be powered by a direct connection to a wire node."
icon = 'icons/obj/lighting.dmi'
icon_state = "floodlight"
density = TRUE
max_integrity = 100
integrity_failure = 0.8
idle_power_usage = 0
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION
anchored = FALSE
light_power = 1.75
can_change_cable_layer = TRUE
/// List of power usage multipliers
var/list/light_setting_list = list(0, 5, 10, 15)
/// Constant coeff. for power usage
var/light_power_coefficient = 200
/// Intensity of the floodlight.
var/setting = FLOODLIGHT_OFF
/obj/machinery/power/floodlight/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_OBJ_PAINTED, TYPE_PROC_REF(/obj/machinery/power/floodlight, on_color_change)) //update light color when color changes
RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
register_context()
/obj/machinery/power/floodlight/proc/on_color_change(obj/machinery/power/flood_light, mob/user, obj/item/toy/crayon/spraycan/spraycan, is_dark_color)
SIGNAL_HANDLER
if(!spraycan.actually_paints)
return
if(setting > FLOODLIGHT_OFF)
update_light_state()
/obj/machinery/power/floodlight/Destroy()
UnregisterSignal(src, COMSIG_OBJ_PAINTED)
. = ..()
/// change light color during operation
/obj/machinery/power/floodlight/proc/update_light_state()
var/light_color = NONSENSICAL_VALUE
if(!isnull(color))
light_color = color
set_light(light_setting_list[setting], light_power, light_color)
/obj/machinery/power/floodlight/add_context(
atom/source,
list/context,
obj/item/held_item,
mob/living/user,
)
if(isnull(held_item))
if(panel_open)
context[SCREENTIP_CONTEXT_LMB] = "Remove Light"
return CONTEXTUAL_SCREENTIP_SET
return NONE
var/message = null
if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
message = "Open Panel"
else if(held_item.tool_behaviour == TOOL_WRENCH)
message = anchored ? "Unsecure light" : "Secure light"
if(isnull(message))
return NONE
context[SCREENTIP_CONTEXT_LMB] = message
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/power/floodlight/examine(mob/user)
. = ..()
if(!anchored)
. += span_notice("It needs to be wrenched on top of a wire.")
else
. += span_notice("Its at power level [setting].")
if(panel_open)
. += span_notice("Its maintainence hatch is open but can be [EXAMINE_HINT("screwed")] close.")
. += span_notice("You can remove the light tube by [EXAMINE_HINT("hand")].")
else
. += span_notice("Its maintainence hatch can be [EXAMINE_HINT("screwed")] open.")
/obj/machinery/power/floodlight/process()
var/turf/T = get_turf(src)
var/obj/structure/cable/C = locate() in T
if(!C && powernet)
disconnect_from_network()
if(setting > FLOODLIGHT_OFF) //If on
if(avail(active_power_usage))
add_load(active_power_usage)
else
change_setting(FLOODLIGHT_OFF)
else if(avail(idle_power_usage))
add_load(idle_power_usage)
/obj/machinery/power/floodlight/proc/change_setting(newval, mob/user)
if((newval < FLOODLIGHT_OFF) || (newval > light_setting_list.len))
return
setting = newval
active_power_usage = light_setting_list[setting] * light_power_coefficient
if(!avail(active_power_usage) && setting > FLOODLIGHT_OFF)
return change_setting(setting - 1)
update_light_state()
var/setting_text = ""
if(setting > FLOODLIGHT_OFF)
icon_state = "[initial(icon_state)]_on"
else
icon_state = initial(icon_state)
switch(setting)
if(FLOODLIGHT_OFF)
setting_text = "OFF"
if(FLOODLIGHT_LOW)
setting_text = "low power"
if(FLOODLIGHT_MED)
setting_text = "standard lighting"
if(FLOODLIGHT_HIGH)
setting_text = "high power"
if(user)
to_chat(user, span_notice("You set [src] to [setting_text]."))
/obj/machinery/power/floodlight/cable_layer_act(mob/living/user, obj/item/tool)
if(anchored)
balloon_alert(user, "unanchor first!")
return ITEM_INTERACT_BLOCKING
return ..()
/obj/machinery/power/floodlight/wrench_act(mob/living/user, obj/item/tool)
. = ..()
default_unfasten_wrench(user, tool)
change_setting(FLOODLIGHT_OFF)
if(anchored)
connect_to_network()
else
disconnect_from_network()
return ITEM_INTERACT_SUCCESS
/obj/machinery/power/floodlight/screwdriver_act(mob/living/user, obj/item/tool)
. = ..()
change_setting(FLOODLIGHT_OFF)
panel_open = TRUE
balloon_alert(user, "opened panel")
return TRUE
/obj/machinery/power/floodlight/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
if(panel_open)
var/obj/structure/floodlight_frame/floodlight_frame = new(loc)
floodlight_frame.state = FLOODLIGHT_NEEDS_LIGHTS
var/obj/item/light/tube/light_tube = new(loc)
user.put_in_active_hand(light_tube)
qdel(src)
var/current = setting
if(current == FLOODLIGHT_OFF)
current = light_setting_list.len
else
current--
change_setting(current, user)
/obj/machinery/power/floodlight/attack_robot(mob/user)
return attack_hand(user)
/obj/machinery/power/floodlight/attack_ai(mob/user)
return attack_hand(user)
/obj/machinery/power/floodlight/proc/on_saboteur(datum/source, disrupt_duration)
SIGNAL_HANDLER
atom_break(ENERGY) // technically,
return COMSIG_SABOTEUR_SUCCESS
/obj/machinery/power/floodlight/atom_break(damage_flag)
. = ..()
if(!.)
return
playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE)
var/obj/structure/floodlight_frame/floodlight_frame = new(loc)
floodlight_frame.state = FLOODLIGHT_NEEDS_LIGHTS
var/obj/item/light/tube/our_light = new(loc)
our_light.shatter()
qdel(src)
/obj/machinery/power/floodlight/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
playsound(src, 'sound/effects/glasshit.ogg', 75, TRUE)
#undef FLOODLIGHT_OFF
#undef FLOODLIGHT_LOW
#undef FLOODLIGHT_MED
#undef FLOODLIGHT_HIGH