frame refactor (#6336)

rolling bugfix + frame refactor branch
This commit is contained in:
silicons
2024-05-26 20:08:28 +02:00
committed by GitHub
parent 4269e13f32
commit 01fa629df2
68 changed files with 1988 additions and 619 deletions
+540
View File
@@ -0,0 +1,540 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
GLOBAL_LIST_INIT(frame_datum_lookup, init_frame_datums())
/proc/init_frame_datums()
var/list/constructed = list()
for(var/datum/frame2/frame_path as anything in subtypesof(/datum/frame2))
if(initial(frame_path.abstract_type) == frame_path)
continue
var/datum/frame2/made = new frame_path
constructed[frame_path] = made
return constructed
/proc/fetch_frame_datum(datum/frame2/framelike)
if(istype(framelike))
else
framelike = GLOB.frame_datum_lookup[framelike]
return framelike
/**
* arbitrary construction framework
*
* ### how state machines work here
*
* frames operate off two principles, and are effectively a state machine:
* * stage - *usually* linear, 1 to n. stepping back from 1 deconstructs the frame. stepping forwards from n finishes the frame.
* * context - arbitrary data storage list
*
* ### how construction/deconstruction stage lists works:
*
* * set 'key' = /datum/frame_stage typepath
* * you'll probably want anonymous types.
* * please see examples.
*
* ### special things about the stage list
* * if there are no stages, we immediately finish() with stage null with no context when someone tries to place the item/structure.
*
* ### special things in general
* * wall frames should face away from the wall. most wall machinery face **away** from the wall.
*
* todo: no support for multiple 'interact' stages yet
* todo: /datum/frame_context so it can hold entities / data inside (e.g. can drop stuff if needed)
* todo: similarly, we need a way to store items inserted as part of a step, maybe as a part of context? so it can be dropped later.
*/
/datum/frame2
/// frame name
var/name = "construction frame"
/// sheet metal cost
var/material_cost = 1
// todo: non-steel support
/// for future use: set to TRUE to allow all materials
var/material_unlocked = FALSE
/// can we be built?
var/material_buildable = TRUE
/// deconstructs into item frame, or materials?
var/deconstruct_into_item = TRUE
/// deconstruct: **if and only if** steps_backwards does not specify a first step,
/// default to this tool
var/deconstruct_default_tool = TOOL_WRENCH
/// default deconstruction time
var/deconstruct_default_time = 3 SECONDS
/// default deconstruction cost multiplier
var/deconstruct_default_cost = 1
/// construction stages
/// see /datum/frame2 readme (so up above in this file) for how to do this
var/list/stages = list()
/// stage that a new construct starts at
/// defaults to stages[1]
var/stage_starting
// todo: implement anchor shit.
/// is this frame freely un/anchorable?
var/freely_anchorable = FALSE
/// do we need to be anchored to finish? we will not allow progression past last stage if so.
var/requires_anchored_to_finish = TRUE
/// requires anchored to do anything
var/requires_anchored = TRUE
/// starts anchored; null = [requires_anchored]
var/starts_anchored
/// anchoring time
var/anchor_time = 2 SECONDS
/// anchoring tool
var/anchor_tool = TOOL_WRENCH
/// are we a dense frame object? if it's a wall mount, the answer should probably be no.
var/has_density = FALSE
/// check for all dense objects on turf if we're dense
var/check_turf_content_density_dynamic = TRUE
/// still check for any density
var/check_turf_content_density_always = TRUE
/// check for another frame of this type on turf
var/check_turf_frame_duplicate = TRUE
/// check for another frame of this class on turf
/// * wallframes in same direction
/// * other non-wall-frames
var/check_turf_frame_collision = TRUE
/// is this frame a wall-frame?
var/wall_frame = FALSE
/// does the wall frame.. require a wall? please put yes.
var/wall_frame_requires_wall = TRUE
/// default pixel x offset for wall-frames; positive if right, negative if left
/// can set to list of "[NORTH]" = number, as well.
var/wall_pixel_x = 16
/// default pixel y offset for wall-frames; positive if up, negative if down
/// can set to list of "[NORTH]" = number, as well.
var/wall_pixel_y = 16
/// are we climbable if we're dense?
var/climb_allowed = TRUE
/// climb delay
var/climb_delay = 2 SECONDS
/// our structure's depth, if not a wall mount
var/depth_level = 16
/// does our structure project depth?
var/depth_projected = TRUE
/// our icon
var/icon
/// do we append -stage to structure?
/// structure preview state will always be "structure"
var/has_structure_stage_states = FALSE
/// do we append -stage to items?
/// item preview state will always be "item"
// todo: currently unused given lack of support for storing state
// todo: in the future, we will want this. for now, everything is just 'item'.
var/has_item_stage_states = FALSE
/// weight class of item
var/item_weight_class = WEIGHT_CLASS_NORMAL
/// weight volume of item
var/item_weight_volume = WEIGHT_VOLUME_NORMAL
/// what tool, if any to deconstruct item into materials
var/item_recycle_tool
/// time to decon for tool as item
var/item_recycle_time = 0 SECONDS
/// cost mult to decon for tool as item
var/item_recycle_cost = 1
/// what tool, if any, to attempt to set us up in a location
var/item_deploy_tool = TOOL_WRENCH
/// is tool required for deployment?
var/item_deploy_requires_tool = FALSE
/// deployment time
var/item_deploy_time = 0 SECONDS
/// deployment tool cost multiplier
var/item_deploy_cost = 1
/datum/frame2/New()
for(var/i in 1 to length(stages))
var/key = stages[i]
var/value = stages[key]
if(istype(key, /datum/frame_stage))
continue
else if(istext(key))
stages[key] = new value
if(isnull(stage_starting) && length(stages))
stage_starting = stages[1]
/datum/frame2/proc/apply_to_frame(obj/structure/frame2/frame)
frame.set_density(has_density)
frame.icon = icon
frame.update_appearance()
if(wall_frame)
// wall frames face away from walls :/
if(islist(wall_pixel_y))
frame.set_base_pixel_x(wall_pixel_x["[frame.dir]"] || 0)
else
frame.set_base_pixel_x(frame.dir & EAST? -wall_pixel_x : (frame.dir & WEST? wall_pixel_x : 0))
if(islist(wall_pixel_y))
frame.set_base_pixel_y(wall_pixel_y["[frame.dir]"] || 0)
else
frame.set_base_pixel_y(frame.dir & NORTH? -wall_pixel_y : (frame.dir & SOUTH? wall_pixel_y : 0))
frame.climb_allowed = FALSE
frame.climb_delay = 0
frame.depth_level = 0
frame.depth_projected = FALSE
else
frame.climb_allowed = climb_allowed
frame.climb_delay = climb_delay
frame.depth_level = depth_level
frame.depth_projected = depth_projected
/**
* @return finished product
*/
/datum/frame2/proc/finish_frame(obj/structure/frame2/frame, datum/event_args/actor/actor, destroy_structure = TRUE)
ASSERT(isturf(frame.loc))
. = instance_product(frame)
if(destroy_structure)
qdel(frame)
/**
* todo: /instance_from_frame()? we certainly can't have this be on /Initialize level at /obj, which sucks.. oh well.
*
* @return finished product
*/
/datum/frame2/proc/instance_product(obj/structure/frame2/frame)
CRASH("abstract proc called.")
/**
* makes frame structure from item
*
* @return /obj/structure/frame, **if** we have stages. If not, we just finish it immediately.
*/
/datum/frame2/proc/deploy_frame(obj/item/frame2/frame_item, datum/event_args/actor/actor, atom/location, dir, destroy_item = TRUE)
var/obj/structure/frame2/creating_frame = new(location, dir, src)
creating_frame.set_anchored(isnull(starts_anchored)? requires_anchored : starts_anchored)
if(destroy_item)
qdel(frame_item)
if(!length(stages))
return // return nothing
return creating_frame
/**
* @params
* * frame - the frame
* * actor - the person doing it, if any
* * put_in_hand_if_possible - put in user's hand instead of put it on the floor if possible
* * override_slice_to_parts - if non null, TRUE = deconstruct(), FALSE = collapse to item
*
* @return frame item dropped, if any.
*/
/datum/frame2/proc/deconstruct_frame(obj/structure/frame2/frame, datum/event_args/actor/actor, put_in_hand_if_possible = TRUE, override_slice_to_parts)
var/breaking_to_parts = isnull(override_slice_to_parts)? !deconstruct_into_item : override_slice_to_parts
if(breaking_to_parts)
frame.deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED)
else
var/obj/item/frame2/collapsed
if(actor?.performer && put_in_hand_if_possible)
collapsed = new(actor.performer, src)
actor.performer.put_in_hand_or_drop(collapsed)
else
collapsed = new(frame.drop_location(), src)
return collapsed
/**
* @params
* * method - ATOM_DECONSTRUCT_* define
* * where - atom to drop stuff at
* * stage_key - stage id
* * context - context list
*/
/datum/frame2/proc/drop_deconstructed_products(method, atom/where, stage_key, list/context)
// todo: drop all other stored things from steps!!
new /obj/item/stack/material/steel(where, material_cost)
/**
* always use this proc, it's guarded against race conditions.
*
* If trying to deconstruct or finish the frame, you *must* do:
* * FRAME_STAGE_DECONSTRUCT
* * FRAME_STAGE_FINISH
*
* @params
* * frame - the frame being operated on
* * from_stage - move from this stage; if current stage key doesn't match expected, we abort as it might be a race condition.
* * to_stage - move to this stage
* * actor - actor data
*
* @return TRUE / FALSE success / fail
*/
/datum/frame2/proc/move_frame_to(obj/structure/frame2/frame, from_stage, to_stage, datum/event_args/actor/actor)
if(frame.stage != from_stage)
return FALSE
var/not_obliterating = FALSE
switch(to_stage)
if(FRAME_STAGE_DECONSTRUCT)
if(FRAME_STAGE_FINISH)
else
if(isnull(stages[to_stage]))
// check your fucking inputs
CRASH("attempted to go to invalid stage!")
if(from_stage == to_stage)
// check your fucking inputs
CRASH("attempted to move from the same state to the same state. why?")
not_obliterating = TRUE
frame.stage = to_stage
on_frame_step(frame, from_stage, to_stage)
if(not_obliterating)
frame.update_appearance()
log_construction(actor, frame, "mov [from_stage] -> [to_stage]")
switch(to_stage)
if(FRAME_STAGE_DECONSTRUCT)
deconstruct_frame(frame, actor)
if(FRAME_STAGE_FINISH)
finish_frame(frame, actor)
/**
* Called when we transition stage.
* * called before update_icon() / re-renders
* * called before deconstruction/finish
*/
/datum/frame2/proc/on_frame_step(obj/structure/frame2/frame, from_stage, to_stage)
return
/datum/frame2/proc/on_examine(obj/structure/frame2/frame, datum/event_args/actor/actor, list/examine_list, distance)
var/datum/frame_stage/stage = stages[frame.stage]
examine_list += stage.on_examine(frame, actor, examine_list, distance)
examine_list += instruction_steps(frame, actor, distance)
examine_list += instruction_special(frame, actor, distance)
/**
* @return string or list of strings
*/
/datum/frame2/proc/instruction_steps(obj/structure/frame2/frame, datum/event_args/actor/actor, distance)
var/datum/frame_stage/stage = stages[frame.stage]
if(isnull(stage))
return list()
var/list/datum/frame_step/steps = stage.steps
if(!length(steps))
return list()
. = list()
for(var/datum/frame_step/step as anything in steps)
. += step.examine(frame, actor)
/**
* @return list(string, ...)
*/
/datum/frame2/proc/instruction_special(obj/structure/frame2/frame, datum/event_args/actor/actor, distance)
return list()
/**
* @return TRUE if handled
*/
/datum/frame2/proc/on_item(obj/structure/frame2/frame, obj/item/item, datum/event_args/actor/actor)
// todo: support for multiple possible steps
var/datum/frame_step/step_to_take
var/datum/frame_stage/current_stage = stages[frame.stage]
for(var/datum/frame_step/potential_step as anything in current_stage.steps)
if(!potential_step.valid_interaction(actor, item, src, frame))
continue
step_to_take = potential_step
break
if(!step_to_take)
return FALSE
var/time_needed = step_to_take.time
. = TRUE
standard_progress_step(frame, actor, item, step_to_take, time_needed)
/**
* @return TRUE if handled
*/
/datum/frame2/proc/on_tool(obj/structure/frame2/frame, obj/item/tool, datum/event_args/actor/actor, function, flags, hint)
var/datum/frame_step/step_to_take
var/datum/frame_stage/current_stage = stages[frame.stage]
for(var/datum/frame_step/potential_step as anything in current_stage.steps)
if(!potential_step.valid_interaction(actor, tool, src, frame))
continue
if(hint && (potential_step.name != hint))
continue
step_to_take = potential_step
break
if(isnull(step_to_take))
return FALSE
// tool speed is handled by use_tool
var/time_needed = step_to_take.time
return standard_progress_step(frame, actor, tool, step_to_take, time_needed)
/**
* @return list
*/
/datum/frame2/proc/on_tool_query(obj/structure/frame2/frame, obj/item/tool, datum/event_args/actor/clickchain/click)
. = list()
var/datum/frame_stage/stage = stages[frame.stage]
for(var/datum/frame_step/step as anything in stage.steps)
if(step.request_type != FRAME_REQUEST_TYPE_TOOL)
continue
if(isnull(.[step.request]))
.[step.request] = list()
.[step.request][step.name || "yell at coders"] = step.tool_image()
/**
* @return TRUE if handled
*/
/datum/frame2/proc/on_interact(obj/structure/frame2/frame, datum/event_args/actor/actor)
// todo: support for multiple possible steps
var/datum/frame_step/step_to_take
var/datum/frame_stage/current_stage = stages[frame.stage]
for(var/datum/frame_step/potential_step as anything in current_stage.steps)
if(!potential_step.valid_interaction(actor, null, src, frame))
continue
step_to_take = potential_step
break
if(isnull(step_to_take))
return FALSE
var/time_needed = step_to_take.time
return standard_progress_step(frame, actor, null, step_to_take, time_needed)
/**
* handles the do after, item manipulation, logging, and whatnot
*
* @return TRUE / FALSE
*/
/datum/frame2/proc/standard_progress_step(obj/structure/frame2/frame, datum/event_args/actor/actor, obj/item/using_item, datum/frame_step/frame_step, time_needed)
var/stage_we_were_in = frame.stage
if(frame_step.stage == FRAME_STAGE_FINISH && !frame.anchored && requires_anchored_to_finish)
actor.chat_feedback(SPAN_WARNING("[frame] needs to be anchored to be finished."), frame)
return FALSE
if((isnull(frame_step.requires_anchored)? requires_anchored : frame_step.requires_anchored) && !frame.anchored)
var/rendered = frame_step.action_descriptor || "<[frame_step.name]>"
actor.chat_feedback(SPAN_WARNING("[frame] needs to be anchored in order for you to [rendered]."), frame)
return FALSE
if(!frame_step.check_consumption(actor, using_item, src, frame))
return FALSE
frame_step.feedback_begin(
actor,
src,
frame,
using_item,
time_needed,
)
if(!frame_step.perform_usage(actor, using_item, src, frame, time_needed))
return FALSE
if(frame.stage != stage_we_were_in)
return FALSE
if(!frame_step.handle_consumption(actor, using_item, src, frame))
return FALSE
frame_step.feedback_finish(
actor,
src,
frame,
using_item,
time_needed,
)
frame_step.on_finish(src, frame, actor, using_item)
move_frame_to(frame, stage_we_were_in, frame_step.stage, actor)
return TRUE
/**
* @return finished product if finished
*/
/datum/frame2/proc/try_finish_frame(obj/structure/frame2/frame, datum/event_args/actor/actor, destroy_structure = TRUE)
ASSERT(isturf(frame.loc))
if(!completion_checks(frame, frame.loc, frame.dir, actor))
return
return finish_frame(frame, destroy_structure)
/**
* ran only on deployment, not completion
* * also ran when anchoring a frame down
*
* regarding direction
* * is in direction of machine that will be built if non-wall
* * will be in the direction of the wall from the tile if wall
*
* @return TRUE / FALSE
*/
/datum/frame2/proc/deployment_checks(obj/item/frame2/frame, turf/location, dir, datum/event_args/actor/actor, silent)
if(wall_frame && wall_frame_requires_wall)
var/turf/checking = get_step(location, turn(dir, 180))
if(!checking.get_wallmount_anchor())
actor.chat_feedback(
SPAN_WARNING("[checking] isn't a valid anchor for this wall mount!"),
target = frame,
)
return FALSE
if((check_turf_content_density_dynamic && has_density) || check_turf_content_density_always)
for(var/obj/thing in location)
if(thing.density)
return FALSE
if(check_turf_frame_collision || check_turf_frame_duplicate)
for(var/obj/structure/frame2/other_frame in location)
if(other_frame.frame == src && check_turf_frame_duplicate)
return FALSE
if(!other_frame.frame.wall_frame)
if(!wall_frame)
return FALSE
else
if(other_frame.dir == frame.dir)
return FALSE
return TRUE
/**
* ran on deployment as well as completion
*
* @return FALSE if obstructed
*/
/datum/frame2/proc/completion_checks(obj/structure/frame2/frame, turf/location, dir, datum/event_args/actor/actor, silent)
return valid_location(location, dir, actor, silent)
/**
* checks if we semantically should even be able to be built here at all;
* ran during both deployment and completion
*
* used for stuff like APCs rejecting areas that shouldn't be powerable/etc
*
* @params
* * entity - either the item or structure frame. this is generic, and is only really used for chat feedback object name purposes.
* * location - where it's being built/placed
* * dir - direction that it's being built/placed in
* * actor - (optional) person doing it
* * silent - don't emit chat feedback
*/
/datum/frame2/proc/valid_location(obj/entity, turf/location, dir, datum/event_args/actor/actor, silent)
return TRUE
/**
* gets a list of managed overlays to apply to a frame
*/
/datum/frame2/proc/get_overlays(obj/structure/frame/frame)
return list()
/**
* template action text
*/
/datum/frame2/proc/template_action_string(list/tokens, mob/performer, obj/structure/frame2/frame, obj/item/tool)
if(isnull(tokens))
return // null = null
// i wish this was typescript so i could just return tokens.map((t) => ...) :confounded:
// pov i'm losing my mcfucking mind
. = tokens.Copy()
for(var/i in 1 to length(.))
switch(.[i])
if(FRAME_TEXT_TOKEN_PERFORMER)
.[i] = "[performer]"
if(FRAME_TEXT_TOKEN_FRAME)
.[i] = "[frame]"
if(FRAME_TEXT_TOKEN_TOOL)
.[i] = "[tool]"
if(FRAME_TEXT_TOKEN_THEIR)
.[i] = "[performer.p_their()]"
if(FRAME_TEXT_TOKEN_THEM)
.[i] = "[performer.p_them()]"
if(FRAME_TEXT_TOKEN_THEYRE)
.[i] = "[performer.p_theyre()]"
return jointext(., "")
+49
View File
@@ -0,0 +1,49 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
/**
* a discrete stage in a construction frame
*/
/datum/frame_stage
/// name; for vv. defaults to key
var/name
/// key; autoset from key if associating via frames.
var/key
/// list of step datums
/// set to typepaths to init
/// anonymous typepaths are allowed and encouraged.
var/list/datum/frame_step/steps = list()
/// name prepend; if existing, will be prepended with a space
var/name_prepend
/// name append; if existing, will be appended with a space
/// * the (xyz) format e.g. "(wired)" is recommended, resulting in a render of "frame (wired)"
var/name_append
/// name override; if existing, will override base name (prepend/append are still used)
/// * the bare format e.g. "wired" is recommended, resulting in a render of "wired frame"
var/name_override
/// "the [name] [descriptor]" on examine
var/descriptor
/// default require anchor for steps; if null, default to frame
/// * this is for steps going from us, not steps going to us!
var/requires_anchored
/// allow unanchor while in this stage
/// if null, defaults to frame not being requires_anchored **or** us being the first stage.
var/allow_unanchor
/datum/frame_stage/New(set_key)
if(!isnull(set_key))
key = set_key
if(isnull(name))
name = key
for(var/i in 1 to length(steps))
var/datum/frame_step/step_casted = steps[i]
if(istype(step_casted))
continue
var/datum/frame_step/creating = new step_casted
if(isnull(creating.requires_anchored))
creating.requires_anchored = src.requires_anchored
steps[i] = creating
/datum/frame_stage/proc/on_examine(obj/structure/frame2/frame, datum/event_args/actor/actor, list/examine_list, distance)
if(descriptor)
examine_list += SPAN_NOTICE("[frame] [descriptor]")
+372
View File
@@ -0,0 +1,372 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
/**
* a transition from one stage to another
*/
/datum/frame_step
/// step name for tool radials & more
var/name
/// "use a [whatever] on [frame] to [action_descriptor: "unscrew the panel"]"
var/action_descriptor
/// "you start [x -> 'unscrewing the panel on'][src] ..."
/// add a space after!
/// please don't put pronouns in this, it doesn't get interpolated properly.
var/action_text_leading
/// "you start ... [src][x -> ', removing the screws in the process.']"
/// don't forget punctuation at the end.
/// please don't put pronouns in this, it doesn't get interpolated properly.
var/action_text_trailing
/// stage key this moves us to
/// * [STAGE_DECONSTRUCT] to deconstruct
/// * [STAGE_FINISH] to finish
var/stage
/// direction: [TOOL_DIRECTION_FORWARDS] or [TOOL_DIRECTION_BACKWARDS] or [TOOL_DIRECTION_NEUTRAL]
/// * this is used as a hint for tool graphics
/// * this is used as a hint for other visual / textual feedback
var/direction = TOOL_DIRECTION_NEUTRAL
/// FRAME_REQUEST_TYPE_* define
var/request_type
/// ergo: stack type, item type, tool function, etc. what this is depends on [step_type]
/// limited autodetection is allowed.
var/request
/// * stacks: amount
/// * items: amount; if 0, we just apply the item to it
/// * rest: unused.
var/request_amount
/// * tools: this is cost
/// * rest: unused
var/request_cost
/// time needed to do this
/// note that for tool steps, this might impact the total cost!
var/time = 0 SECONDS
/// requires anchored; if null, defaults to stage.
var/requires_anchored
// todo: request_store: null for default, context key to store under context
/// what to drop when undertaking this step
/// can either be:
/// * /obj/item/stack typepath
/// * /datum/material typepath
/// * /obj/item typepath
/// todo: text for 'drop context key'
var/drop
/// amount to drop
var/drop_amount = 1
/// use custom text?
var/use_custom_feedback = FALSE
/// list of tokens to concat into a string to display when beginning the step.
/// will not be shown if the step is fast enough.
/// null for default.
///
/// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
var/list/visible_text_begin
/// list of tokens to concat into a string to display when beginning the step.
/// will not be shown if the step is fast enough.
/// null for default.
///
/// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
var/list/audible_text_begin
/// list of tokens to concat into a string to display when beginning the step.
/// will not be shown if the step is fast enough.
/// null for default.
///
/// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
var/list/self_text_begin
/// list of tokens to concat into a string to display when finishing the step.
/// null for default.
///
/// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
var/list/visible_text_end
/// list of tokens to concat into a string to display when finishing the step.
/// null for default.
///
/// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
var/list/audible_text_end
/// list of tokens to concat into a string to display when finishing the step.
/// null for default.
///
/// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
var/list/self_text_end
/datum/frame_step/New()
if(isnull(request_type))
// autodetect
var/detected
if(ispath(request, /datum/material))
request_type = FRAME_REQUEST_TYPE_MATERIAL
else if(ispath(request, /obj/item/stack))
request_type = FRAME_REQUEST_TYPE_STACK
else if(ispath(request, /obj/item))
request_type = FRAME_REQUEST_TYPE_ITEM
else if(istext(request))
if(request in global.all_tool_functions)
request_type = FRAME_REQUEST_TYPE_TOOL
detected = TRUE
else if(!detected)
CRASH("failed to autodetect request")
/datum/frame_step/proc/examine(obj/structure/frame2/frame, datum/event_args/actor/actor)
var/rendered_action = action_descriptor || SPAN_BOLD(name)
switch(request_type)
if(FRAME_REQUEST_TYPE_INTERACT)
. = "<b>Interact</b> with [frame] using an empty hand to [rendered_action]."
if(FRAME_REQUEST_TYPE_ITEM)
var/rendered_item
var/obj/item/casted = request
rendered_item = initial(casted.name)
// todo: support amounts
. = "Use an <b>[rendered_item]</b> on [frame] to [rendered_action]."
if(FRAME_REQUEST_TYPE_MATERIAL)
var/rendered_material
var/rendered_stack_name
var/datum/material/resolved = SSmaterials.resolve_material(request)
rendered_material = resolved.display_name
rendered_stack_name = resolved.sheet_plural_name
. = "Apply [request_amount || 0] [rendered_stack_name] of <b>[rendered_material]</b> to [rendered_action]."
if(FRAME_REQUEST_TYPE_PROC)
if(FRAME_REQUEST_TYPE_STACK)
var/rendered_stack
var/rendered_stack_name
var/obj/item/stack/casted = request
rendered_stack = initial(casted.name)
rendered_stack_name = initial(casted.name) || "sheets"
. = "Use [request_amount || 0] [rendered_stack_name] of <b>[rendered_stack]</b> to [rendered_action]."
if(FRAME_REQUEST_TYPE_TOOL)
var/rendered_tool = request
. = "Use a <b>[rendered_tool]</b> to [rendered_action]."
return SPAN_NOTICE(.)
/datum/frame_step/proc/tool_image()
switch(direction)
if(TOOL_DIRECTION_BACKWARDS)
return dyntool_image_backward(request)
if(TOOL_DIRECTION_FORWARDS)
return dyntool_image_forward(request)
if(TOOL_DIRECTION_NEUTRAL)
return dyntool_image_neutral(request)
/**
* checks if a given person, using a given tool, can undertake this step.
*/
/datum/frame_step/proc/valid_interaction(datum/event_args/actor/actor, obj/item/using_tool, datum/frame2/frame_datum, obj/structure/frame2/frame)
switch(request_type)
if(FRAME_REQUEST_TYPE_INTERACT)
return TRUE
if(FRAME_REQUEST_TYPE_ITEM)
return using_tool?.type == request
if(FRAME_REQUEST_TYPE_STACK)
return using_tool?.type == request
if(FRAME_REQUEST_TYPE_PROC)
return FALSE // override this proc
if(FRAME_REQUEST_TYPE_TOOL)
return using_tool?.tool_check(request, actor, frame, TOOL_OP_SILENT)
if(FRAME_REQUEST_TYPE_MATERIAL)
var/obj/item/stack/material/material_stack = using_tool
return istype(material_stack) && (ispath(request, /datum/material)? material_stack.material.type == request : material_stack.material.id == request)
return FALSE
/**
* This proc may assume the item is already type filtered to be the valid item / type / stack / whatever.
* If it isn't, do not istype(); allow the runtime to happen so we can yell at those responsible.
*/
/datum/frame_step/proc/check_consumption(datum/event_args/actor/actor, obj/item/using_tool, datum/frame2/frame_datum, obj/structure/frame2/frame)
switch(request_type)
if(FRAME_REQUEST_TYPE_STACK)
var/obj/item/stack/stack = using_tool
if(stack.amount < request_amount)
return FALSE
return TRUE
if(FRAME_REQUEST_TYPE_MATERIAL)
var/obj/item/stack/material/material_stack = using_tool
if(material_stack.amount < request_amount)
return FALSE
return TRUE
return TRUE
/**
* we take in time_needed, as computed by /datum/frame2.
*
* todo: should we be taking in time needed? this seems like the right option for now.
*/
/datum/frame_step/proc/perform_usage(datum/event_args/actor/actor, obj/item/using_tool, datum/frame2/frame_datum, obj/structure/frame2/frame, time_needed)
switch(request_type)
if(FRAME_REQUEST_TYPE_TOOL)
return frame.use_tool(
request,
using_tool,
actor,
delay = time_needed,
cost = request_cost,
)
else
return do_after(
actor.performer,
time_needed,
frame,
mobility_flags = MOBILITY_CAN_USE,
max_distance = using_tool?.reach || 1,
)
/datum/frame_step/proc/handle_consumption(datum/event_args/actor/actor, obj/item/using_tool, datum/frame2/frame_datum, obj/structure/frame2/frame)
switch(request_type)
if(FRAME_REQUEST_TYPE_ITEM)
if(!actor.performer.attempt_void_item_for_installation(using_tool))
actor.chat_feedback(
SPAN_WARNING("[using_tool] is stuck to your hand!"),
target = frame,
)
return FALSE
qdel(using_tool)
return TRUE
if(FRAME_REQUEST_TYPE_TOOL)
// we do the usage in perform_usainge
return TRUE
if(FRAME_REQUEST_TYPE_STACK)
var/obj/item/stack/stack = using_tool
return stack.use(request_amount)
if(FRAME_REQUEST_TYPE_MATERIAL)
var/obj/item/stack/material/material_stack = using_tool
return material_stack.use(request_amount)
return TRUE
/**
* called before frame is moved to new stage
*/
/datum/frame_step/proc/on_finish(datum/frame2/frame_datum, obj/structure/frame2/frame, datum/event_args/actor/actor, obj/item/using_item)
if(drop)
var/atom/drop_where = frame.drop_location()
if(ispath(drop, /obj/item/stack))
var/safety = 50
var/left = drop_amount
var/obj/item/stack/casted_stack = drop
do
var/dropping = min(left, initial(casted_stack.max_amount))
new drop(drop_where, dropping)
left -= dropping
while(--safety > 0 && left > 0)
else if(ispath(drop, /datum/material))
var/safety = 50
var/left = drop_amount
var/datum/material/resolved_material = SSmaterials.resolve_material(drop)
do
var/dropping = min(left, 50)
// todo: /datum/material based max stacks.
resolved_material.place_sheet(drop_where, dropping)
left -= dropping
while(--safety > 0 && left > 0)
else if(ispath(drop, /obj/item))
for(var/i in 1 to min(50, drop_amount))
new drop(drop_where)
/datum/frame_step/proc/feedback_begin(datum/event_args/actor/actor, datum/frame2/frame_datum, obj/structure/frame2/frame, obj/item/tool, time_needed)
// don't bother if it's that fast
if(time_needed <= 0.5 SECONDS)
return
if(use_custom_feedback)
// custom
actor.visible_feedback(
target = frame,
visible = frame_datum.template_action_string(visible_text_begin, actor.performer, frame, tool),
audible = frame_datum.template_action_string(audible_text_begin, actor.performer, frame, tool),
otherwise_self = frame_datum.template_action_string(self_text_begin, actor.performer, frame, tool),
)
else
// default
standard_feedback_handling(actor, frame_datum, frame, tool, time_needed, TRUE)
/datum/frame_step/proc/feedback_finish(datum/event_args/actor/actor, datum/frame2/frame_datum, obj/structure/frame2/frame, obj/item/tool, time_taken)
if(use_custom_feedback)
// custom
actor.visible_feedback(
target = frame,
visible = frame_datum.template_action_string(visible_text_end, actor.performer, frame, tool),
audible = frame_datum.template_action_string(audible_text_end, actor.performer, frame, tool),
otherwise_self = frame_datum.template_action_string(self_text_end, actor.performer, frame, tool),
)
else
// default
standard_feedback_handling(actor, frame_datum, frame, tool, time_taken, FALSE)
/datum/frame_step/proc/standard_feedback_handling(datum/event_args/actor/actor, datum/frame2/frame_datum, obj/structure/frame2/frame, obj/item/tool, time, beginning)
switch(request_type)
if(FRAME_REQUEST_TYPE_INTERACT)
if(beginning)
actor.visible_feedback(
target = frame,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = "[actor.performer] starts [action_text_leading || "tinkering with "][frame][action_text_trailing || "."]",
audible = "You hear something being tinkered with.",
otherwise_self = "You start [action_text_leading || "tinkering with "][frame][action_text_trailing || "."]",
)
else
actor.visible_feedback(
target = frame,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = "[actor.performer] finishes [action_text_leading || "tinkering with "][frame][action_text_trailing || "."]",
audible = "You hear something being tinkered with.",
otherwise_self = "You finish [action_text_leading || "tinkering with "][frame][action_text_trailing || "."]",
)
if(FRAME_REQUEST_TYPE_ITEM)
if(beginning)
actor.visible_feedback(
target = frame,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = "[actor.performer] starts [action_text_leading || "tinkering with "][frame][action_text_trailing || " using [tool]."]",
audible = "You hear something being tinkered with.",
otherwise_self = "You start [action_text_leading || "tinkering with "][frame][action_text_trailing || " using [tool]."]",
)
else
actor.visible_feedback(
target = frame,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = "[actor.performer] finishes [action_text_leading || "tinkering with "][frame][action_text_trailing || " using [tool]."]",
audible = "You hear something being tinkered with.",
otherwise_self = "You finish [action_text_leading || "tinkering with "][frame][action_text_trailing || " using [tool]."]",
)
if(FRAME_REQUEST_TYPE_MATERIAL, FRAME_REQUEST_TYPE_STACK)
var/name_to_use
if(request_type == FRAME_REQUEST_TYPE_MATERIAL)
var/datum/material/resolved_material = SSmaterials.resolve_material(request)
name_to_use = "[resolved_material.name || resolved_material.display_name] [resolved_material.sheet_plural_name]"
else
var/obj/item/stack/casted_stack = request
name_to_use = casted_stack.name
if(beginning)
actor.visible_feedback(
target = frame,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = "[actor.performer] starts [action_text_leading || "inserting some [name_to_use] into "][frame][action_text_trailing || "."]",
audible = "You hear something being tinkered with.",
otherwise_self = "You start [action_text_leading || "inserting some [name_to_use] into "][frame][action_text_trailing || "."]",
)
else
actor.visible_feedback(
target = frame,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = "[actor.performer] finishes [action_text_leading || "inserting some [name_to_use] into "][frame][action_text_trailing || "."]",
audible = "You hear something being tinkered with.",
otherwise_self = "You finish [action_text_leading || "inserting some [name_to_use] into "][frame][action_text_trailing || "."]",
)
if(FRAME_REQUEST_TYPE_TOOL)
if(beginning)
actor.visible_feedback(
target = frame,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = "[actor.performer] starts [action_text_leading || "tinkering with "][frame][action_text_trailing || " with [tool]."]",
audible = "You hear something being tinkered with.",
otherwise_self = "You start [action_text_leading || "tinkering with "][frame][action_text_trailing || " with [tool]."]",
)
else
actor.visible_feedback(
target = frame,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = "[actor.performer] finishes [action_text_leading || "tinkering with "][frame][action_text_trailing || " with [tool]."]",
audible = "You hear something being tinkered with.",
otherwise_self = "You finish [action_text_leading || "tinkering with "][frame][action_text_trailing || " with [tool]."]",
)
+304
View File
@@ -0,0 +1,304 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
/obj/item/frame2
name = "entity frame"
desc = "Why do you see this? Contact a coder."
icon = 'icons/modules/frames/base.dmi'
icon_state = "item"
item_flags = ITEM_NOBLUDGEON
obj_rotation_flags = OBJ_ROTATION_ENABLED | OBJ_ROTATION_DEFAULTING
/// frame datum - set to typepath for initialization
var/datum/frame2/frame
/// our cached image for hover
var/image/hover_image
/// viewing clients
var/list/client/viewing
/obj/item/frame2/Initialize(mapload, datum/frame2/frame)
. = ..()
if(!isnull(frame))
src.frame = frame
else if(ispath(src.frame))
src.frame = fetch_frame_datum(src.frame)
sync_frame(src.frame)
/obj/item/frame2/Destroy()
for(var/client/C as anything in viewing)
hide_frame_image(C)
return ..()
/obj/item/frame2/proc/sync_frame(datum/frame2/frame)
name = "[frame.name]"
icon = frame.icon
icon_state = "item"
w_class = frame.item_weight_class
weight_volume = frame.item_weight_volume
/obj/item/frame2/examine(mob/user, dist)
. = ..()
if(!frame.item_deploy_requires_tool)
if(frame.wall_frame)
. += SPAN_NOTICE("Use it on a wall, window, or other 'wall-like' object to attach the frame.")
else
. += SPAN_NOTICE("Use it in hand to deploy it in the direction you are looking at.")
if(frame.item_deploy_tool)
. += SPAN_NOTICE("Use a <b>[frame.item_deploy_tool]</b> on it to deploy it in its current direction.")
if(frame.item_recycle_tool)
. += SPAN_NOTICE("Use a <b>[frame.item_recycle_tool]</b> on it to deconstruct it back into material sheets.")
/obj/item/frame2/MouseEntered(location, control, params)
..()
if(!usr?.client)
return
show_frame_image(usr.client)
/obj/item/frame2/MouseExited(location, control, params)
..()
if(!usr?.client)
return
hide_frame_image(usr.client)
/obj/item/frame2/proc/show_frame_image(client/C)
LAZYDISTINCTADD(viewing, C)
C.images += get_hover_image()
RegisterSignal(C, COMSIG_PARENT_QDELETING, PROC_REF(on_client_delete))
/obj/item/frame2/proc/hide_frame_image(client/C)
LAZYREMOVE(viewing, C)
C.images -= get_hover_image()
UnregisterSignal(C, COMSIG_PARENT_QDELETING)
if(!length(viewing))
hover_image = null
/obj/item/frame2/proc/on_client_delete(datum/source)
hide_frame_image(source)
/obj/item/frame2/proc/get_hover_image()
if(isnull(hover_image))
// todo: big/multi-tile frame support
hover_image = image('icons/modules/frames/base.dmi', "arrow")
hover_image.loc = src
hover_image.filters = list(
filter(type = "outline", size = 1, color = "#aaffaa77"),
)
update_hover_image()
return hover_image
/obj/item/frame2/proc/update_hover_image()
if(isnull(hover_image))
return
hover_image.pixel_x = 0
hover_image.pixel_y = 0
switch(dir)
if(NORTH)
hover_image.pixel_y = 12
if(SOUTH)
hover_image.pixel_y = -12
if(EAST)
hover_image.pixel_x = 12
if(WEST)
hover_image.pixel_x = -12
/obj/item/frame2/setDir(ndir)
. = ..()
if(!.)
return
update_hover_image()
/obj/item/frame2/on_attack_self(datum/event_args/actor/e_args)
. = ..()
if(.)
return
if(frame.item_deploy_requires_tool)
e_args.chat_feedback(SPAN_WARNING("[src] requires the use of a [frame.item_deploy_tool] to be deployed!"), src)
return TRUE
var/use_dir = e_args.performer.dir
//! shitcode for wallmounts
if(frame.wall_frame)
use_dir = turn(use_dir, 180)
//! end
if(!can_deploy(e_args, use_dir, e_args.performer.loc))
return TRUE
if(frame.item_deploy_time)
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] starts to deploy [src]."),
audible = SPAN_WARNING("You hear something being assembled."),
otherwise_self = SPAN_WARNING("You start to deploy [src]."),
)
log_construction(e_args, src, "started deploying")
if(!do_after(e_args.performer, frame.item_deploy_time, src, mobility_flags = MOBILITY_CAN_USE))
return TRUE
if(!attempt_deploy(e_args, use_dir = use_dir, use_loc = e_args.performer.loc))
return TRUE
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] deploys [src]."),
audible = SPAN_WARNING("You hear something finish being assembled."),
otherwise_self = SPAN_WARNING("You deploy [src]."),
)
return TRUE
/obj/item/frame2/afterattack(atom/target, mob/user, clickchain_flags, list/params)
if(!frame.wall_frame)
return ..()
if(!user.Adjacent(target))
return ..()
var/use_dir = get_dir(user, target)
var/datum/event_args/actor/e_args = new(user)
if(IS_DIAGONAL(use_dir))
e_args.chat_feedback(SPAN_WARNING("You must be standing cardinally to [target] to attempt a deployment there!"), src)
return CLICKCHAIN_DO_NOT_PROPAGATE
//! shitcode for wallmounts
if(frame.wall_frame)
use_dir = turn(use_dir, 180)
//! end
if(frame.item_deploy_requires_tool)
e_args.chat_feedback(SPAN_WARNING("[src] requires the use of a [frame.item_deploy_tool] to be deployed!"), src)
return CLICKCHAIN_DO_NOT_PROPAGATE
if(!can_deploy(e_args, use_dir, e_args.performer.loc))
return CLICKCHAIN_DO_NOT_PROPAGATE
if(frame.item_deploy_time)
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] starts to deploy [src]."),
audible = SPAN_WARNING("You hear something being assembled."),
otherwise_self = SPAN_WARNING("You start to deploy [src]."),
)
log_construction(e_args, src, "started deploying")
if(!do_after(e_args.performer, frame.item_deploy_time, src, mobility_flags = MOBILITY_CAN_USE))
return CLICKCHAIN_DO_NOT_PROPAGATE
if(!attempt_deploy(e_args, use_dir, use_loc = e_args.performer.loc))
return CLICKCHAIN_DO_NOT_PROPAGATE
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] deploys [src]."),
audible = SPAN_WARNING("You hear something finish being assembled."),
otherwise_self = SPAN_WARNING("You deploy [src]."),
)
return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
/obj/item/frame2/proc/can_deploy(datum/event_args/actor/e_args, use_dir = src.dir, use_loc = src.loc, silent)
if(!isturf(use_loc))
if(!silent)
e_args?.chat_feedback(SPAN_WARNING("[src] must be on the floor to be deployed!"), src)
return FALSE
if(!frame.deployment_checks(src, use_loc, use_dir, e_args))
return FALSE
return TRUE
/obj/item/frame2/proc/attempt_deploy(datum/event_args/actor/e_args, use_dir = src.dir, use_loc = src.loc, silent)
if(!can_deploy(e_args, use_dir, use_loc, silent))
return FALSE
return deploy(e_args, use_dir, use_loc)
/obj/item/frame2/proc/deploy(datum/event_args/actor/e_args, use_dir = src.dir, use_loc = src.loc)
if(!isturf(use_loc))
CRASH("non turf?")
frame.deploy_frame(src, e_args, use_loc, use_dir)
log_construction(e_args, src, "deployed")
/obj/item/frame2/context_query(datum/event_args/actor/e_args)
. = ..()
.["deploy-frame"] = atom_context_tuple("deploy", image(src), 1, MOBILITY_CAN_USE)
/obj/item/frame2/context_act(datum/event_args/actor/e_args, key)
. = ..()
if(.)
return
switch(key)
if("deploy-frame")
if(!e_args.performer.Reachability(src))
e_args.chat_feedback(SPAN_WARNING("You can't reach [src] right now!"), src)
return TRUE
var/use_dir = src.dir
//! shitcode for wallmounts
if(frame.wall_frame)
use_dir = turn(use_dir, 180)
//! end
if(frame.item_deploy_requires_tool)
e_args.chat_feedback(SPAN_WARNING("[src] requires the use of a [frame.item_deploy_tool] to be deployed!"), src)
return CLICKCHAIN_DO_NOT_PROPAGATE
if(!can_deploy(e_args, use_dir, loc))
return CLICKCHAIN_DO_NOT_PROPAGATE
if(frame.item_deploy_time)
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] starts to deploy [src]."),
audible = SPAN_WARNING("You hear something being assembled."),
otherwise_self = SPAN_WARNING("You start to deploy [src]."),
)
log_construction(e_args, src, "started deploying")
if(!do_after(e_args.performer, frame.item_deploy_time, src, mobility_flags = MOBILITY_CAN_USE))
return CLICKCHAIN_DO_NOT_PROPAGATE
attempt_deploy(e_args, use_dir = use_dir)
return TRUE
/obj/item/frame2/drop_products(method, atom/where)
. = ..()
frame.drop_deconstructed_products(method, where, null, list())
/obj/item/frame2/tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint)
if(function == frame.item_recycle_tool)
if(frame.item_recycle_time)
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] starts to recycle [src]."),
audible = SPAN_WARNING("You hear someone starting to disassemble something."),
otherwise_self = SPAN_WARNING("You start to recycle [src]."),
)
log_construction(e_args, src, "started recycling")
if(!use_tool(function, I, e_args, flags, frame.item_recycle_time, frame.item_recycle_cost))
return CLICKCHAIN_DO_NOT_PROPAGATE
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] disassembles [src] back into raw material."),
audible = SPAN_WARNING("You hear something being disassembled back into raw material."),
otherwise_self = SPAN_WARNING("You recycle [src] back into raw material."),
)
log_construction(e_args, src, "recycled")
deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED)
qdel(src)
return CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
if(function == frame.item_deploy_tool)
if(frame.item_deploy_time)
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] starts to deploy [src]."),
audible = SPAN_WARNING("You hear something being assembled."),
otherwise_self = SPAN_WARNING("You start to deploy [src]."),
)
log_construction(e_args, src, "started deploying")
if(!use_tool(function, I, e_args, flags, frame.item_deploy_time, frame.item_deploy_cost))
return CLICKCHAIN_DO_NOT_PROPAGATE
if(!attempt_deploy(e_args))
return CLICKCHAIN_DO_NOT_PROPAGATE
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_WARNING("[e_args.performer] deploys [src]."),
audible = SPAN_WARNING("You hear something finish being assembled."),
otherwise_self = SPAN_WARNING("You deploy [src]."),
)
return CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
return ..()
/obj/item/frame2/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images)
. = list()
if(frame.item_recycle_tool)
LAZYSET(.[frame.item_recycle_tool], "recycle", dyntool_image_neutral(frame.item_recycle_tool))
if(frame.item_deploy_tool)
LAZYSET(.[frame.item_deploy_tool], "deploy", dyntool_image_neutral(frame.item_deploy_tool))
return merge_double_lazy_assoc_list(., ..())
+123
View File
@@ -0,0 +1,123 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
// todo: rename to just 'frame' from 'frame2' after all old frames are converted.
/obj/structure/frame2
name = "construction frame"
desc = "if you see this, yell at coders"
icon = 'icons/modules/frames/base.dmi'
icon_state = "structure"
climb_knockable = TRUE
obj_rotation_flags = OBJ_ROTATION_ENABLED
/// frame datum; set to typepath to default to that on init
var/datum/frame2/frame
/// current stage
var/stage
/// current context
// todo: frame context system proper?
var/list/context
/obj/structure/frame2/Initialize(mapload, dir, datum/frame2/set_frame_to, stage_id, list/context)
var/datum/frame2/applying_frame = fetch_frame_datum(set_frame_to || src.frame)
src.context = context || list()
setDir(dir)
if(!length(applying_frame.stages))
applying_frame.finish_frame(src)
return INITIALIZE_HINT_QDEL
src.stage = stage_id || applying_frame.stage_starting || stack_trace("no stage...")
src.frame = applying_frame
src.frame.apply_to_frame(src)
return ..()
/obj/structure/frame2/proc/set_context(key, value)
LAZYSET(context, key, value)
/obj/structure/frame2/proc/get_context(key)
return context?[key]
/obj/structure/frame2/update_icon_state()
icon_state = "structure[frame.has_structure_stage_states? "-[stage]" : ""]"
return ..()
/obj/structure/frame2/update_overlays()
. = ..()
. += frame.get_overlays(src)
/obj/structure/frame2/update_name()
var/datum/frame_stage/frame_stage = frame.stages[stage]
name = "[frame_stage.name_prepend && "[frame_stage.name_prepend] "][frame_stage.name_override || frame.name][frame_stage.name_append && " [frame_stage.name_append]"]"
return ..()
/obj/structure/frame2/drop_products(method, atom/where)
. = ..()
frame.drop_deconstructed_products(method, where, stage, context)
/obj/structure/frame2/examine(mob/user, dist)
. = ..()
frame.on_examine(src, new /datum/event_args/actor(user), ., dist)
/obj/structure/frame2/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args)
// please don't hurt me lohikar
. = list()
if(frame.freely_anchorable && frame.anchor_tool)
.[frame.anchor_tool] = list(
"[anchored? "unanchor" : "anchor"]" = anchored? dyntool_image_backward(frame.anchor_tool) : dyntool_image_forward(frame.anchor_tool),
)
. = merge_double_lazy_assoc_list(frame.on_tool_query(src, I, e_args), .)
. = merge_double_lazy_assoc_list(., ..())
/obj/structure/frame2/proc/still_anchored(anchorvalue)
return anchored == anchorvalue
/obj/structure/frame2/tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint)
if(frame.freely_anchorable && frame.anchor_tool == function)
var/datum/frame_stage/current_stage = frame.stages[stage]
// if anchored, and either: current stage allow_unanchor is set to FALSE (not null) OR it's to null
// and the frame is requiring anchored and the frame's not on its starting stage, do not allow unanchoring.
if(anchored && (isnull(current_stage.allow_unanchor)? (frame.requires_anchored? frame.stage_starting != stage : FALSE) : !current_stage.allow_unanchor))
e_args.chat_feedback(
SPAN_WARNING("[src] cannot be unanchored while in this stage!"),
target = src,
)
return CLICKCHAIN_DO_NOT_PROPAGATE
if(frame.anchor_time)
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_NOTICE("[e_args.performer] starts to [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."),
otherwise_self = SPAN_NOTICE("You begin to [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."),
)
log_construction(e_args, src, "started [anchored? "unanchoring" : "anchoring"]")
if(!use_tool(function, I, e_args, flags, frame.anchor_time))
return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
set_anchored(!anchored)
log_construction(e_args, src, "[anchored? "anchored" : "unanchored"]")
e_args.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_NOTICE("[e_args.performer] [anchored? "bolts" : "unbolts"] [src] [anchored? "to" : "from"] the floor."),
audible = SPAN_WARNING("You hear a set of bolts being [anchored? "fastened" : "undone"]."),
otherwise_self = SPAN_NOTICE("You [anchored? "bolt" : "unbolt"] [src] [anchored? "to" : "from"] the floor."),
)
return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
if(frame.on_tool(src, I, e_args, function, flags, hint))
// todo: did something might be sent even if we .. didn't do anything successfully.
return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
return ..()
/obj/structure/frame2/on_attack_hand(datum/event_args/actor/clickchain/e_args)
. = ..()
if(.)
return
if(frame.on_interact(src, e_args))
return TRUE
/obj/structure/frame2/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier)
if(user.a_intent == INTENT_HARM)
return ..()
if(frame.on_item(src, I, new /datum/event_args/actor/clickchain(user)))
return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
return ..()
+50
View File
@@ -0,0 +1,50 @@
AUTO_FRAME_DATUM(/datum/frame2/apc, apc, 'icons/machinery/power/apc.dmi')
/datum/frame2/apc
name = "APC frame"
material_cost = 2
// we immediately form the entity on place; no stages
stages = list()
wall_frame = TRUE
wall_pixel_x = 24
wall_pixel_y = 24
/datum/frame2/apc/instance_product(obj/structure/frame/frame)
return new /obj/machinery/power/apc(frame.loc, frame.dir, TRUE)
/datum/frame2/apc/valid_location(obj/entity, turf/location, dir, datum/event_args/actor/actor, silent)
if(!istype(location, /turf/simulated))
if(!silent)
actor.chat_feedback(
SPAN_WARNING("[entity] must be placed on normal flooring."),
target = entity,
)
return FALSE
var/area/area = location.loc
if(!area)
if(!silent)
actor.chat_feedback(
SPAN_WARNING("Missing area. Report this to coders with a screenshot of your screen. How did you get here?"),
target = entity,
)
return FALSE
if(!area.requires_power || area.always_unpowered)
if(!silent)
actor.chat_feedback(
SPAN_WARNING("[location] doesn't require power, or is externally powered."),
target = entity,
)
return FALSE
if(area.get_apc())
if(!silent)
actor.chat_feedback(
SPAN_WARNING("[location] is part of an area that already has an APC."),
target = entity,
)
return FALSE
for(var/obj/machinery/power/terminal/T in location)
actor.chat_feedback(
SPAN_WARNING("There is another powernet terminal here."),
target = entity,
)
return FALSE
return ..()
+85
View File
@@ -0,0 +1,85 @@
AUTO_FRAME_DATUM(/datum/frame2/fire_alarm, fire_alarm, 'icons/machinery/fire_alarm.dmi')
/datum/frame2/fire_alarm
name = "fire alarm frame"
wall_pixel_y = 24
wall_pixel_x = 24
wall_frame = TRUE
material_cost = 2
stages = list(
"frame" = /datum/frame_stage{
steps = list(
/datum/frame_step{
request = /obj/item/circuitboard/firealarm;
name = "insert circuit";
stage = "circuit";
direction = TOOL_DIRECTION_FORWARDS;
},
/datum/frame_step{
request = TOOL_WRENCH;
time = 1 SECONDS;
name = "detach frame";
stage = FRAME_STAGE_DECONSTRUCT;
direction = TOOL_DIRECTION_BACKWARDS;
},
);
descriptor = "is currently an empty shell.";
},
"circuit" = /datum/frame_stage{
steps = list(
/datum/frame_step{
request = TOOL_SCREWDRIVER;
name = "secure circuit";
stage = "secured";
direction = TOOL_DIRECTION_FORWARDS;
},
/datum/frame_step{
request_type = FRAME_REQUEST_TYPE_INTERACT;
name = "remove circuit";
stage = "frame";
drop = /obj/item/circuitboard/firealarm;
direction = TOOL_DIRECTION_BACKWARDS;
},
);
descriptor = "has the circuit installed";
},
"secured" = /datum/frame_stage{
steps = list(
/datum/frame_step{
request = /obj/item/stack/cable_coil;
name = "unsecure circuit";
request_amount = 1;
stage = "wired";
direction = TOOL_DIRECTION_FORWARDS;
},
/datum/frame_step{
request = TOOL_SCREWDRIVER;
name = "unsecure circuit";
stage = "circuit";
direction = TOOL_DIRECTION_FORWARDS;
},
);
descriptor = "has the circuit secured.";
},
"wired" = /datum/frame_stage{
steps = list(
/datum/frame_step{
request = TOOL_SCREWDRIVER;
name = "secure panel";
stage = FRAME_STAGE_FINISH;
direction = TOOL_DIRECTION_FORWARDS;
},
/datum/frame_step{
request = TOOL_WIRECUTTER;
name = "remove wiring";
stage = "secured";
direction = TOOL_DIRECTION_BACKWARDS;
},
);
descriptor = "has its wiring installed.";
name_append = "(wired)";
},
)
/datum/frame2/fire_alarm/instance_product(obj/structure/frame/frame)
return new /obj/machinery/fire_alarm(frame.loc, frame.dir)
+69
View File
@@ -0,0 +1,69 @@
AUTO_FRAME_DATUM(/datum/frame2/solar_panel, solar_panel, 'icons/machinery/power/solar/panel.dmi')
/datum/frame2/solar_panel
name = "solar assembly"
material_buildable = FALSE
has_density = TRUE
freely_anchorable = TRUE
stages = list(
"frame" = /datum/frame_stage{
steps = list(
/datum/frame_step{
name = "finish panel";
request = /datum/material/glass;
request_amount = 1;
direction = TOOL_DIRECTION_FORWARDS;
stage = FRAME_STAGE_FINISH;
},
);
},
)
has_structure_stage_states = FALSE
/datum/frame2/solar_panel/on_item(obj/structure/frame2/frame, obj/item/item, datum/event_args/actor/clickchain/click)
. = ..()
if(.)
return
if(istype(item, /obj/item/tracker_electronics))
if(frame.get_context("tracker"))
click.chat_feedback(
SPAN_WARNING("[frame] already has tracker electronics installed."),
target = frame,
)
return TRUE
if(!click.performer.attempt_consume_item_for_construction(item))
return TRUE
click.visible_feedback(
target = src,
range = MESSAGE_RANGE_CONSTRUCTION,
visible = SPAN_NOTICE("[click.performer] inserts [item] into [frame].")
)
// todo: context system proper?
frame.set_context("tracker", TRUE)
return TRUE
/datum/frame2/solar_panel/instance_product(obj/structure/frame2/frame)
// todo: context system proper?
if(frame.get_context("tracker"))
return new /obj/machinery/power/tracker(frame.loc)
else
return new /obj/machinery/power/solar(frame.loc)
/datum/frame2/solar_panel/instruction_special(obj/structure/frame2/frame, datum/event_args/actor/clickchain/click)
. = ..()
// todo: context system proper?
if(!frame.get_context("tracker"))
. += SPAN_NOTICE("Add <b>tracker electronics</b> to make this a solar tracker assembly.")
else
. += SPAN_NOTICE("This assembly is wired to be a <b>solar tracker</b>.")
/obj/structure/frame2/solar_panel/anchored
anchored = TRUE
/obj/structure/frame2/solar_panel/tracker
context = list(
"tracker" = TRUE,
)
/obj/structure/frame2/solar_panel/tracker/anchored
anchored = TRUE