mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 14:08:31 +01:00
## About The Pull Request 1. DeForest first aid stations can now be constructed and deconstructed Deconstruction is a simple screwdriver + crowbar. Construction involves the following: ``` 1x wound analyzer 1x syringe 1x scalpel 1x hemostat ``` Extra circuitboards can only be obtained from cargo for 1200 credits. Emag status is tied to the circuitboard. Innate armor has been tweaked to accomodate. 2. DeForest emergency first aid stations now take 2x longer to recharge but heal 2x faster. These are the ones you find on shuttles that are always free to use. 3. Medical crate larp updates Most medical supplies now come in generic `DeForest Medical crate`s. Sansufentanyl now comes in a generic `Interdyne Pharmaceutics crate`. ## Why It's Good For The Game 1. I wanted a way to replace broken or hacked first aid stations WITHOUT making them spammable. (They really musn't be spammable or it puts Medbay out of a job.) So Cargo seemed pretty appropriate. 2. Since you can now theoretically steal these from space ruins (which I think is fine and cool), they needed a small update so as to not also put Medbay out of a job. 3. Larp larp larp. Cementing DeForest as the main provider of conventional medical supplies, but also Sansufentanyl is already flavored as being produced by Interdyne. ## Changelog 🆑 Melbert add: You can now deconstruct first aid stations with a screwdriver and a crowbar. add: You can now buy first aid station circuitboards from cargo for 1200 credits. add: Most medical supplies ordered from cargo now come in DeForest crates. Sansufentanyl comes in an Interdyne create. balance: As they can now be replaced, first aid station armor has been reduced. balance: Emergency first aid stations now heal twice as fast but also take twice as long to recharge. /🆑
472 lines
16 KiB
Plaintext
472 lines
16 KiB
Plaintext
/obj/structure/frame/machine
|
|
name = "machine frame"
|
|
desc = "The standard frame for most station appliances. Its appearance and function is controlled by the inserted board."
|
|
board_type = /obj/item/circuitboard/machine
|
|
/// List of all compnents inside the frame contributing to its construction
|
|
var/list/components
|
|
/// List of all components required to construct the frame
|
|
var/list/req_components
|
|
/// User-friendly list of names of required components
|
|
var/list/req_component_names
|
|
|
|
/obj/structure/frame/machine/Initialize(mapload)
|
|
. = ..()
|
|
register_context()
|
|
|
|
/obj/structure/frame/machine/Destroy()
|
|
QDEL_LIST(components)
|
|
return ..()
|
|
|
|
/obj/structure/frame/machine/atom_deconstruct(disassembled = TRUE)
|
|
if(state >= FRAME_STATE_WIRED)
|
|
new /obj/item/stack/cable_coil(drop_location(), 5)
|
|
dump_contents()
|
|
return ..()
|
|
|
|
/obj/structure/frame/machine/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
|
. = NONE
|
|
if(isnull(held_item))
|
|
return
|
|
|
|
if(held_item.tool_behaviour == TOOL_WRENCH && !circuit?.needs_anchored)
|
|
context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Unan" : "An"]chor"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
switch(state)
|
|
if(FRAME_STATE_EMPTY)
|
|
if(istype(held_item, /obj/item/stack/cable_coil))
|
|
context[SCREENTIP_CONTEXT_LMB] = "Wire Frame"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
else if(held_item.tool_behaviour == TOOL_WELDER)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Unweld frame"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
else if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Disassemble frame"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
if(FRAME_STATE_WIRED)
|
|
if(held_item.tool_behaviour == TOOL_WIRECUTTER)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Cut wires"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
else if(istype(held_item, board_type))
|
|
context[SCREENTIP_CONTEXT_LMB] = "Insert board"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
if(FRAME_STATE_BOARD_INSTALLED)
|
|
if(held_item.tool_behaviour == TOOL_CROWBAR)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Pry out components"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
else if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
|
|
var/needs_components = FALSE
|
|
for(var/component in req_components)
|
|
if(!req_components[component])
|
|
continue
|
|
needs_components = TRUE
|
|
break
|
|
if(!needs_components)
|
|
context[SCREENTIP_CONTEXT_LMB] = "Complete frame"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
else if(!istype(held_item, /obj/item/storage/part_replacer))
|
|
for(var/component in req_components)
|
|
if(!req_components[component])
|
|
continue
|
|
var/stock_part_path
|
|
if(ispath(component, /obj/item))
|
|
stock_part_path = component
|
|
else if(ispath(component, /datum/stock_part))
|
|
var/datum/stock_part/stock_part_datum_type = component
|
|
stock_part_path = initial(stock_part_datum_type.physical_object_type)
|
|
if(istype(held_item, stock_part_path))
|
|
context[SCREENTIP_CONTEXT_LMB] = "Insert part"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
/obj/structure/frame/machine/examine(user)
|
|
. = ..()
|
|
if(!circuit?.needs_anchored)
|
|
. += span_notice("It can be [EXAMINE_HINT("anchored")] [anchored ? "loose." : "into place."]")
|
|
if(state == FRAME_STATE_EMPTY)
|
|
if(!anchored)
|
|
. += span_notice("It can be [EXAMINE_HINT("welded")] or [EXAMINE_HINT("screwed")] apart.")
|
|
. += span_info("It should be [EXAMINE_HINT("wired")] with 5 cables.")
|
|
return
|
|
if(state == FRAME_STATE_WIRED)
|
|
. += span_notice("Its wires can be [EXAMINE_HINT("cut")].")
|
|
if(state != FRAME_STATE_BOARD_INSTALLED)
|
|
. += span_warning("It's missing a circuit board!")
|
|
return
|
|
if(!length(req_components))
|
|
. += span_info("It requires no components.")
|
|
return
|
|
|
|
var/list/nice_list = list()
|
|
for(var/component in req_components)
|
|
if(!req_components[component])
|
|
continue
|
|
nice_list += list("[req_components[component]] [req_component_names[component]]\s")
|
|
. += span_info("It requires [english_list(nice_list, "no more components")].")
|
|
|
|
. += span_notice("All the components can be [EXAMINE_HINT("pried")] out.")
|
|
if(!length(nice_list))
|
|
. += span_info("The frame should be [EXAMINE_HINT("screwed")] to complete it.")
|
|
|
|
/obj/structure/frame/machine/dump_contents()
|
|
var/atom/drop_loc = drop_location()
|
|
|
|
// We need a snowflake check for stack items since they don't exist anymore
|
|
for(var/component in circuit?.req_components)
|
|
if(!ispath(component, /obj/item/stack))
|
|
continue
|
|
var/obj/item/stack/stack_path = component
|
|
var/stack_amount = circuit.req_components[component] - req_components[component]
|
|
if(stack_amount > 0)
|
|
new stack_path(drop_loc, stack_amount)
|
|
|
|
// Rest of the stuff can just be spat out (this includes the circuitboard0)
|
|
for(var/component in components)
|
|
if(ismovable(component))
|
|
var/atom/movable/atom_component = component
|
|
atom_component.forceMove(drop_loc)
|
|
|
|
else if(istype(component, /datum/stock_part))
|
|
var/datum/stock_part/stock_part_datum = component
|
|
var/physical_object_type = initial(stock_part_datum.physical_object_type)
|
|
new physical_object_type(drop_loc)
|
|
|
|
else
|
|
stack_trace("Invalid component [component] was found in constructable frame")
|
|
|
|
components = null
|
|
req_components = null
|
|
req_component_names = null
|
|
|
|
/obj/structure/frame/machine/install_board(mob/living/user, obj/item/circuitboard/machine/board, by_hand = TRUE)
|
|
if(state == FRAME_STATE_EMPTY)
|
|
balloon_alert(user, "needs wiring!")
|
|
return FALSE
|
|
if(state == FRAME_STATE_BOARD_INSTALLED)
|
|
balloon_alert(user, "circuit already installed!")
|
|
return FALSE
|
|
if(!anchored && istype(board) && board.needs_anchored)
|
|
balloon_alert(user, "frame must be anchored!")
|
|
return FALSE
|
|
|
|
return ..()
|
|
|
|
/obj/structure/frame/machine/circuit_added(obj/item/circuitboard/machine/added)
|
|
state = FRAME_STATE_BOARD_INSTALLED
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
|
|
//add circuit board as the first component to the list of components
|
|
//required for part_replacer to locate it while exchanging parts
|
|
//so it does not early return in /obj/machinery/proc/exchange_parts
|
|
components = list(circuit)
|
|
req_components = added.req_components.Copy()
|
|
if(!req_components)
|
|
return
|
|
|
|
//creates a list of names from all the required parts
|
|
req_component_names = list()
|
|
for(var/component_path in req_components)
|
|
if(!ispath(component_path))
|
|
continue
|
|
|
|
if(ispath(component_path, /obj/item/stack))
|
|
var/obj/item/stack/stack_path = component_path
|
|
if(initial(stack_path.singular_name))
|
|
req_component_names[component_path] = initial(stack_path.singular_name)
|
|
else
|
|
req_component_names[component_path] = initial(stack_path.name)
|
|
else if(ispath(component_path, /datum/stock_part))
|
|
var/datum/stock_part/stock_part = component_path
|
|
var/obj/item/physical_object_type = initial(stock_part.physical_object_type)
|
|
|
|
req_component_names[component_path] = initial(physical_object_type.name)
|
|
else if(ispath(component_path, /obj/item/stock_parts))
|
|
var/obj/item/stock_parts/stock_part = component_path
|
|
|
|
if(!added.specific_parts && initial(stock_part.base_name))
|
|
req_component_names[component_path] = initial(stock_part.base_name)
|
|
else
|
|
req_component_names[component_path] = initial(stock_part.name)
|
|
else if(ispath(component_path, /obj/item))
|
|
var/obj/item/part = component_path
|
|
|
|
req_component_names[component_path] = initial(part.name)
|
|
else
|
|
stack_trace("Invalid component part [component_path] in [type], couldn't get its name")
|
|
req_component_names[component_path] = "[component_path] (this is a bug)"
|
|
|
|
/obj/structure/frame/machine/circuit_removed(obj/item/circuitboard/machine/removed)
|
|
components -= removed
|
|
state = FRAME_STATE_WIRED
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
|
|
/**
|
|
* Returns the instance of path1 in list, else path2 in list
|
|
*
|
|
* Arguments
|
|
* * parts - the list of parts to search
|
|
* * path1 - the first path to search for
|
|
* * path2 - the second path to search for, if path1 is not found
|
|
*/
|
|
/obj/structure/frame/machine/proc/look_for(list/parts, path1, path2)
|
|
PRIVATE_PROC(TRUE)
|
|
|
|
return (locate(path1) in parts) || (path2 ? (locate(path2) in parts) : null)
|
|
|
|
/obj/structure/frame/machine/install_parts_from_part_replacer(mob/living/user, obj/item/storage/part_replacer/replacer, no_sound = FALSE)
|
|
if(!length(replacer.contents))
|
|
return FALSE
|
|
var/amt = 0
|
|
for(var/path in req_components)
|
|
amt += req_components[path]
|
|
if(!amt)
|
|
return FALSE
|
|
|
|
var/play_sound = FALSE
|
|
var/list/part_list = replacer.get_sorted_parts() //parts sorted in order of tier
|
|
for(var/path in req_components)
|
|
var/target_path
|
|
if(ispath(path, /datum/stock_part))
|
|
var/datum/stock_part/datum_part = path
|
|
target_path = initial(datum_part.physical_object_base_type)
|
|
else
|
|
target_path = path
|
|
|
|
var/obj/item/part
|
|
while(req_components[path] > 0 && (part = look_for(part_list, target_path, ispath(path, /obj/item/stack/ore/bluespace_crystal) ? /obj/item/stack/sheet/bluespace_crystal : null)))
|
|
part_list -= part
|
|
if(istype(part, /obj/item/stack))
|
|
var/obj/item/stack/S = part
|
|
var/used_amt = min(round(S.get_amount()), req_components[path])
|
|
var/stack_name = S.singular_name
|
|
if(!used_amt || !S.use(used_amt))
|
|
continue
|
|
req_components[path] -= used_amt
|
|
// No balloon alert here so they can look back and see what they added
|
|
to_chat(user, span_notice("You add [used_amt] [stack_name] to [src]."))
|
|
play_sound = TRUE
|
|
else if(replacer.atom_storage.attempt_remove(part, src))
|
|
var/stock_part_datum = GLOB.stock_part_datums_per_object[part.type]
|
|
if (!isnull(stock_part_datum))
|
|
components += stock_part_datum
|
|
qdel(part)
|
|
else
|
|
components += part
|
|
part.forceMove(src)
|
|
req_components[path]--
|
|
// No balloon alert here so they can look back and see what they added
|
|
to_chat(user, span_notice("You add [part] to [src]."))
|
|
play_sound = TRUE
|
|
|
|
if(play_sound && !no_sound)
|
|
replacer.play_rped_effect()
|
|
|
|
return TRUE
|
|
|
|
/obj/structure/frame/machine/can_be_unfasten_wrench(mob/user, silent)
|
|
. = ..()
|
|
if(. != SUCCESSFUL_UNFASTEN)
|
|
return .
|
|
|
|
if(circuit?.needs_anchored)
|
|
balloon_alert(user, "frame must be anchored!")
|
|
return FAILED_UNFASTEN
|
|
|
|
return .
|
|
|
|
/obj/structure/frame/machine/screwdriver_act(mob/living/user, obj/item/tool)
|
|
. = ..()
|
|
if(. & ITEM_INTERACT_ANY_BLOCKER)
|
|
return .
|
|
if(state != FRAME_STATE_BOARD_INSTALLED)
|
|
return .
|
|
if(finalize_construction(user, tool))
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
/obj/structure/frame/machine/wirecutter_act(mob/living/user, obj/item/tool)
|
|
if(user.combat_mode)
|
|
return NONE
|
|
if(state != FRAME_STATE_WIRED)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
balloon_alert(user, "removing cables...")
|
|
if(!tool.use_tool(src, user, 2 SECONDS, volume = 50) || state != FRAME_STATE_WIRED)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
state = FRAME_STATE_EMPTY
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
new /obj/item/stack/cable_coil(drop_location(), 5)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/structure/frame/machine/crowbar_act(mob/living/user, obj/item/tool)
|
|
if(user.combat_mode)
|
|
return NONE
|
|
if(state != FRAME_STATE_BOARD_INSTALLED)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
tool.play_tool_sound(src)
|
|
var/list/leftover_components = components.Copy() - circuit
|
|
dump_contents()
|
|
balloon_alert(user, "circuit board[length(leftover_components) ? " and components" : ""] removed")
|
|
// Circuit exited handles updating state
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/**
|
|
* Attempts to add the passed part to the frame
|
|
*
|
|
* Requires no sanity check that the passed part is a stock part
|
|
*
|
|
* Arguments
|
|
* * user - the player
|
|
* * tool - the part to add
|
|
*/
|
|
/obj/structure/frame/machine/proc/add_part(mob/living/user, obj/item/tool)
|
|
PRIVATE_PROC(TRUE)
|
|
|
|
for(var/stock_part_base in req_components)
|
|
if (req_components[stock_part_base] == 0)
|
|
continue
|
|
|
|
var/stock_part_path
|
|
|
|
if(ispath(stock_part_base, /obj/item))
|
|
stock_part_path = stock_part_base
|
|
else if(ispath(stock_part_base, /datum/stock_part))
|
|
var/datum/stock_part/stock_part_datum_type = stock_part_base
|
|
stock_part_path = initial(stock_part_datum_type.physical_object_type)
|
|
else
|
|
stack_trace("Bad stock part in req_components: [stock_part_base]")
|
|
continue
|
|
|
|
//if we require an bluespace crystall and we have an full sheet of them we can allow that
|
|
if(ispath(stock_part_path, /obj/item/stack/ore/bluespace_crystal) && istype(tool, /obj/item/stack/sheet/bluespace_crystal))
|
|
pass() //allow it
|
|
else if(!istype(tool, stock_part_path))
|
|
continue
|
|
|
|
if(isstack(tool))
|
|
var/obj/item/stack/S = tool
|
|
var/used_amt = min(round(S.get_amount()), req_components[stock_part_path])
|
|
if(used_amt && S.use(used_amt))
|
|
req_components[stock_part_path] -= used_amt
|
|
// No balloon alert here so they can look back and see what they added
|
|
to_chat(user, span_notice("You add [tool] to [src]."))
|
|
return
|
|
|
|
// We might end up qdel'ing the part if it's a stock part datum.
|
|
// In practice, this doesn't have side effects to the name,
|
|
// but academically we should not be using an object after it's deleted.
|
|
var/part_name = "\the [tool]"
|
|
|
|
if (ispath(stock_part_base, /datum/stock_part))
|
|
// We can't just reuse stock_part_path here or its singleton,
|
|
// or else putting in a tier 2 part will deconstruct to a tier 1 part.
|
|
var/stock_part_datum = GLOB.stock_part_datums_per_object[tool.type]
|
|
if (isnull(stock_part_datum))
|
|
stack_trace("[tool.type] does not have an associated stock part datum!")
|
|
continue
|
|
|
|
components += stock_part_datum
|
|
|
|
// We regenerate the stock parts on deconstruct.
|
|
// This technically means we lose unique qualities of the stock part, but
|
|
// it's worth it for how dramatically this simplifies the code.
|
|
// The only place I can see it affecting anything is like...RPG qualities. :P
|
|
qdel(tool)
|
|
else if(user.transferItemToLoc(tool, src))
|
|
components += tool
|
|
else
|
|
break
|
|
|
|
// No balloon alert here so they can look back and see what they added
|
|
to_chat(user, span_notice("You add [part_name] to [src]."))
|
|
req_components[stock_part_base]--
|
|
return TRUE
|
|
|
|
balloon_alert(user, "can't add that!")
|
|
return FALSE
|
|
|
|
/obj/structure/frame/machine/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
. = ..()
|
|
if(. & ITEM_INTERACT_ANY_BLOCKER)
|
|
return .
|
|
|
|
switch(state)
|
|
if(FRAME_STATE_EMPTY)
|
|
if(istype(tool, /obj/item/stack/cable_coil))
|
|
if(!tool.tool_start_check(user, amount = 5))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
balloon_alert(user, "adding cables...")
|
|
if(!tool.use_tool(src, user, 2 SECONDS, volume = 50, amount = 5) || state != FRAME_STATE_EMPTY)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
state = FRAME_STATE_WIRED
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
if(FRAME_STATE_WIRED)
|
|
if(isnull(circuit) && istype(tool, /obj/item/storage/part_replacer))
|
|
return install_circuit_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING
|
|
|
|
if(FRAME_STATE_BOARD_INSTALLED)
|
|
if(istype(tool, /obj/item/storage/part_replacer))
|
|
return install_parts_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING
|
|
|
|
return .
|
|
|
|
// Override of base_item_interaction so we only try to add parts to the frame AFTER running item_interaction and all the tool_acts
|
|
/obj/structure/frame/machine/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
. = ..()
|
|
if(. & ITEM_INTERACT_ANY_BLOCKER)
|
|
return .
|
|
if(user.combat_mode)
|
|
return NONE
|
|
|
|
return add_part(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING
|
|
|
|
/**
|
|
* Attempt to finalize the construction of the frame into a machine
|
|
* as according to our circuit and parts
|
|
*
|
|
* If successful, results in qdel'ing the frame and newing of a machine
|
|
*
|
|
* Arguments
|
|
* * user - the player
|
|
* * tool - the tool used to finalize the construction
|
|
*/
|
|
/obj/structure/frame/machine/finalize_construction(mob/living/user, obj/item/tool)
|
|
if(locate(circuit.build_path) in loc)
|
|
balloon_alert(user, "identical machine present!")
|
|
return FALSE
|
|
for(var/component in req_components)
|
|
if(req_components[component] > 0)
|
|
user.balloon_alert(user, "missing components!")
|
|
return FALSE
|
|
|
|
if(!circuit.completion_requirements(src, user))
|
|
return FALSE
|
|
|
|
tool.play_tool_sound(src)
|
|
// Prevent us from dropping stuff thanks to /Exited
|
|
var/obj/item/circuitboard/machine/leaving_circuit = circuit
|
|
components -= leaving_circuit
|
|
leaving_circuit.replacement_parts = components
|
|
// Build the machine with the replacement parts
|
|
circuit = null
|
|
var/obj/machinery/new_machine = new leaving_circuit.build_path(loc, board = leaving_circuit)
|
|
if(istype(new_machine))
|
|
// Set anchor state
|
|
new_machine.set_anchored(anchored)
|
|
//Inform machine that its finished & cleanup
|
|
new_machine.on_construction(user)
|
|
components = null
|
|
qdel(src)
|
|
return TRUE
|
|
|
|
/obj/structure/frame/machine/secured
|
|
icon_state = "box_1"
|
|
state = FRAME_STATE_WIRED
|
|
anchored = TRUE
|