Files
Bubberstation/code/modules/plumbing/ducts.dm
T
ArcaneMusic f2fd69a49a Minerals have been refactored so costs and minerals in items are now in terms of mineral defines. (#75052)
Ladies, Gentlemen, Gamers. You're probably wondering why I've called you
all here (through the automatic reviewer request system). So, mineral
balance! Mineral balance is less a balance and more of a nervous white
dude juggling spinning plates on a high-wire on his first day. The fact
it hasn't failed after going on this long is a miracle in and of itself.

This PR does not change mineral balance. What this does is moves over
every individual cost, both in crafting recipes attached to an object
over to a define based system. We have 3 defines:

`sheet_material_amount=2000` . Stock standard mineral sheet. This being
our central mineral unit, this is used for all costs 2000+.
`half_sheet_material_amount=1000` . Same as above, but using iron rods
as our inbetween for costs of 1000-1999.
`small_material_amount=100` . This hits 1-999. This covers... a
startlingly large amount of the codebase. It's feast or famine out here
in terms of mineral costs as a result, items are either sheets upon
sheets, or some fraction of small mats.

Shout out to riot darts for being the worst material cost in the game. I
will not elaborate.

Regardless, this has no functional change, but it sets the groundwork
for making future changes to material costs much, MUCH easier, and moves
over to a single, standardized set of units to help enforce coding
standards on new items, and will bring up lots of uncomfortable balance
questions down the line.

For now though, this serves as some rough boundaries on how items costs
are related, and will make adjusting these values easier going forward.

Except for foam darts.

I did round up foam darts.

Adjusting mineral balance on the macro scale will be as simple as
changing the aforementioned mineral defines, where the alternative is a
rats nest of magic number defines. ~~No seriously, 11.25 iron for a foam
dart are you kidding me what is the POINT WHY NOT JUST MAKE IT 11~~

Items individual numbers have not been adjusted yet, but we can
standardize how the conversation can be held and actually GET SOMEWHERE
on material balance as opposed to throwing our hands up or ignoring it
for another 10 years.
2023-05-03 14:44:51 +00:00

381 lines
12 KiB
Plaintext

/*
All the important duct code:
/code/datums/components/plumbing/plumbing.dm
/code/datums/ductnet.dm
*/
/obj/machinery/duct
name = "fluid duct"
icon = 'icons/obj/plumbing/fluid_ducts.dmi'
icon_state = "nduct"
layer = PLUMBING_PIPE_VISIBILE_LAYER
use_power = NO_POWER_USE
///bitfield with the directions we're connected in
var/connects
///set to TRUE to disable smart duct behaviour
var/dumb = FALSE
///wheter we allow our connects to be changed after initialization or not
var/lock_connects = FALSE
///our ductnet, wich tracks what we're connected to
var/datum/ductnet/duct
///amount we can transfer per process. note that the ductnet can carry as much as the lowest capacity duct
var/capacity = 10
///the color of our duct
var/duct_color = COLOR_VERY_LIGHT_GRAY
///TRUE to ignore colors, so yeah we also connect with other colors without issue
var/ignore_colors = FALSE
///1,2,4,8,16
var/duct_layer = DUCT_LAYER_DEFAULT
///whether we allow our layers to be altered
var/lock_layers = FALSE
///TRUE to let colors connect when forced with a wrench, false to just not do that at all
var/color_to_color_support = TRUE
///wheter to even bother with plumbing code or not
var/active = TRUE
///track ducts we're connected to. Mainly for ducts we connect to that we normally wouldn't, like different layers and colors, for when we regenerate the ducts
var/list/neighbours = list()
///what stack to drop when disconnected. Must be /obj/item/stack/ducts or a subtype
var/drop_on_wrench = /obj/item/stack/ducts
/obj/machinery/duct/Initialize(mapload, no_anchor, color_of_duct = null, layer_of_duct = null, force_connects, force_ignore_colors)
. = ..()
if(force_connects)
connects = force_connects //skip change_connects() because we're still initializing and we need to set our connects at one point
if(!lock_layers && layer_of_duct)
duct_layer = layer_of_duct
if(force_ignore_colors)
ignore_colors = force_ignore_colors
if(!ignore_colors && color_of_duct)
duct_color = color_of_duct
if(duct_color)
add_atom_colour(duct_color, FIXED_COLOUR_PRIORITY)
if(no_anchor)
active = FALSE
set_anchored(FALSE)
else if(!can_anchor())
if(mapload)
log_mapping("Overlapping ducts detected at [AREACOORD(src)], unanchoring one.")
// Note that qdeling automatically drops a duct stack
return INITIALIZE_HINT_QDEL
handle_layer()
attempt_connect()
AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE)
///start looking around us for stuff to connect to
/obj/machinery/duct/proc/attempt_connect()
for(var/direction in GLOB.cardinals)
if(dumb && !(direction & connects))
continue
for(var/atom/movable/duct_candidate in get_step(src, direction))
if(connect_network(duct_candidate, direction))
add_connects(direction)
update_appearance()
///see if whatever we found can be connected to
/obj/machinery/duct/proc/connect_network(atom/movable/plumbable, direction)
if(istype(plumbable, /obj/machinery/duct))
return connect_duct(plumbable, direction)
for(var/datum/component/plumbing/plumber as anything in plumbable.GetComponents(/datum/component/plumbing))
. += connect_plumber(plumber, direction) //so that if one is true, all is true. beautiful.
///connect to a duct
/obj/machinery/duct/proc/connect_duct(obj/machinery/duct/other, direction)
var/opposite_dir = turn(direction, 180)
if(!active || !other.active)
return
if(!dumb && other.dumb && !(opposite_dir & other.connects))
return
if(dumb && other.dumb && !(connects & other.connects)) //we eliminated a few more scenarios in attempt connect
return
if((duct == other.duct) && duct)//check if we're not just comparing two null values
add_neighbour(other, direction)
other.add_connects(opposite_dir)
other.update_appearance()
return TRUE //tell the current pipe to also update it's sprite
if(!(other in neighbours)) //we cool
if((duct_color != other.duct_color) && !(ignore_colors || other.ignore_colors))
return
if(!(duct_layer & other.duct_layer))
return
if(other.duct)
if(duct)
duct.assimilate(other.duct)
else
other.duct.add_duct(src)
else
if(duct)
duct.add_duct(other)
else
create_duct()
duct.add_duct(other)
add_neighbour(other, direction)
//Delegate to timer subsystem so its handled the next tick and doesnt cause byond to mistake it for an infinite loop and kill the game
addtimer(CALLBACK(other, PROC_REF(attempt_connect)))
return TRUE
///connect to a plumbing object
/obj/machinery/duct/proc/connect_plumber(datum/component/plumbing/plumbing, direction)
var/opposite_dir = turn(direction, 180)
if(!(duct_layer & plumbing.ducting_layer))
return FALSE
if(!plumbing.active)
return
var/comp_directions = plumbing.supply_connects + plumbing.demand_connects //they should never, ever have supply and demand connects overlap or catastrophic failure
if(opposite_dir & comp_directions)
if(!duct)
create_duct()
if(duct.add_plumber(plumbing, opposite_dir))
neighbours[plumbing.parent] = direction
return TRUE
///we disconnect ourself from our neighbours. we also destroy our ductnet and tell our neighbours to make a new one
/obj/machinery/duct/proc/disconnect_duct(skipanchor)
if(!skipanchor) //since set_anchored calls us too.
set_anchored(FALSE)
active = FALSE
if(duct)
duct.remove_duct(src)
lose_neighbours()
reset_connects(0)
update_appearance()
if(ispath(drop_on_wrench))
var/obj/item/stack/ducts/duct_stack = new drop_on_wrench(drop_location())
duct_stack.duct_color = GLOB.pipe_color_name[duct_color] || DUCT_COLOR_OMNI
duct_stack.duct_layer = GLOB.plumbing_layer_names["[duct_layer]"] || GLOB.plumbing_layer_names["[DUCT_LAYER_DEFAULT]"]
duct_stack.add_atom_colour(duct_color, FIXED_COLOUR_PRIORITY)
drop_on_wrench = null
if(!QDELING(src))
qdel(src)
///Special proc to draw a new connect frame based on neighbours. not the norm so we can support multiple duct kinds
/obj/machinery/duct/proc/generate_connects()
if(lock_connects)
return
connects = 0
for(var/A in neighbours)
connects |= neighbours[A]
update_appearance()
///create a new duct datum
/obj/machinery/duct/proc/create_duct()
duct = new()
duct.add_duct(src)
///add a duct as neighbour. this means we're connected and will connect again if we ever regenerate
/obj/machinery/duct/proc/add_neighbour(obj/machinery/duct/other, direction)
if(!(other in neighbours))
neighbours[other] = direction
if(!(src in other.neighbours))
other.neighbours[src] = turn(direction, 180)
///remove all our neighbours, and remove us from our neighbours aswell
/obj/machinery/duct/proc/lose_neighbours()
for(var/obj/machinery/duct/other in neighbours)
other.neighbours.Remove(src)
other.generate_connects()
neighbours = list()
///add a connect direction
/obj/machinery/duct/proc/add_connects(new_connects) //make this a define to cut proc calls?
if(!lock_connects)
connects |= new_connects
///remove a connect direction
/obj/machinery/duct/proc/remove_connects(dead_connects)
if(!lock_connects)
connects &= ~dead_connects
///remove our connects
/obj/machinery/duct/proc/reset_connects()
if(!lock_connects)
connects = 0
///get a list of the ducts we can connect to if we are dumb
/obj/machinery/duct/proc/get_adjacent_ducts()
var/list/adjacents = list()
for(var/direction in GLOB.cardinals)
if(direction & connects)
for(var/obj/machinery/duct/other in get_step(src, direction))
if((turn(direction, 180) & other.connects) && other.active)
adjacents += other
return adjacents
/obj/machinery/duct/update_icon_state()
var/temp_icon = initial(icon_state)
for(var/direction in GLOB.cardinals)
switch(direction & connects)
if(NORTH)
temp_icon += "_n"
if(SOUTH)
temp_icon += "_s"
if(EAST)
temp_icon += "_e"
if(WEST)
temp_icon += "_w"
icon_state = temp_icon
return ..()
///update the layer we are on
/obj/machinery/duct/proc/handle_layer()
var/offset
//it's a bitfield, but it's fine because ducts themselves are only on one layer
switch(duct_layer)
if(FIRST_DUCT_LAYER)
offset = -10
if(SECOND_DUCT_LAYER)
offset = -5
if(THIRD_DUCT_LAYER)
offset = 0
if(FOURTH_DUCT_LAYER)
offset = 5
if(FIFTH_DUCT_LAYER)
offset = 10
pixel_x = offset
pixel_y = offset
layer = initial(layer) + duct_layer * 0.0003
/obj/machinery/duct/set_anchored(anchorvalue)
. = ..()
if(isnull(.))
return
if(anchorvalue)
active = TRUE
attempt_connect()
else
disconnect_duct(TRUE)
/obj/machinery/duct/wrench_act(mob/living/user, obj/item/wrench) //I can also be the RPD
..()
add_fingerprint(user)
wrench.play_tool_sound(src)
if(anchored || can_anchor())
set_anchored(!anchored)
user.visible_message( \
"[user] [anchored ? null : "un"]fastens \the [src].", \
span_notice("You [anchored ? null : "un"]fasten \the [src]."), \
span_hear("You hear ratcheting."))
return TRUE
///collection of all the sanity checks to prevent us from stacking ducts that shouldn't be stacked
/obj/machinery/duct/proc/can_anchor(turf/destination)
if(!destination)
destination = get_turf(src)
for(var/obj/machinery/duct/other in destination)
if(other.anchored && other != src && (duct_layer & other.duct_layer))
return FALSE
for(var/obj/machinery/machine in destination)
for(var/datum/component/plumbing/plumber as anything in machine.GetComponents(/datum/component/plumbing))
if(plumber.ducting_layer & duct_layer)
return FALSE
return TRUE
/obj/machinery/duct/doMove(destination)
. = ..()
disconnect_duct()
set_anchored(FALSE)
/obj/machinery/duct/Destroy()
disconnect_duct()
return ..()
/obj/machinery/duct/MouseDrop_T(atom/drag_source, mob/living/user)
if(!istype(drag_source, /obj/machinery/duct))
return
var/obj/machinery/duct/other = drag_source
if(get_dist(src, other) != 1)
return
var/direction = get_dir(src, other)
if(!(direction in GLOB.cardinals))
return
if(!(duct_layer & other.duct_layer))
to_chat(user, span_warning("The ducts must be on the same layer to connect them!"))
return
var/obj/item/held_item = user.get_active_held_item()
if(held_item?.tool_behaviour != TOOL_WRENCH)
to_chat(user, span_warning("You need to be holding a wrench in your active hand to do that!"))
return
add_connects(direction) //the connect of the other duct is handled in connect_network, but do this here for the parent duct because it's not necessary in normal cases
add_neighbour(other, direction)
connect_network(other, direction)
update_appearance()
held_item.play_tool_sound(src)
to_chat(user, span_notice("You connect the two plumbing ducts."))
/obj/item/stack/ducts
name = "stack of duct"
desc = "A stack of fluid ducts."
singular_name = "duct"
icon = 'icons/obj/plumbing/fluid_ducts.dmi'
icon_state = "ducts"
mats_per_unit = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*5)
w_class = WEIGHT_CLASS_TINY
novariants = FALSE
max_amount = 50
item_flags = NOBLUDGEON
merge_type = /obj/item/stack/ducts
matter_amount = 1
///Color of our duct
var/duct_color = "omni"
///Default layer of our duct
var/duct_layer = "Default Layer"
/obj/item/stack/ducts/examine(mob/user)
. = ..()
. += span_notice("It's current color and layer are [duct_color] and [duct_layer]. Use in-hand to change.")
/obj/item/stack/ducts/attack_self(mob/user)
var/new_layer = tgui_input_list(user, "Select a layer", "Layer", GLOB.plumbing_layers, duct_layer)
if(new_layer)
duct_layer = new_layer
var/new_color = tgui_input_list(user, "Select a color", "Color", GLOB.pipe_paint_colors, duct_color)
if(new_color)
duct_color = new_color
add_atom_colour(GLOB.pipe_paint_colors[new_color], FIXED_COLOUR_PRIORITY)
/obj/item/stack/ducts/afterattack(atom/target, user, proximity)
. = ..()
if(!proximity)
return
if(istype(target, /obj/machinery/duct))
var/obj/machinery/duct/duct = target
if(duct.anchored)
to_chat(user, span_warning("The duct must be unanchored before it can be picked up."))
return
// Turn into a duct stack and then merge to the in-hand stack.
var/obj/item/stack/ducts/stack = new(duct.loc, 1, FALSE)
qdel(duct)
if(stack.can_merge(src))
stack.merge(src)
return
check_attach_turf(target)
/obj/item/stack/ducts/proc/check_attach_turf(atom/target)
if(isopenturf(target) && use(1))
var/turf/open/open_turf = target
var/is_omni = duct_color == DUCT_COLOR_OMNI
new /obj/machinery/duct(open_turf, FALSE, GLOB.pipe_paint_colors[duct_color], GLOB.plumbing_layers[duct_layer], null, is_omni)
playsound(get_turf(src), 'sound/machines/click.ogg', 50, TRUE)
/obj/item/stack/ducts/fifty
amount = 50