Files
Bubberstation/code/modules/recycling/conveyor.dm
T
Ghom d4cdd6b63e Replaces lava and chasm's "safeties" and ignoring turf slowdown on catwalks with traits and a new element. (#76376)
## About The Pull Request
This adds a new element for movables that grants turfs they're in
traits, changes lava and the chasm component to check for traits
instead, ditto for turf slowdown. It also implements another trait that
prevents wet floor from slipping people, as well as some other changes
(feel free to opine on them really):
- Tables and conveyor belts now stop turf slowdown, much like catwalks,
as I imagine people walking on them are not really touching the floor.
(I'd include protection against lava too... until they melt, but that'd
mean finding a way to have these objects burn in the first place, and
lava code is still stupid despite a years old refactor I did)
- Tables also stop slippery turfs from slipping (bananas, soaps etc.
still apply). I wish there were a way to make some objects slippery by
coating them in water vapor or splashing water/lube, but that's outside
the scope of this PR.
- Fixed an edge case in which a mob standing on a lava turf would be
left permanently visually on fire if the lava is changed to another kind
of turf.
- Removed unused code from stone tiles.

I'm going to include these traits in that global list for admin-added
traits... tomorrow perhaps. 💤

## Why It's Good For The Game
Replacing some hard-coded mechanics with easier to use traits and an
element, which I also need for the submerge element PR.

## Changelog

🆑
refactor: Replaced hardcoded "safeties" for lava, chasms and ignoring
turf slowdowns on catwalks with traits.
balance: much like catwalks, tables and conveyors also disable turf
slowdowns.
balance: slippery turfs won't slip you when walking on a table.
fix: Fixed an edge case in which a mob standing on a lava turf would be
left visually but permanently on fire if the lava is changed to another
kind of turf.
/🆑
2023-07-07 10:04:33 +01:00

597 lines
20 KiB
Plaintext

/// Maximum amount of items a conveyor can move at once.
#define MAX_CONVEYOR_ITEMS_MOVE 30
/// Conveyor is currently off.
#define CONVEYOR_OFF 0
/// Conveyor is currently configured to move items forward.
#define CONVEYOR_FORWARD 1
/// Conveyor is currently configured to move items backwards.
#define CONVEYOR_BACKWARDS -1
GLOBAL_LIST_EMPTY(conveyors_by_id)
/obj/machinery/conveyor
icon = 'icons/obj/recycling.dmi'
icon_state = "conveyor_map"
base_icon_state = "conveyor"
name = "conveyor belt"
desc = "A conveyor belt."
layer = BELOW_OPEN_DOOR_LAYER
processing_flags = NONE
/// The current state of the switch.
var/operating = CONVEYOR_OFF
/// This is the default (forward) direction, set by the map dir.
var/forwards
/// The opposite of forwards. It's set in a special var for corner belts, which aren't using the opposite direction when in reverse.
var/backwards
/// The actual direction to move stuff in.
var/movedir
/// The time between movements of the conveyor belts, base 0.2 seconds
var/speed = 0.2
/// The control ID - must match at least one conveyor switch's ID to be useful.
var/id = ""
/// Inverts the direction the conveyor belt moves when true.
var/inverted = FALSE
/// Is the conveyor's belt flipped? Useful mostly for conveyor belt corners. It makes the belt point in the other direction, rather than just going in reverse.
var/flipped = FALSE
/// Are we currently conveying items?
var/conveying = FALSE
//Direction -> if we have a conveyor belt in that direction
var/list/neighbors
/obj/machinery/conveyor/Initialize(mapload)
. = ..()
AddElement(/datum/element/footstep_override, priority = STEP_SOUND_CONVEYOR_PRIORITY)
var/static/list/give_turf_traits = list(TRAIT_TURF_IGNORE_SLOWDOWN)
AddElement(/datum/element/give_turf_traits, give_turf_traits)
/obj/machinery/conveyor/examine(mob/user)
. = ..()
if(inverted)
. += span_notice("It is currently set to go in reverse.")
. += "\nLeft-click with a <b>wrench</b> to rotate."
. += "Left-click with a <b>screwdriver</b> to invert its direction."
. += "Right-click with a <b>screwdriver</b> to flip its belt around."
/obj/machinery/conveyor/centcom_auto
id = "round_end_belt"
/obj/machinery/conveyor/inverted //Directions inverted so you can use different corner pieces.
icon_state = "conveyor_map_inverted"
flipped = TRUE
/obj/machinery/conveyor/inverted/Initialize(mapload)
. = ..()
if(mapload && !(ISDIAGONALDIR(dir)))
log_mapping("[src] at [AREACOORD(src)] spawned without using a diagonal dir. Please replace with a normal version.")
// Auto conveyor is always on unless unpowered.
/obj/machinery/conveyor/auto
processing_flags = START_PROCESSING_ON_INIT
/obj/machinery/conveyor/auto/Initialize(mapload, newdir)
. = ..()
set_operating(TRUE)
/obj/machinery/conveyor/auto/update()
. = ..()
if(.)
set_operating(TRUE)
// create a conveyor
/obj/machinery/conveyor/Initialize(mapload, new_dir, new_id)
..()
if(new_dir)
setDir(new_dir)
if(new_id)
id = new_id
neighbors = list()
///Leaving onto conveyor detection won't work at this point, but that's alright since it's an optimization anyway
///Should be fine without it
var/static/list/loc_connections = list(
COMSIG_ATOM_EXITED = PROC_REF(conveyable_exit),
COMSIG_ATOM_ENTERED = PROC_REF(conveyable_enter),
COMSIG_ATOM_INITIALIZED_ON = PROC_REF(conveyable_enter)
)
AddElement(/datum/element/connect_loc, loc_connections)
update_move_direction()
LAZYADD(GLOB.conveyors_by_id[id], src)
return INITIALIZE_HINT_LATELOAD
/obj/machinery/conveyor/LateInitialize()
. = ..()
build_neighbors()
/obj/machinery/conveyor/Destroy()
set_operating(FALSE)
LAZYREMOVE(GLOB.conveyors_by_id[id], src)
return ..()
/obj/machinery/conveyor/vv_edit_var(var_name, var_value)
if (var_name == NAMEOF(src, id))
// if "id" is varedited, update our list membership
LAZYREMOVE(GLOB.conveyors_by_id[id], src)
. = ..()
LAZYADD(GLOB.conveyors_by_id[id], src)
else
return ..()
/obj/machinery/conveyor/setDir(newdir)
. = ..()
update_move_direction()
/obj/machinery/conveyor/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(!.)
return
//Now that we've moved, rebuild our neighbors list
neighbors = list()
build_neighbors()
/obj/machinery/conveyor/proc/build_neighbors()
//This is acceptable because conveyor belts only move sometimes. Otherwise would be n^2 insanity
var/turf/our_turf = get_turf(src)
for(var/direction in GLOB.cardinals)
var/turf/new_turf = get_step(our_turf, direction)
var/obj/machinery/conveyor/valid = locate(/obj/machinery/conveyor) in new_turf
if(QDELETED(valid))
continue
neighbors["[direction]"] = TRUE
valid.neighbors["[DIRFLIP(direction)]"] = TRUE
RegisterSignal(valid, COMSIG_MOVABLE_MOVED, PROC_REF(nearby_belt_changed), override=TRUE)
RegisterSignal(valid, COMSIG_QDELETING, PROC_REF(nearby_belt_changed), override=TRUE)
valid.RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(nearby_belt_changed), override=TRUE)
valid.RegisterSignal(src, COMSIG_QDELETING, PROC_REF(nearby_belt_changed), override=TRUE)
/obj/machinery/conveyor/proc/nearby_belt_changed(datum/source)
SIGNAL_HANDLER
neighbors = list()
build_neighbors()
/**
* Proc to handle updating the directions in which the conveyor belt is moving items.
*/
/obj/machinery/conveyor/proc/update_move_direction()
switch(dir)
if(NORTH)
forwards = NORTH
backwards = SOUTH
if(SOUTH)
forwards = SOUTH
backwards = NORTH
if(EAST)
forwards = EAST
backwards = WEST
if(WEST)
forwards = WEST
backwards = EAST
if(NORTHEAST)
forwards = EAST
backwards = SOUTH
if(NORTHWEST)
forwards = NORTH
backwards = EAST
if(SOUTHEAST)
forwards = SOUTH
backwards = WEST
if(SOUTHWEST)
forwards = WEST
backwards = NORTH
if(inverted)
var/temp = forwards
forwards = backwards
backwards = temp
// We need to do this this way to ensure good functionality on corner belts.
// Basically, this allows the conveyor belts that used a flipped belt sprite to
// still convey items in the direction of their arrows. It's different from inverted,
// which makes them go backwards so they need to be ran separately, so a flipped conveyor
// can also be reversed.
if(flipped)
var/temp = forwards
forwards = backwards
backwards = temp
if(operating == CONVEYOR_FORWARD)
movedir = forwards
else
movedir = backwards
update()
/obj/machinery/conveyor/update_icon_state()
icon_state = "[base_icon_state][inverted ? -operating : operating ][flipped ? "-flipped" : ""]"
return ..()
/obj/machinery/conveyor/proc/set_operating(new_value)
if(operating == new_value)
return
operating = new_value
update_appearance()
update_move_direction()
//If we ever turn off, disable moveloops
if(operating == CONVEYOR_OFF)
for(var/atom/movable/movable in get_turf(src))
stop_conveying(movable)
/obj/machinery/conveyor/proc/update()
if(machine_stat & NOPOWER)
set_operating(FALSE)
return FALSE
update_appearance()
// If we're on, start conveying so moveloops on our tile can be refreshed if they stopped for some reason
if(operating != CONVEYOR_OFF)
for(var/atom/movable/movable in get_turf(src))
start_conveying(movable)
return TRUE
/obj/machinery/conveyor/proc/conveyable_enter(datum/source, atom/convayable)
SIGNAL_HANDLER
if(operating == CONVEYOR_OFF)
SSmove_manager.stop_looping(convayable, SSconveyors)
return
start_conveying(convayable)
/obj/machinery/conveyor/proc/conveyable_exit(datum/source, atom/convayable, direction)
SIGNAL_HANDLER
var/has_conveyor = neighbors["[direction]"]
if(!has_conveyor || !isturf(convayable.loc)) //If you've entered something on us, stop moving
SSmove_manager.stop_looping(convayable, SSconveyors)
/obj/machinery/conveyor/proc/start_conveying(atom/movable/moving)
if(QDELETED(moving))
return
var/datum/move_loop/move/moving_loop = SSmove_manager.processing_on(moving, SSconveyors)
if(moving_loop)
moving_loop.direction = movedir
moving_loop.delay = speed * 1 SECONDS
return
var/static/list/unconveyables = typecacheof(list(/obj/effect, /mob/dead))
if(!istype(moving) || is_type_in_typecache(moving, unconveyables) || moving == src)
return
moving.AddComponent(/datum/component/convey, movedir, speed * 1 SECONDS)
/obj/machinery/conveyor/proc/stop_conveying(atom/movable/thing)
if(!ismovable(thing))
return
SSmove_manager.stop_looping(thing, SSconveyors)
// attack with item, place item on conveyor
/obj/machinery/conveyor/attackby(obj/item/attacking_item, mob/living/user, params)
if(attacking_item.tool_behaviour == TOOL_CROWBAR)
user.visible_message(span_notice("[user] struggles to pry up [src] with [attacking_item]."), \
span_notice("You struggle to pry up [src] with [attacking_item]."))
if(!attacking_item.use_tool(src, user, 4 SECONDS, volume = 40))
return
set_operating(FALSE)
var/obj/item/stack/conveyor/belt_item = new /obj/item/stack/conveyor(loc, 1, TRUE, null, null, id)
if(!QDELETED(belt_item)) //God I hate stacks
transfer_fingerprints_to(belt_item)
to_chat(user, span_notice("You remove [src]."))
qdel(src)
else if(attacking_item.tool_behaviour == TOOL_WRENCH)
attacking_item.play_tool_sound(src)
setDir(turn(dir, -45))
to_chat(user, span_notice("You rotate [src]."))
else if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
attacking_item.play_tool_sound(src)
inverted = !inverted
update_move_direction()
to_chat(user, span_notice("You set [src]'s direction [inverted ? "backwards" : "back to default"]."))
else if(!user.combat_mode)
user.transferItemToLoc(attacking_item, drop_location())
else
return ..()
/obj/machinery/conveyor/attackby_secondary(obj/item/attacking_item, mob/living/user, params)
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
attacking_item.play_tool_sound(src)
flipped = !flipped
update_move_direction()
to_chat(user, span_notice("You flip [src]'s belt [flipped ? "around" : "back to normal"]."))
else if(!user.combat_mode)
user.transferItemToLoc(attacking_item, drop_location())
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
// attack with hand, move pulled object onto conveyor
/obj/machinery/conveyor/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
user.Move_Pulled(src)
/obj/machinery/conveyor/power_change()
. = ..()
update()
// Conveyor switch
/obj/machinery/conveyor_switch
name = "conveyor switch"
desc = "A conveyor control switch."
icon = 'icons/obj/recycling.dmi'
icon_state = "switch-off"
base_icon_state = "switch"
processing_flags = START_PROCESSING_MANUALLY
/// The current state of the switch.
var/position = CONVEYOR_OFF
/// Last direction setting.
var/last_pos = CONVEYOR_BACKWARDS
/// If the switch only operates the conveyor belts in a single direction.
var/oneway = FALSE
/// If the level points the opposite direction when it's turned on.
var/invert_icon = FALSE
/// The ID of the switch, must match conveyor IDs to control them.
var/id = ""
/// The set time between movements of the conveyor belts
var/conveyor_speed = 0.2
/obj/machinery/conveyor_switch/Initialize(mapload, newid)
. = ..()
if (newid)
id = newid
update_appearance()
LAZYADD(GLOB.conveyors_by_id[id], src)
set_wires(new /datum/wires/conveyor(src))
AddComponent(/datum/component/usb_port, list(
/obj/item/circuit_component/conveyor_switch,
))
/obj/machinery/conveyor_switch/Destroy()
LAZYREMOVE(GLOB.conveyors_by_id[id], src)
QDEL_NULL(wires)
. = ..()
/obj/machinery/conveyor_switch/vv_edit_var(var_name, var_value)
if (var_name == NAMEOF(src, id))
// if "id" is varedited, update our list membership
LAZYREMOVE(GLOB.conveyors_by_id[id], src)
. = ..()
LAZYADD(GLOB.conveyors_by_id[id], src)
else
return ..()
// update the icon depending on the position
/obj/machinery/conveyor_switch/update_icon_state()
icon_state = "[base_icon_state]-off"
if(position < CONVEYOR_OFF)
icon_state = "[base_icon_state]-[invert_icon ? "fwd" : "rev"]"
else if(position > CONVEYOR_OFF)
icon_state = "[base_icon_state]-[invert_icon ? "rev" : "fwd"]"
return ..()
/// Updates all conveyor belts that are linked to this switch, and tells them to start processing.
/obj/machinery/conveyor_switch/proc/update_linked_conveyors()
for(var/obj/machinery/conveyor/belt in GLOB.conveyors_by_id[id])
belt.set_operating(position)
belt.speed = conveyor_speed
CHECK_TICK
/// Finds any switches with same `id` as this one, and set their position and icon to match us.
/obj/machinery/conveyor_switch/proc/update_linked_switches()
for(var/obj/machinery/conveyor_switch/belt_switch in GLOB.conveyors_by_id[id])
belt_switch.invert_icon = invert_icon
belt_switch.position = position
belt_switch.conveyor_speed = conveyor_speed
belt_switch.update_appearance()
CHECK_TICK
/// Updates the switch's `position` and `last_pos` variable. Useful so that the switch can properly cycle between the forwards, backwards and neutral positions.
/obj/machinery/conveyor_switch/proc/update_position()
if(position == CONVEYOR_OFF)
if(oneway) //is it a oneway switch
position = oneway
else
if(last_pos < CONVEYOR_OFF)
position = CONVEYOR_FORWARD
last_pos = CONVEYOR_OFF
else
position = CONVEYOR_BACKWARDS
last_pos = CONVEYOR_OFF
else
last_pos = position
position = CONVEYOR_OFF
/// Called when a user clicks on this switch with an open hand.
/obj/machinery/conveyor_switch/interact(mob/user)
add_fingerprint(user)
update_position()
update_appearance()
update_linked_conveyors()
update_linked_switches()
/obj/machinery/conveyor_switch/attackby(obj/item/attacking_item, mob/user, params)
if(is_wire_tool(attacking_item))
wires.interact(user)
return TRUE
/obj/machinery/conveyor_switch/multitool_act(mob/living/user, obj/item/I)
var/input_speed = tgui_input_number(user, "Set the speed of the conveyor belts in seconds", "Speed", conveyor_speed, 20, 0.2, round_value = FALSE)
if(!input_speed || QDELETED(user) || QDELETED(src) || !usr.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return
conveyor_speed = input_speed
to_chat(user, span_notice("You change the time between moves to [input_speed] seconds."))
update_linked_conveyors()
return TRUE
/obj/machinery/conveyor_switch/crowbar_act(mob/user, obj/item/tool)
tool.play_tool_sound(src, 50)
var/obj/item/conveyor_switch_construct/switch_construct = new/obj/item/conveyor_switch_construct(src.loc)
switch_construct.id = id
transfer_fingerprints_to(switch_construct)
to_chat(user, span_notice("You detach [src]."))
qdel(src)
return TRUE
/obj/machinery/conveyor_switch/screwdriver_act(mob/user, obj/item/tool)
tool.play_tool_sound(src, 50)
oneway = !oneway
to_chat(user, span_notice("You set [src] to [oneway ? "one way" : "default"] configuration."))
return TRUE
/obj/machinery/conveyor_switch/wrench_act(mob/user, obj/item/tool)
tool.play_tool_sound(src, 50)
invert_icon = !invert_icon
update_appearance()
to_chat(user, span_notice("You set [src] to [invert_icon ? "inverted": "normal"] position."))
return TRUE
/obj/machinery/conveyor_switch/examine(mob/user)
. = ..()
. += span_notice("[src] is set to [oneway ? "one way" : "default"] configuration. It can be changed with a <b>screwdriver</b>.")
. += span_notice("[src] is set to [invert_icon ? "inverted": "normal"] position. It can be rotated with a <b>wrench</b>.")
. += span_notice("[src] is set to move [conveyor_speed] seconds per belt. It can be changed with a <b>multitool</b>.")
/obj/machinery/conveyor_switch/oneway
icon_state = "conveyor_switch_oneway"
desc = "A conveyor control switch. It appears to only go in one direction."
oneway = TRUE
/obj/machinery/conveyor_switch/oneway/Initialize(mapload)
. = ..()
if((dir == NORTH) || (dir == WEST))
invert_icon = TRUE
/obj/item/conveyor_switch_construct
name = "conveyor switch assembly"
desc = "A conveyor control switch assembly."
icon = 'icons/obj/recycling.dmi'
icon_state = "switch-off"
w_class = WEIGHT_CLASS_BULKY
// ID of the switch-in-the-making, to link conveyor belts to it.
var/id = ""
/obj/item/conveyor_switch_construct/Initialize(mapload)
. = ..()
id = "[rand()]" //this couldn't possibly go wrong
/obj/item/conveyor_switch_construct/attack_self(mob/user)
for(var/obj/item/stack/conveyor/belt in view())
belt.id = id
to_chat(user, span_notice("You have linked all nearby conveyor belt assemblies to this switch."))
/obj/item/conveyor_switch_construct/afterattack(atom/target, mob/user, proximity)
. = ..()
if(!proximity || user.stat || !isfloorturf(target) || istype(target, /area/shuttle))
return
var/found = FALSE
for(var/obj/machinery/conveyor/belt in view())
if(belt.id == src.id)
found = TRUE
break
if(!found)
to_chat(user, "[icon2html(src, user)]" + span_notice("The conveyor switch did not detect any linked conveyor belts in range."))
return
var/obj/machinery/conveyor_switch/built_switch = new/obj/machinery/conveyor_switch(target, id)
transfer_fingerprints_to(built_switch)
qdel(src)
/obj/item/stack/conveyor
name = "conveyor belt assembly"
desc = "A conveyor belt assembly."
icon = 'icons/obj/recycling.dmi'
icon_state = "conveyor_construct"
max_amount = 30
singular_name = "conveyor belt"
w_class = WEIGHT_CLASS_BULKY
merge_type = /obj/item/stack/conveyor
/// ID for linking a belt to one or more switches, all conveyors with the same ID will be controlled the same switch(es).
var/id = ""
/obj/item/stack/conveyor/Initialize(mapload, new_amount, merge = TRUE, list/mat_override=null, mat_amt=1, _id)
. = ..()
id = _id
/obj/item/stack/conveyor/afterattack(atom/target, mob/user, proximity)
. = ..()
if(!proximity || user.stat || !isfloorturf(target) || istype(target, /area/shuttle))
return
var/belt_dir = get_dir(target, user)
if(target == user.loc)
to_chat(user, span_warning("You cannot place a conveyor belt under yourself!"))
return
var/obj/machinery/conveyor/belt = new/obj/machinery/conveyor(target, belt_dir, id)
transfer_fingerprints_to(belt)
use(1)
/obj/item/stack/conveyor/attackby(obj/item/item_used, mob/user, params)
..()
if(istype(item_used, /obj/item/conveyor_switch_construct))
to_chat(user, span_notice("You link the switch to the conveyor belt assembly."))
var/obj/item/conveyor_switch_construct/switch_construct = item_used
id = switch_construct.id
/obj/item/stack/conveyor/update_weight()
return FALSE
/obj/item/stack/conveyor/thirty
amount = 30
/obj/item/paper/guides/conveyor
name = "paper- 'Nano-it-up U-build series, #9: Build your very own conveyor belt, in SPACE'"
default_raw_text = "<h1>Congratulations!</h1><p>You are now the proud owner of the best conveyor set available for \
space mail order! We at Nano-it-up know you love to prepare your own structures without wasting time, \
so we have devised a special streamlined assembly procedure that puts all other mail-order products to \
shame!</p><p>Firstly, you need to link the conveyor switch assembly to each of the conveyor belt \
assemblies. After doing so, you simply need to install the belt assemblies onto the floor, et voila, \
belt built. Our special Nano-it-up smart switch will detected any linked assemblies as far as the eye \
can see! This convenience, you can only have it when you Nano-it-up. Stay nano!</p>"
#undef MAX_CONVEYOR_ITEMS_MOVE
/obj/item/circuit_component/conveyor_switch
display_name = "Conveyor Switch"
desc = "Allows to control connected conveyor belts."
circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL
/// The current direction of the conveyor attached to the component.
var/datum/port/output/direction
/// The switch this conveyor switch component is attached to.
var/obj/machinery/conveyor_switch/attached_switch
/obj/item/circuit_component/conveyor_switch/populate_ports()
direction = add_output_port("Conveyor Direction", PORT_TYPE_NUMBER)
/obj/item/circuit_component/conveyor_switch/get_ui_notices()
. = ..()
. += create_ui_notice("Conveyor direction 0 means that it is stopped, 1 means that it is active and -1 means that it is working in reverse mode", "orange", "info")
/obj/item/circuit_component/conveyor_switch/register_usb_parent(atom/movable/shell)
. = ..()
if(istype(shell, /obj/machinery/conveyor_switch))
attached_switch = shell
/obj/item/circuit_component/conveyor_switch/unregister_usb_parent(atom/movable/shell)
attached_switch = null
return ..()
/obj/item/circuit_component/conveyor_switch/input_received(datum/port/input/port)
if(!attached_switch)
return
INVOKE_ASYNC(src, PROC_REF(update_conveyers), port)
/obj/item/circuit_component/conveyor_switch/proc/update_conveyers(datum/port/input/port)
if(!attached_switch)
return
attached_switch.update_position()
attached_switch.update_appearance()
attached_switch.update_linked_conveyors()
attached_switch.update_linked_switches()
direction.set_output(attached_switch.position)
#undef CONVEYOR_BACKWARDS
#undef CONVEYOR_OFF
#undef CONVEYOR_FORWARD