mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-22 09:17:07 +01:00
@@ -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(., "")
|
||||
@@ -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]")
|
||||
@@ -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]."]",
|
||||
)
|
||||
@@ -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(., ..())
|
||||
@@ -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 ..()
|
||||
@@ -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 ..()
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user