Files
Bubberstation/code/game/machinery/pipe/construction.dm
SkyratBot 320cb90555 [MIRROR] Thermomachines no longer self-destruct when built on blocked ports [MDB IGNORE] (#19664)
* Thermomachines no longer self-destruct when built on blocked ports (#73580)

## About The Pull Request

Ever felt the utter pain when you make a new thermomachine, hook it up
perfectly and then screwdriver it only for it to immediately become a
pile of iron and components without warning? This PR fixes that.

Instead of doing what it did previously, it unanchors itself and opens
its panel.

Right, almost forgot to mention, failing to wrench down a thermomachine
no longer bonks it with the wrench.

Due to not being allowed to use visual messages for when the port is
blocked, I've added a mob/user variable to all on_construction() procs.
(This allowed me to use balloon messages instead.)
## Why It's Good For The Game

Saves a lot of unnecessary headaches when working with thermomachines.
## Changelog
🆑
fix: Atmosians have finally convinced the thermomachines to not
self-destruct when built on blocked ports.
fix: Failing to wrench down a thermomachine no longer hits it with the
wrench.
/🆑

* Thermomachines no longer self-destruct when built on blocked ports

---------

Co-authored-by: RikuTheKiller <88713943+RikuTheKiller@users.noreply.github.com>
2023-03-04 19:53:44 -08:00

423 lines
15 KiB
Plaintext

/*CONTENTS
Buildable pipes
Buildable meters
*/
//construction defines are in __defines/pipe_construction.dm
//update those defines ANY TIME an atmos path is changed...
//...otherwise construction will stop working
/obj/item/pipe
name = "pipe"
desc = "A pipe."
var/pipe_type
var/pipename
force = 7
throwforce = 7
icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi'
icon_state = "simple"
icon_state_preview = "manifold4w"
inhand_icon_state = "buildpipe"
w_class = WEIGHT_CLASS_NORMAL
///Piping layer that we are going to be on
var/piping_layer = PIPING_LAYER_DEFAULT
///Type of pipe-object made, selected from the RPD
var/RPD_type
///Whether it can be painted
var/paintable = FALSE
///Color of the pipe is going to be made from this pipe-object
var/pipe_color
///Initial direction of the created pipe (either made from the RPD or after unwrenching the pipe)
var/p_init_dir = SOUTH
/obj/item/pipe/directional
RPD_type = PIPE_UNARY
/obj/item/pipe/directional/he_junction
icon_state_preview = "junction"
pipe_type = /obj/machinery/atmospherics/pipe/heat_exchanging/junction
/obj/item/pipe/directional/vent
icon_state_preview = "uvent"
pipe_type = /obj/machinery/atmospherics/components/unary/vent_pump
/obj/item/pipe/directional/scrubber
icon_state_preview = "scrubber"
pipe_type = /obj/machinery/atmospherics/components/unary/vent_scrubber
/obj/item/pipe/directional/connector
icon_state_preview = "connector"
pipe_type = /obj/machinery/atmospherics/components/unary/portables_connector
/obj/item/pipe/directional/passive_vent
icon_state_preview = "pvent"
pipe_type = /obj/machinery/atmospherics/components/unary/passive_vent
/obj/item/pipe/directional/injector
icon_state_preview = "injector"
pipe_type = /obj/machinery/atmospherics/components/unary/outlet_injector
/obj/item/pipe/directional/he_exchanger
icon_state_preview = "heunary"
pipe_type = /obj/machinery/atmospherics/components/unary/heat_exchanger
/obj/item/pipe/binary
RPD_type = PIPE_STRAIGHT
/obj/item/pipe/binary/layer_adapter
icon_state_preview = "manifoldlayer"
pipe_type = /obj/machinery/atmospherics/pipe/layer_manifold
/obj/item/pipe/binary/color_adapter
icon_state_preview = "adapter_center"
pipe_type = /obj/machinery/atmospherics/pipe/color_adapter
/obj/item/pipe/binary/pressure_pump
icon_state_preview = "pump"
pipe_type = /obj/machinery/atmospherics/components/binary/pump
/obj/item/pipe/binary/manual_valve
icon_state_preview = "mvalve"
pipe_type = /obj/machinery/atmospherics/components/binary/valve
/obj/item/pipe/binary/bendable
RPD_type = PIPE_BENDABLE
/obj/item/pipe/trinary
RPD_type = PIPE_TRINARY
/obj/item/pipe/trinary/flippable
RPD_type = PIPE_TRIN_M
var/flipped = FALSE
/obj/item/pipe/trinary/flippable/filter
icon_state_preview = "filter"
pipe_type = /obj/machinery/atmospherics/components/trinary/filter
/obj/item/pipe/trinary/flippable/mixer
icon_state_preview = "mixer"
pipe_type = /obj/machinery/atmospherics/components/trinary/mixer
/obj/item/pipe/quaternary
RPD_type = PIPE_ONEDIR
/obj/item/pipe/quaternary/pipe
icon_state_preview = "manifold4w"
pipe_type = /obj/machinery/atmospherics/pipe/smart
/obj/item/pipe/quaternary/he_pipe
icon_state_preview = "he_manifold4w"
pipe_type = /obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w
/obj/item/pipe/Initialize(mapload, _pipe_type, _dir, obj/machinery/atmospherics/make_from, device_color, device_init_dir = SOUTH)
if(make_from)
make_from_existing(make_from)
else
p_init_dir = device_init_dir
pipe_type = _pipe_type
pipe_color = device_color
setDir(_dir)
update()
pixel_x += rand(-5, 5)
pixel_y += rand(-5, 5)
//Flipping handled manually due to custom handling for trinary pipes
AddComponent(/datum/component/simple_rotation, ROTATION_NO_FLIPPING)
return ..()
/obj/item/pipe/proc/make_from_existing(obj/machinery/atmospherics/make_from)
p_init_dir = make_from.initialize_directions
setDir(make_from.dir)
pipename = make_from.name
add_atom_colour(make_from.color, FIXED_COLOUR_PRIORITY)
pipe_type = make_from.type
paintable = make_from.paintable
pipe_color = make_from.pipe_color
/obj/item/pipe/trinary/flippable/make_from_existing(obj/machinery/atmospherics/components/trinary/make_from)
..()
if(make_from.flipped)
do_a_flip()
/obj/item/pipe/dropped()
if(loc)
set_piping_layer(piping_layer)
return ..()
/obj/item/pipe/proc/set_piping_layer(new_layer = PIPING_LAYER_DEFAULT)
var/obj/machinery/atmospherics/fakeA = pipe_type
if(initial(fakeA.pipe_flags) & PIPING_ALL_LAYER)
new_layer = PIPING_LAYER_DEFAULT
piping_layer = new_layer
PIPING_LAYER_SHIFT(src, piping_layer)
layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE)
/obj/item/pipe/proc/update()
var/obj/machinery/atmospherics/fakeA = pipe_type
name = "[initial(fakeA.name)] fitting"
desc = initial(fakeA.desc)
icon_state = initial(fakeA.pipe_state)
if(ispath(pipe_type,/obj/machinery/atmospherics/pipe/heat_exchanging))
resistance_flags |= FIRE_PROOF | LAVA_PROOF
/obj/item/pipe/verb/flip()
set category = "Object"
set name = "Invert Pipe"
set src in view(1)
if ( usr.incapacitated() )
return
do_a_flip()
/obj/item/pipe/proc/do_a_flip()
setDir(turn(dir, -180))
/obj/item/pipe/trinary/flippable/do_a_flip()
setDir(turn(dir, flipped ? 45 : -45))
flipped = !flipped
/obj/item/pipe/Move()
var/old_dir = dir
..()
setDir(old_dir) //pipes changing direction when moved is just annoying and buggy
// Convert dir of fitting into dir of built component
/obj/item/pipe/proc/fixed_dir()
return dir
/obj/item/pipe/binary/fixed_dir()
. = dir
if(dir == SOUTH)
. = NORTH
else if(dir == WEST)
. = EAST
/obj/item/pipe/trinary/flippable/fixed_dir()
. = dir
if(ISDIAGONALDIR(dir))
. = turn(dir, 45)
/obj/item/pipe/attack_self(mob/user)
setDir(turn(dir,-90))
///Check if the pipe on the turf and our to be placed binary pipe are perpendicular to each other
/obj/item/pipe/proc/check_ninety_degree_dir(obj/machinery/atmospherics/machine)
if(ISDIAGONALDIR(machine.dir))
return FALSE
if(EWCOMPONENT(machine.dir) && EWCOMPONENT(dir))
return FALSE
if(NSCOMPONENT(machine.dir) && NSCOMPONENT(dir))
return FALSE
return TRUE
/obj/item/pipe/wrench_act(mob/living/user, obj/item/wrench/wrench)
. = ..()
if(!isturf(loc))
return TRUE
add_fingerprint(user)
var/obj/machinery/atmospherics/fakeA = pipe_type
var/flags = initial(fakeA.pipe_flags)
var/list/potentially_conflicting_machines = list()
// Work out which machines we would potentially conflict with
for(var/obj/machinery/atmospherics/machine in loc)
// Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers.
if(machine.pipe_flags & flags & PIPING_ONE_PER_TURF)
to_chat(user, span_warning("Something is hogging the tile!"))
return TRUE
// skip checks if we don't overlap layers, either by being on the same layer or by something being on all layers
if(machine.piping_layer != piping_layer && !((machine.pipe_flags | flags) & PIPING_ALL_LAYER))
continue
potentially_conflicting_machines += machine
// See if we would conflict with any of the potentially interacting machines
for(var/obj/machinery/atmospherics/machine as anything in potentially_conflicting_machines)
// if the pipes have any directions in common, we can't place it that way.
var/our_init_dirs = SSair.get_init_dirs(pipe_type, fixed_dir(), p_init_dir)
if(machine.get_init_directions() & our_init_dirs)
// We have a conflict!
if (length(potentially_conflicting_machines) != 1 || !try_smart_reconfiguration(machine, our_init_dirs, user))
// No solutions found
to_chat(user, span_warning("There is already a pipe at that location!"))
return TRUE
// no conflicts found
var/obj/machinery/atmospherics/built_machine = new pipe_type(loc, , , p_init_dir)
build_pipe(built_machine)
built_machine.on_construction(user, pipe_color, piping_layer)
transfer_fingerprints_to(built_machine)
wrench.play_tool_sound(src)
user.visible_message( \
"[user] fastens \the [src].", \
span_notice("You fasten \the [src]."), \
span_hear("You hear ratcheting."))
qdel(src)
/**
* Attempt to automatically resolve a pipe conflict by reconfiguring any smart pipes involved.
*
* Constraints:
* - A smart pipe cannot have current connections reconfigured.
* - A smart pipe cannot have fewer than two directions in which it will connect.
* - A smart pipe, existing or new, will not automatically reconfigure itself to permit directions it was not previously permitting.
*/
/obj/item/pipe/proc/try_smart_reconfiguration(obj/machinery/atmospherics/machine, our_init_dirs, mob/living/user)
// If we're a smart pipe, we might be able to solve this by placing down a more constrained version of ourselves.
var/obj/machinery/atmospherics/pipe/smart/other_smart_pipe = machine
if(ispath(pipe_type, /obj/machinery/atmospherics/pipe/smart/))
// If we're conflicting with another smart pipe, see if we can negotiate.
if(istype(other_smart_pipe))
// Two smart pipes. This is going to get complicated.
// Check to see whether the already placed pipe is bent or not.
if (ISDIAGONALDIR(other_smart_pipe.dir))
// The other pipe is bent, with at least two current connections. See if we can bounce off it as a bent pipe in the other direction.
var/opposing_dir = our_init_dirs & ~other_smart_pipe.connections
if (ISNOTSTUB(opposing_dir))
// We only get here if both smart pipes have two directions.
p_init_dir = opposing_dir
other_smart_pipe.set_init_directions(other_smart_pipe.connections)
other_smart_pipe.update_pipe_icon()
return TRUE
// We're left with one or no available directions if we look at the complement of the other smart pipe's live connections.
// There's nothing further we can do.
return FALSE
else
// The other pipe is straight. See if we can go over it in a perpindicular direction.
// Note that the other pipe cannot be unconnected, since we have a conflict.
if(EWCOMPONENT(other_smart_pipe.dir))
if ((NORTH|SOUTH) & ~p_init_dir)
// Not allowed to connect this way
return FALSE
if (~other_smart_pipe.get_init_directions() & (EAST|WEST))
// Not allowed to reconfigure the other pipe this way
return FALSE
p_init_dir = NORTH|SOUTH
other_smart_pipe.set_init_directions(EAST|WEST)
other_smart_pipe.update_pipe_icon()
return TRUE
if (NSCOMPONENT(other_smart_pipe.dir))
if ((EAST|WEST) & ~p_init_dir)
// Not allowed to connect this way
return FALSE
if (~other_smart_pipe.get_init_directions() & (NORTH|SOUTH))
// Not allowed to reconfigure the other pipe this way
return FALSE
p_init_dir = EAST|WEST
other_smart_pipe.set_init_directions(NORTH|SOUTH)
other_smart_pipe.update_pipe_icon()
return TRUE
return FALSE
// We're not dealing with another smart pipe. See if we can become the complement of the conflicting machine.
var/opposing_dir = our_init_dirs & ~machine.get_init_directions()
if (ISNOTSTUB(opposing_dir))
// We have at least two permitted directions in the complement. Use them.
p_init_dir = opposing_dir
return TRUE
return FALSE
else if(istype(other_smart_pipe))
// We're not a smart pipe ourselves, but we are conflicting with a smart pipe. We might be able to solve this by constraining the smart pipe.
if (our_init_dirs & other_smart_pipe.connections)
// We needed to go where a smart pipe already had connections, nothing further we can do
return FALSE
var/opposing_dir = other_smart_pipe.get_init_directions() & ~our_init_dirs
if (ISNOTSTUB(opposing_dir))
// At least two directions remain for that smart pipe, reconfigure it
other_smart_pipe.set_init_directions(opposing_dir)
other_smart_pipe.update_pipe_icon()
return TRUE
return FALSE
// No smart pipes involved, the conflict can't be solved this way.
return FALSE
/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A)
A.setDir(fixed_dir())
A.set_init_directions(p_init_dir)
if(pipename)
A.name = pipename
if(A.on)
// Certain pre-mapped subtypes are on by default, we want to preserve
// every other aspect of these subtypes (name, pre-set filters, etc.)
// but they shouldn't turn on automatically when wrenched.
A.on = FALSE
/obj/item/pipe/trinary/flippable/build_pipe(obj/machinery/atmospherics/components/trinary/T)
..()
T.flipped = flipped
/obj/item/pipe/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] shoves [src] in [user.p_their()] mouth and turns it on! It looks like [user.p_theyre()] trying to commit suicide!"))
if(iscarbon(user))
var/mob/living/carbon/C = user
for(var/i in 1 to 20)
C.vomit(0, TRUE, FALSE, 4, FALSE)
if(prob(20))
C.spew_organ()
sleep(0.5 SECONDS)
C.blood_volume = 0
return(OXYLOSS|BRUTELOSS)
/obj/item/pipe/examine(mob/user)
. = ..()
. += span_notice("The pipe layer is set to [piping_layer].")
. += span_notice("You can change the pipe layer by Right-Clicking the device.")
/obj/item/pipe/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
return
var/layer_to_set = (piping_layer >= PIPING_LAYER_MAX) ? PIPING_LAYER_MIN : (piping_layer + 1)
set_piping_layer(layer_to_set)
balloon_alert(user, "pipe layer set to [piping_layer]")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/item/pipe/AltClick(mob/user)
return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation
/obj/item/pipe/trinary/flippable/examine(mob/user)
. = ..()
. += span_notice("You can flip the device by Right-Clicking it.")
/obj/item/pipe/trinary/flippable/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
return
do_a_flip()
balloon_alert(user, "pipe was flipped")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/item/pipe_meter
name = "meter"
desc = "A meter that can be wrenched on pipes, or attached to the floor with screws."
icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi'
icon_state = "meter"
inhand_icon_state = "buildpipe"
w_class = WEIGHT_CLASS_BULKY
var/piping_layer = PIPING_LAYER_DEFAULT
/obj/item/pipe_meter/wrench_act(mob/living/user, obj/item/wrench/W)
. = ..()
var/obj/machinery/atmospherics/pipe/pipe
for(var/obj/machinery/atmospherics/pipe/P in loc)
if(P.piping_layer == piping_layer)
pipe = P
break
if(!pipe)
to_chat(user, span_warning("You need to fasten it to a pipe!"))
return TRUE
new /obj/machinery/meter(loc, piping_layer)
W.play_tool_sound(src)
to_chat(user, span_notice("You fasten the meter to the pipe."))
qdel(src)
/obj/item/pipe_meter/screwdriver_act(mob/living/user, obj/item/S)
. = ..()
if(.)
return TRUE
if(!isturf(loc))
to_chat(user, span_warning("You need to fasten it to the floor!"))
return TRUE
new /obj/machinery/meter/turf(loc, piping_layer)
S.play_tool_sound(src)
to_chat(user, span_notice("You fasten the meter to the [loc.name]."))
qdel(src)
/obj/item/pipe_meter/dropped()
. = ..()
if(loc)
setAttachLayer(piping_layer)
/obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT)
piping_layer = new_layer
PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer)