diff --git a/citadel.dme b/citadel.dme index 2208c82566d..7f87b372bd7 100644 --- a/citadel.dme +++ b/citadel.dme @@ -46,6 +46,7 @@ #include "code\__DEFINES\dna.dm" #include "code\__DEFINES\event_args.dm" #include "code\__DEFINES\fonts.dm" +#include "code\__DEFINES\frames.dm" #include "code\__DEFINES\gamemode.dm" #include "code\__DEFINES\holidays.dm" #include "code\__DEFINES\holomap.dm" @@ -852,6 +853,7 @@ #include "code\datums\recipe\material_recipes\furniture.dm" #include "code\datums\recipe\material_recipes\structures.dm" #include "code\datums\recipe\material_recipes\tools.dm" +#include "code\datums\recipe\stack_recipes\frame.dm" #include "code\datums\recipe\stack_recipes\misc.dm" #include "code\datums\recipe\stack_recipes\structures.dm" #include "code\datums\repositories\ammomaterial.dm" @@ -1539,7 +1541,6 @@ #include "code\game\objects\effects\temporary_visuals\projectiles\projectile_effects.dm" #include "code\game\objects\effects\temporary_visuals\projectiles\tracer.dm" #include "code\game\objects\items\antag_spawners.dm" -#include "code\game\objects\items\apc_frame.dm" #include "code\game\objects\items\balls.dm" #include "code\game\objects\items\bells.dm" #include "code\game\objects\items\blueprints.dm" @@ -2000,8 +2001,6 @@ #include "code\game\turfs\turf_flick_animations.dm" #include "code\game\turfs\turf_movement.dm" #include "code\game\turfs\unsimulated.dm" -#include "code\game\turfs\initialization\init.dm" -#include "code\game\turfs\initialization\maintenance.dm" #include "code\game\turfs\simulated\sky.dm" #include "code\game\turfs\simulated\floor\floor.dm" #include "code\game\turfs\simulated\floor\floor_acts.dm" @@ -2763,6 +2762,14 @@ #include "code\modules\food\machinery\appliance\grill.dm" #include "code\modules\food\machinery\appliance\oven.dm" #include "code\modules\food\structures\icecream_cart.dm" +#include "code\modules\frames\frame.dm" +#include "code\modules\frames\frame_stage.dm" +#include "code\modules\frames\frame_step.dm" +#include "code\modules\frames\item.dm" +#include "code\modules\frames\structure.dm" +#include "code\modules\frames\types\apc.dm" +#include "code\modules\frames\types\fire_alarm.dm" +#include "code\modules\frames\types\solar_panel.dm" #include "code\modules\gamemaster\controller.dm" #include "code\modules\gamemaster\defines.dm" #include "code\modules\gamemaster\helpers.dm" @@ -4186,9 +4193,7 @@ #include "code\modules\power\power.dm" #include "code\modules\power\powernet.dm" #include "code\modules\power\powersupply.dm" -#include "code\modules\power\solar.dm" #include "code\modules\power\terminal.dm" -#include "code\modules\power\tracker.dm" #include "code\modules\power\turbine.dm" #include "code\modules\power\antimatter\containment_jar.dm" #include "code\modules\power\antimatter\control.dm" @@ -4237,6 +4242,9 @@ #include "code\modules\power\smes\coil.dm" #include "code\modules\power\smes\smes.dm" #include "code\modules\power\smes\smes_construction.dm" +#include "code\modules\power\solar\solar.dm" +#include "code\modules\power\solar\solar_control.dm" +#include "code\modules\power\solar\tracker.dm" #include "code\modules\power\supermatter\setup_supermatter.dm" #include "code\modules\power\supermatter\supermatter.dm" #include "code\modules\power\tesla\coil.dm" diff --git a/code/__DEFINES/_flags/obj_flags.dm b/code/__DEFINES/_flags/obj_flags.dm index 8afdce1157d..d18c75ed843 100644 --- a/code/__DEFINES/_flags/obj_flags.dm +++ b/code/__DEFINES/_flags/obj_flags.dm @@ -20,7 +20,9 @@ /// Materials have been initialized #define OBJ_MATERIAL_INITIALIZED (1<<9) /// no sculpting -#define OBJ_NO_SCULPTING (1<<10) +#define OBJ_NO_SCULPTING (1<<10) +/// wall-mounted; facing *towards* the wall we're mounted on (e.g. be NORTH if we're shifted north) +#define OBJ_WALL_MOUNTED (1<<11) DEFINE_BITFIELD(obj_flags, list( BITFIELD(OBJ_EMAGGED), @@ -34,6 +36,7 @@ DEFINE_BITFIELD(obj_flags, list( BITFIELD(OBJ_MATERIAL_PARTS_MODIFIED), BITFIELD(OBJ_MATERIAL_INITIALIZED), BITFIELD(OBJ_NO_SCULPTING), + BITFIELD_NAMED("Wall Mounted", OBJ_WALL_MOUNTED), )) //* /obj/var/obj_rotation_flags diff --git a/code/__DEFINES/frames.dm b/code/__DEFINES/frames.dm new file mode 100644 index 00000000000..cdf493afb4e --- /dev/null +++ b/code/__DEFINES/frames.dm @@ -0,0 +1,76 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2023 Citadel Station developers. *// + +//* request types + +/// use x of a specific stack type +/// request is typepath of stack +/// amount is stack amount used +#define FRAME_REQUEST_TYPE_STACK "!STACK" +/// use x of a material +/// request is typepath of material +/// amount is stack amount used +#define FRAME_REQUEST_TYPE_MATERIAL "!MATERIAL" +/// use x of an item +/// request is typepath of item +/// amount is items used +#define FRAME_REQUEST_TYPE_ITEM "!ITEM" +/// rely on proc +#define FRAME_REQUEST_TYPE_PROC "!PROC" +/// interact with hand +/// amount is time needed +#define FRAME_REQUEST_TYPE_INTERACT "!INTERACT" +/// request is tool function +/// amount is time needed +#define FRAME_REQUEST_TYPE_TOOL "!TOOL" + +//* special stages + +/// deconstruct; usually default behavior on reverting past first stage +#define FRAME_STAGE_DECONSTRUCT "!DEL" +/// finish; usually default behavior on progressing past last stage +#define FRAME_STAGE_FINISH "!FIN" + +//* special contexts + +/// automatic context for storing items put into the frame +#define FRAME_CONTEXT_FOR_STAGE(N) "![N]-parts" + +//* special things + +#define AUTO_FRAME_DATUM(TYPEPATH, EXTENSION, ICON) \ +/obj/structure/frame2/##EXTENSION { \ + icon = ##ICON; \ + frame = ##TYPEPATH; \ +} \ +/obj/structure/frame2/##EXTENSION/unanchored { \ + anchored = FALSE; \ +} \ +/obj/item/frame2/##EXTENSION { \ + icon = ##ICON; \ + frame = ##TYPEPATH; \ +} \ +##TYPEPATH { \ + icon = ##ICON; \ +} + +#define AUTO_FRAME_DATUM_UNANCHORABLE(TYPEPATH, EXTENSION, ICON) \ +AUTO_FRAME_DATUM(TYPEPATH, EXTENSION, ICON); \ +/obj/structure/frame2/##EXTENSION/unanchored { \ + anchored = FALSE; \ +} + +//* text template fragments + +/// "firstname lastname" +#define FRAME_TEXT_TOKEN_PERFORMER "!performer" +/// "the fire alarm frame" +#define FRAME_TEXT_TOKEN_FRAME "!frame" +/// "the fire alarm electronics" or "the welding torch" +#define FRAME_TEXT_TOKEN_TOOL "!tool" +/// pronoun for 'their' +#define FRAME_TEXT_TOKEN_THEIR "!their" +/// pronoun for 'them' +#define FRAME_TEXT_TOKEN_THEM "!them" +/// pronoun for 'they're' (he's, she's, etc) +#define FRAME_TEXT_TOKEN_THEYRE "!theyre" diff --git a/code/__DEFINES/tools/functionality.dm b/code/__DEFINES/tools/functionality.dm index bdeeefc443d..a3c33e90bc7 100644 --- a/code/__DEFINES/tools/functionality.dm +++ b/code/__DEFINES/tools/functionality.dm @@ -23,6 +23,31 @@ #define TOOL_GLASS_CUT "glasskit" #define TOOL_BONESET "bonesetter" +GLOBAL_REAL_VAR(all_tool_functions) = list( + // engineering + TOOL_CROWBAR, + TOOL_MULTITOOL, + TOOL_SCREWDRIVER, + TOOL_WIRECUTTER, + TOOL_WRENCH, + TOOL_WELDER, + TOOL_ANALYZER, + // mining + TOOL_MINING, + TOOL_SHOVEL, + // surgery + TOOL_RETRACTOR, + TOOL_HEMOSTAT, + TOOL_CAUTERY, + TOOL_DRILL, + TOOL_SCALPEL, + TOOL_SAW, + // glassworking + TOOL_BLOW, + TOOL_GLASS_CUT, + TOOL_BONESET, +) + /// Yes, this is a real global. No, you shouldn't touch this for no reason. /// Add tools to this when they get states in the default icon file for: /// - neutral (no append) @@ -31,7 +56,7 @@ GLOBAL_REAL_VAR(_dyntool_image_states) = list( null = "unknown", TOOL_CROWBAR = "crowbar", - TOOL_SCREWDRIVER = "screwdriver" + TOOL_SCREWDRIVER = "screwdriver", ) //? Tool usage flags @@ -57,3 +82,9 @@ GLOBAL_REAL_VAR(_dyntool_image_states) = list( #define TOOL_LOCKING_STATIC 2 /// automatically, if we only have one dynamic behavior, use that #define TOOL_LOCKING_AUTO 3 + +//? Tool directions - used as hints. + +#define TOOL_DIRECTION_FORWARDS "forwards" +#define TOOL_DIRECTION_BACKWARDS "backwards" +#define TOOL_DIRECTION_NEUTRAL "neutral" diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 1cda55518d3..df7c75ef255 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1091,7 +1091,7 @@ var/list/WALLITEMS = list( /obj/machinery/power/apc, /obj/machinery/air_alarm, /obj/item/radio/intercom, /obj/structure/frame, /obj/structure/extinguisher_cabinet, /obj/structure/reagent_dispensers/peppertank, /obj/machinery/status_display, /obj/machinery/requests_console, /obj/machinery/light_switch, /obj/structure/sign, - /obj/machinery/newscaster, /obj/machinery/firealarm, /obj/structure/noticeboard, /obj/machinery/button/remote, + /obj/machinery/newscaster, /obj/machinery/fire_alarm, /obj/structure/noticeboard, /obj/machinery/button/remote, /obj/machinery/computer/security/telescreen, /obj/machinery/embedded_controller/radio, /obj/item/storage/secure/safe, /obj/machinery/door_timer, /obj/machinery/flasher, /obj/machinery/keycard_auth, /obj/structure/mirror, /obj/structure/fireaxecabinet, /obj/machinery/computer/security/telescreen/entertainment, diff --git a/code/datums/event_args/actor.dm b/code/datums/event_args/actor.dm index c5186ef2ed9..9617fc0c535 100644 --- a/code/datums/event_args/actor.dm +++ b/code/datums/event_args/actor.dm @@ -11,6 +11,8 @@ src.performer = performer src.initiator = isnull(initiator)? performer : initiator +// todo: reowrk these awful ass feedback/message procs wtf + /datum/event_args/actor/proc/chat_feedback(msg, atom/target) performer.action_feedback(msg, target) if(performer != initiator) diff --git a/code/datums/event_args/clickchain.dm b/code/datums/event_args/clickchain.dm index 4f350e9cae3..6e2a45bf2d9 100644 --- a/code/datums/event_args/clickchain.dm +++ b/code/datums/event_args/clickchain.dm @@ -1,12 +1,14 @@ /** - * used to hold data about a click action + * used to hold data about a click (melee/ranged/other) action + * + * the click may be real or fake. */ /datum/event_args/actor/clickchain - /// a_intent + /// optional: a_intent var/intent - /// click params + /// optional: click params var/list/params - /// target atom + /// optional: target atom var/atom/target /datum/event_args/actor/clickchain/New(mob/performer, mob/initiator, atom/target, intent, list/params) diff --git a/code/datums/recipe/stack_recipe.dm b/code/datums/recipe/stack_recipe.dm index 7c90f169bb9..d037f13d52d 100644 --- a/code/datums/recipe/stack_recipe.dm +++ b/code/datums/recipe/stack_recipe.dm @@ -8,10 +8,10 @@ * * why? because this is easier to regex later if it turns out silicons code design(tm) was terrible */ -/proc/create_stack_recipe_datum(name, product, cost, amount = 1, sanity_checks = TRUE, time = 0, recipe_type = /datum/stack_recipe, category, exclusitivity) +/proc/create_stack_recipe_datum(name, product, cost, amount = 1, sanity_checks = TRUE, time = 0, recipe_type = /datum/stack_recipe, category, exclusitivity, list/recipe_args) // check this isn't being misused ASSERT(!ispath(recipe_type, /datum/stack_recipe/material)) - var/datum/stack_recipe/creating = new recipe_type + var/datum/stack_recipe/creating = isnull(recipe_args)? (new recipe_type) : (new recipe_type(arglist(recipe_args))) creating.name = name creating.category = category creating.result_type = product diff --git a/code/datums/recipe/stack_recipes/frame.dm b/code/datums/recipe/stack_recipes/frame.dm new file mode 100644 index 00000000000..8924dbe469b --- /dev/null +++ b/code/datums/recipe/stack_recipes/frame.dm @@ -0,0 +1,14 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/datum/stack_recipe/frame + var/frame_id + +/datum/stack_recipe/frame/New(frame_id) + src.frame_id = frame_id + ..() + +/datum/stack_recipe/frame/make(atom/where, amount, obj/item/stack/stack, mob/user, silent, use_dir, list/created = list()) + for(var/i in 1 to min(amount, 10)) + created += new /obj/item/frame2(where, frame_id) + return TRUE diff --git a/code/datums/recipe/stack_recipes/structures.dm b/code/datums/recipe/stack_recipes/structures.dm index 3e03f2a1d5c..ce642d744f4 100644 --- a/code/datums/recipe/stack_recipes/structures.dm +++ b/code/datums/recipe/stack_recipes/structures.dm @@ -24,7 +24,7 @@ /datum/stack_recipe/railing/make(atom/where, amount, obj/item/stack/stack, mob/user, silent, use_dir = user?.dir, list/created = list()) if(isnull(use_dir)) - return + return FALSE for(var/i in 1 to amount) var/obj/structure/railing/built = new(where, TRUE) built.setDir(use_dir) diff --git a/code/game/area/Ship_Station_Areas.dm b/code/game/area/Ship_Station_Areas.dm index 5a428eeeffa..24fdc7ec3c1 100644 --- a/code/game/area/Ship_Station_Areas.dm +++ b/code/game/area/Ship_Station_Areas.dm @@ -200,10 +200,8 @@ /area/main_map/maintenance area_flags = AREA_RAD_SHIELDED sound_env = TUNNEL_ENCLOSED - turf_initializer = new /datum/turf_initializer/maintenance() ambience = AMBIENCE_MAINTENANCE - /area/main_map/maintenance/engineering name = "Engineering Maintenance" icon_state = "maint_engineering" diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index 1571fb01b9e..033a449212d 100644 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -763,7 +763,6 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/maintenance area_flags = AREA_RAD_SHIELDED sound_env = TUNNEL_ENCLOSED - turf_initializer = new /datum/turf_initializer/maintenance() ambience = AMBIENCE_MAINTENANCE /area/maintenance/aft @@ -2667,7 +2666,6 @@ NOTE: there are two lists of areas in the end of this file: centcom and station name = "\improper Research Outpost Maintenance" area_flags = AREA_RAD_SHIELDED sound_env = TUNNEL_ENCLOSED - turf_initializer = new /datum/turf_initializer/maintenance() ambience = AMBIENCE_MAINTENANCE /area/rnd/outpost/underground diff --git a/code/game/area/rift_areas.dm b/code/game/area/rift_areas.dm index 5d2e26a5e76..df539b26a18 100644 --- a/code/game/area/rift_areas.dm +++ b/code/game/area/rift_areas.dm @@ -434,7 +434,6 @@ name = "\improper Outpost Maintenance" area_flags = AREA_RAD_SHIELDED sound_env = TUNNEL_ENCLOSED - turf_initializer = new /datum/turf_initializer/maintenance() ambience = AMBIENCE_MAINTENANCE /area/outpost/mining_main/outpost/maintenance/south diff --git a/code/game/atoms/appearance.dm b/code/game/atoms/appearance.dm index 2713e8e2537..4078f0f1a76 100644 --- a/code/game/atoms/appearance.dm +++ b/code/game/atoms/appearance.dm @@ -1,3 +1,5 @@ +// todo: rethink everything about how to do signals for these. + /** * Updates the appearence of the icon * diff --git a/code/game/machinery/_frame.dm b/code/game/machinery/_frame.dm index 171763dd9dd..248dd4a0b92 100644 --- a/code/game/machinery/_frame.dm +++ b/code/game/machinery/_frame.dm @@ -1,6 +1,8 @@ /var/global/list/construction_frame_wall /var/global/list/construction_frame_floor + + // TODO: MAKE FRAMES NOT AWFUL // WHY IN THE NAME OF THE SEVEN HELLS ARE THEY NOT STATIC DATUMS AT THIS POINT?! // WHY ARE VARIABLES HARD TYPECHECKED? @@ -384,10 +386,7 @@ circuit.after_construct(new_machine) for(var/obj/O in components) - if(circuit.contain_parts) - O.loc = new_machine - else - O.loc = null + O.loc = new_machine new_machine.component_parts += O circuit.loc = null diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index bf90d6cc3e5..2ad91d1ff30 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -161,6 +161,7 @@ var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_SET_MACHINE +// todo: from_frame? arg for frame to pass in context.. /obj/machinery/Initialize(mapload, newdir) if(newdir) setDir(newdir) diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 722f0a30da3..b6ff02a2f0b 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -113,16 +113,10 @@ src.circuit.construct(new_machine) for(var/obj/O in src) - if(circuit.contain_parts) // things like disposal don't want their parts in them - O.loc = new_machine - else - O.loc = null + O.loc = new_machine new_machine.component_parts += O - if(circuit.contain_parts) - circuit.loc = new_machine - else - circuit.loc = null + circuit.loc = new_machine new_machine.RefreshParts() qdel(src) diff --git a/code/game/machinery/fire_alarm.dm b/code/game/machinery/fire_alarm.dm index 101465e9cf4..c700d38d22b 100644 --- a/code/game/machinery/fire_alarm.dm +++ b/code/game/machinery/fire_alarm.dm @@ -8,21 +8,21 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm, 21) icon = 'icons/obj/firealarm.dmi' icon_state = "casing" plane = TURF_PLANE - layer = ABOVE_TURF_LAYER - var/detecting = TRUE - var/working = TRUE - var/time = 10 - var/timing = 0 - var/lockdownbyai = FALSE + layer = ABOVE_WINDOW_LAYER anchored = TRUE use_power = TRUE idle_power_usage = 2 active_power_usage = 6 power_channel = ENVIRON - var/last_process = 0 panel_open = FALSE - var/seclevel circuit = /obj/item/circuitboard/firealarm + var/detecting = TRUE + var/working = TRUE + var/time = 10 + var/timing = 0 + var/lockdownbyai = FALSE + var/last_process = 0 + var/seclevel /// If the alarms from this machine are visible on consoles. var/alarms_hidden = FALSE @@ -34,7 +34,7 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm/alarms_hidden, 21) dir = turn(dir, -preloader.turn_angle) return FALSE -/obj/machinery/fire_alarm/Initialize(mapload) +/obj/machinery/fire_alarm/Initialize(mapload, dir = src.dir) . = ..() if(z in (LEGACY_MAP_DATUM).contact_levels) set_security_level(GLOB.security_level ? get_security_level() : "green") @@ -155,7 +155,6 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm/alarms_hidden, 21) return alarm() - return /obj/machinery/fire_alarm/process()//Note: this processing was mostly phased out due to other code, and only runs when needed if(machine_stat & (NOPOWER|BROKEN)) @@ -268,117 +267,8 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm/alarms_hidden, 21) fire_alarm.triggerAlarm(loc, FA, duration, hidden = alarms_hidden) update_icon() playsound(src.loc, 'sound/machines/airalarm.ogg', 25, 0, 4) - return /obj/machinery/fire_alarm/proc/set_security_level(var/newlevel) if(seclevel != newlevel) seclevel = newlevel update_icon() - -/* -FIRE ALARM CIRCUIT -Just a object used in constructing fire alarms -/obj/item/firealarm_electronics - name = "fire alarm electronics" - icon = 'icons/obj/doors/door_assembly.dmi' - icon_state = "door_electronics" - desc = "A circuit. It has a label on it, it says \"Can handle heat levels up to 40 degrees celsius!\"" - w_class = WEIGHT_CLASS_SMALL - materials_base = list(MAT_STEEL = 50, MAT_GLASS = 50) -*/ -/obj/machinery/partyalarm - name = "\improper PARTY BUTTON" - desc = "Cuban Pete is in the house!" - icon = 'icons/obj/monitors.dmi' - icon_state = "fire0" - var/detecting = TRUE - var/working = TRUE - var/time = 10 - var/timing = 0 - var/lockdownbyai = FALSE - anchored = TRUE - use_power = TRUE - idle_power_usage = 2 - active_power_usage = 6 - -/obj/machinery/partyalarm/attack_hand(mob/user, list/params) - if(user.stat || machine_stat & (NOPOWER|BROKEN)) - return - - user.machine = src - var/area/A = get_area(src) - ASSERT(isarea(A)) - var/d1 - var/d2 - if(istype(user, /mob/living/carbon/human) || istype(user, /mob/living/silicon/ai)) - - if(A.party) - d1 = "No Party :(" - else - d1 = "PARTY!!!" - if(timing) - d2 = "Stop Time Lock" - else - d2 = "Initiate Time Lock" - var/second = time % 60 - var/minute = (time - second) / 60 - var/dat = "Party Button [d1]\n
\nTimer System: [d2]
\nTime Left: [(minute ? "[minute]:" : null)][second] - - + +\n
" - - user << browse(dat, "window=partyalarm") - onclose(user, "partyalarm") - else - if(A.fire) - d1 = "[stars("No Party :(")]" - else - d1 = "[stars("PARTY!!!")]" - if(timing) - d2 = "[stars("Stop Time Lock")]" - else - d2 = "[stars("Initiate Time Lock")]" - var/second = time % 60 - var/minute = (time - second) / 60 - var/dat = "[stars("Party Button")] [d1]\n
\nTimer System: [d2]
\nTime Left: [(minute ? "[minute]:" : null)][second] - - + +\n
" - - user << browse(dat, "window=partyalarm") - onclose(user, "partyalarm") - return - -/obj/machinery/partyalarm/proc/reset() - if(!(working)) - return - var/area/A = get_area(src) - ASSERT(isarea(A)) - A.partyreset() - return - -/obj/machinery/partyalarm/proc/alarm() - if(!(working)) - return - var/area/A = get_area(src) - ASSERT(isarea(A)) - A.partyalert() - return - -/obj/machinery/partyalarm/Topic(href, href_list) - ..() - if(usr.stat || machine_stat & (BROKEN|NOPOWER)) - return - if((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon/ai))) - usr.machine = src - if(href_list["reset"]) - reset() - else if(href_list["alarm"]) - alarm() - else if(href_list["time"]) - timing = text2num(href_list["time"]) - else if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 0), 120) - updateUsrDialog() - - add_fingerprint(usr) - else - usr << browse(null, "window=partyalarm") - return - return diff --git a/code/game/objects/effects/debris/cleanable/blood.dm b/code/game/objects/effects/debris/cleanable/blood.dm index f87cbc9d534..095f4637469 100644 --- a/code/game/objects/effects/debris/cleanable/blood.dm +++ b/code/game/objects/effects/debris/cleanable/blood.dm @@ -184,9 +184,8 @@ var/global/list/image/splatter_cache=list() if(random_icon_states.len) for(var/obj/effect/debris/cleanable/blood/writing/W in loc) random_icon_states.Remove(W.icon_state) - if(!LAZYLEN(random_icon_states))//If all iconstates are already in used by someone else on our tile, delete yourself - qdel(src) - return + if(!length(random_icon_states)) + return INITIALIZE_HINT_QDEL icon_state = pick(random_icon_states) else icon_state = "writing1" diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 8f96673b39a..94bf8beecc7 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -931,7 +931,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. if(!isnull(obj_storage) && obj_storage.allow_quick_empty && obj_storage.allow_quick_empty_via_attack_self) var/turf/turf = get_turf(e_args.performer) obj_storage.auto_handle_interacted_mass_dumping(e_args, turf) - return CLICKCHAIN_DO_NOT_PROPAGATE + return TRUE return FALSE /** diff --git a/code/game/objects/items/apc_frame.dm b/code/game/objects/items/apc_frame.dm deleted file mode 100644 index a906bc6ddec..00000000000 --- a/code/game/objects/items/apc_frame.dm +++ /dev/null @@ -1,44 +0,0 @@ -// APC HULL - -/obj/item/frame/apc - name = "\improper APC frame" - desc = "Used for repairing or building APCs" - icon = 'icons/obj/apc_repair.dmi' - icon_state = "apc_frame" - refund_amt = 2 - refund_type = /obj/item/stack/material/steel - -/obj/item/frame/apc/attackby(obj/item/W as obj, mob/user as mob) - ..() - if (W.is_wrench()) - new /obj/item/stack/material/steel( get_turf(src.loc), 2 ) - qdel(src) - -/obj/item/frame/apc/try_build(turf/on_wall, mob/user as mob) - if (get_dist(on_wall, user)>1) - return - var/ndir = get_dir(user, on_wall) - if (!(ndir in GLOB.cardinal)) - return - var/turf/loc = get_turf(user) - var/area/A = loc.loc - if (!istype(loc, /turf/simulated/floor)) - to_chat(user, "APC cannot be placed on this spot.") - return - if (A.requires_power == 0 || istype(A, /area/space)) - to_chat(user, "APC cannot be placed in this area.") - return - if (A.get_apc()) - to_chat(user, "This area already has an APC.") - return //only one APC per area - for(var/obj/machinery/power/terminal/T in loc) - if (T.master) - to_chat(user, "There is another network terminal here.") - return - else - var/obj/item/stack/cable_coil/C = new /obj/item/stack/cable_coil(loc) - C.amount = 10 - to_chat(user, "You cut the cables and disassemble the unused power terminal.") - qdel(T) - new /obj/machinery/power/apc(loc, ndir, 1) - qdel(src) diff --git a/code/game/objects/items/circuitboards/circuitboard.dm b/code/game/objects/items/circuitboards/circuitboard.dm index ce82992e42f..c18140f7309 100644 --- a/code/game/objects/items/circuitboards/circuitboard.dm +++ b/code/game/objects/items/circuitboards/circuitboard.dm @@ -14,11 +14,9 @@ MAT_GLASS = 250, ) origin_tech = list(TECH_DATA = 2) - density = FALSE - anchored = FALSE w_class = WEIGHT_CLASS_SMALL - damage_force = 5.0 - throw_force = 5.0 + damage_force = 5 + throw_force = 5 throw_speed = 3 throw_range = 15 var/build_path = null @@ -35,8 +33,6 @@ */ var/list/def_components - var/contain_parts = TRUE - /** * called when we are used to construct something. * diff --git a/code/game/objects/items/circuitboards/frame.dm b/code/game/objects/items/circuitboards/frame.dm index 87b2cc6b214..b473bcc0d1e 100644 --- a/code/game/objects/items/circuitboards/frame.dm +++ b/code/game/objects/items/circuitboards/frame.dm @@ -46,7 +46,7 @@ /obj/item/circuitboard/firealarm name = T_BOARD("fire alarm") - build_path = /obj/machinery/firealarm + build_path = /obj/machinery/fire_alarm board_type = new /datum/frame/frame_types/fire_alarm materials_base = list(MAT_STEEL = 50, MAT_GLASS = 50) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index ca10f1e4975..9cc2b664050 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -848,7 +848,7 @@ //* Tool System *// -/obj/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list()) +/obj/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args) if(isnull(obj_cell_slot) || !obj_cell_slot.remove_tool_behavior || !obj_cell_slot.interaction_active(e_args.performer)) return ..() . = list() diff --git a/code/game/objects/random/mapping.dm b/code/game/objects/random/mapping.dm index 02803fa76d9..a173fa671a6 100644 --- a/code/game/objects/random/mapping.dm +++ b/code/game/objects/random/mapping.dm @@ -569,7 +569,7 @@ prob(10);list( /obj/item/module/power_control, /obj/item/stack/cable_coil, - /obj/item/frame/apc, + /obj/item/frame2/apc, /obj/item/cell/high, /obj/structure/closet/crate/corporate/focalpoint //FOCAL APC ), diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index 1436dfac1b4..8c70b3be3e9 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -353,7 +353,7 @@ //closet_appearance = /singleton/closet_appearance/crate/engineering starts_with = list( - /obj/item/solar_assembly = 21, + /obj/item/frame2/solar_panel = 2, /obj/item/circuitboard/solar_control, /obj/item/tracker_electronics, /obj/item/paper/solar) diff --git a/code/game/turfs/initialization/init.dm b/code/game/turfs/initialization/init.dm deleted file mode 100644 index 6c2d8ead295..00000000000 --- a/code/game/turfs/initialization/init.dm +++ /dev/null @@ -1,11 +0,0 @@ -/datum/turf_initializer/proc/InitializeTurf(var/turf/T) - return - -/area - var/datum/turf_initializer/turf_initializer = null - -/area/LateInitialize() - . = ..() - if(turf_initializer) - for(var/turf/simulated/T in src) - turf_initializer.InitializeTurf(T) diff --git a/code/game/turfs/initialization/maintenance.dm b/code/game/turfs/initialization/maintenance.dm deleted file mode 100644 index 329bb2e9707..00000000000 --- a/code/game/turfs/initialization/maintenance.dm +++ /dev/null @@ -1,64 +0,0 @@ -/datum/turf_initializer/maintenance/InitializeTurf(var/turf/simulated/T) - if(T.density) - return - // Quick and dirty check to avoid placing things inside windows - if(locate(/obj/structure/grille, T)) - return - - var/cardinal_turfs = T.CardinalTurfs() - - T.dirt = rand(10, 50) + rand(0, 50) - // If a neighbor is dirty, then we get dirtier. - var/how_dirty = dirty_neighbors(cardinal_turfs) - for(var/i = 0; i < how_dirty; i++) - T.dirt += rand(0,10) - T.update_dirt() - - if(prob(2)) - var/type = junk() - var/obj/junk = new type(T) - junk.obj_persist_status |= OBJ_PERSIST_STATUS_NO_THANK_YOU - if(prob(2)) - var/obj/effect/debris/cleanable/blood/oil/oil = new(T) - oil.obj_persist_status |= OBJ_PERSIST_STATUS_NO_THANK_YOU - if(prob(25)) // Keep in mind that only "corners" get any sort of web - attempt_web(T, cardinal_turfs) - -var/global/list/random_junk -/datum/turf_initializer/maintenance/proc/junk() - if(prob(25)) - return /obj/effect/debris/cleanable/generic - if(!random_junk) - random_junk = subtypesof(/obj/item/trash) - random_junk += typesof(/obj/item/cigbutt) - random_junk += /obj/effect/debris/cleanable/spiderling_remains - random_junk += /obj/effect/decal/remains/mouse - random_junk += /obj/effect/decal/remains/robot - random_junk -= /obj/item/trash/plate - random_junk -= /obj/item/trash/snack_bowl - random_junk -= /obj/item/trash/syndi_cakes - random_junk -= /obj/item/trash/tray - return pick(random_junk) - -/datum/turf_initializer/maintenance/proc/dirty_neighbors(var/list/cardinal_turfs) - var/how_dirty = 0 - for(var/turf/simulated/T in cardinal_turfs) - // Considered dirty if more than halfway to visible dirt - if(T.dirt > 25) - how_dirty++ - return how_dirty - -/datum/turf_initializer/maintenance/proc/attempt_web(var/turf/simulated/T) - var/turf/north_turf = get_step(T, NORTH) - if(!north_turf || !north_turf.density) - return - - for(var/dir in list(WEST, EAST)) // For the sake of efficiency, west wins over east in the case of 1-tile valid spots, rather than doing pick() - var/turf/neighbour = get_step(T, dir) - if(neighbour && neighbour.density) - if(dir == WEST) - var/obj/effect/debris/cleanable/cobweb/w1 = new(T) - w1.obj_persist_status |= OBJ_PERSIST_STATUS_NO_THANK_YOU - if(dir == EAST) - var/obj/effect/debris/cleanable/cobweb2/w2 = new(T) - w2.obj_persist_status |= OBJ_PERSIST_STATUS_NO_THANK_YOU diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index bb6ddac50bc..03bc1efd7a4 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -30,7 +30,8 @@ SHOULD_CALL_PARENT(FALSE) atom_flags |= ATOM_INITIALIZED - icon_state = SPACE_ICON_STATE(x, y, z) + // we have parallax and don't need this anymore + // icon_state = SPACE_ICON_STATE(x, y, z) // We might be an edge if(y == world.maxy || forced_dirs & NORTH) @@ -46,18 +47,23 @@ if (CONFIG_GET(flag/starlight)) update_starlight() - var/turf/below = below() - if(isnull(below)) - return INITIALIZE_HINT_NORMAL - - if(isspaceturf(below)) - return INITIALIZE_HINT_NORMAL - - var/area/A = below.loc - if(!below.density && (A.area_flags & AREA_FLAG_EXTERNAL)) - return INITIALIZE_HINT_NORMAL - + // todo: audit all this again + // tl;dr given we load maps at runtime now, the maploader will do changeturfing, which means + // we don't need to manually check all this in initialize return INITIALIZE_HINT_NORMAL + + // var/turf/below = below() + // if(isnull(below)) + // return INITIALIZE_HINT_NORMAL + + // if(isspaceturf(below)) + // return INITIALIZE_HINT_NORMAL + + // var/area/A = below.loc + // if(!below.density && (A.area_flags & AREA_FLAG_EXTERNAL)) + // return INITIALIZE_HINT_NORMAL + + // return INITIALIZE_HINT_NORMAL // todo: wtf happened there..? // return INITIALIZE_HINT_LATELOAD // oh no! we need to switch to being a different kind of turf! diff --git a/code/modules/cargo/supplypacks/engineering.dm b/code/modules/cargo/supplypacks/engineering.dm index eecdb6f2922..d5ebeb32a38 100644 --- a/code/modules/cargo/supplypacks/engineering.dm +++ b/code/modules/cargo/supplypacks/engineering.dm @@ -78,7 +78,7 @@ /datum/supply_pack/eng/solar name = "Solar Pack crate" contains = list( - /obj/item/solar_assembly = 21, + /obj/item/frame2/solar_panel = 21, /obj/item/circuitboard/solar_control, /obj/item/tracker_electronics, /obj/item/paper/solar diff --git a/code/modules/frames/frame.dm b/code/modules/frames/frame.dm new file mode 100644 index 00000000000..84fe568590d --- /dev/null +++ b/code/modules/frames/frame.dm @@ -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(., "") diff --git a/code/modules/frames/frame_stage.dm b/code/modules/frames/frame_stage.dm new file mode 100644 index 00000000000..cc78fef7f2c --- /dev/null +++ b/code/modules/frames/frame_stage.dm @@ -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]") diff --git a/code/modules/frames/frame_step.dm b/code/modules/frames/frame_step.dm new file mode 100644 index 00000000000..b9d17192dc8 --- /dev/null +++ b/code/modules/frames/frame_step.dm @@ -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) + . = "Interact 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 [rendered_item] 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 [rendered_material] 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 [rendered_stack] to [rendered_action]." + if(FRAME_REQUEST_TYPE_TOOL) + var/rendered_tool = request + . = "Use a [rendered_tool] 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]."]", + ) diff --git a/code/modules/frames/item.dm b/code/modules/frames/item.dm new file mode 100644 index 00000000000..72974bd7a0b --- /dev/null +++ b/code/modules/frames/item.dm @@ -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 [frame.item_deploy_tool] on it to deploy it in its current direction.") + if(frame.item_recycle_tool) + . += SPAN_NOTICE("Use a [frame.item_recycle_tool] 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(., ..()) diff --git a/code/modules/frames/structure.dm b/code/modules/frames/structure.dm new file mode 100644 index 00000000000..34250802676 --- /dev/null +++ b/code/modules/frames/structure.dm @@ -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 ..() diff --git a/code/modules/frames/types/apc.dm b/code/modules/frames/types/apc.dm new file mode 100644 index 00000000000..f5896d4908e --- /dev/null +++ b/code/modules/frames/types/apc.dm @@ -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 ..() diff --git a/code/modules/frames/types/fire_alarm.dm b/code/modules/frames/types/fire_alarm.dm new file mode 100644 index 00000000000..03f5797e8e8 --- /dev/null +++ b/code/modules/frames/types/fire_alarm.dm @@ -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) diff --git a/code/modules/frames/types/solar_panel.dm b/code/modules/frames/types/solar_panel.dm new file mode 100644 index 00000000000..f3802813a4f --- /dev/null +++ b/code/modules/frames/types/solar_panel.dm @@ -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 tracker electronics to make this a solar tracker assembly.") + else + . += SPAN_NOTICE("This assembly is wired to be a solar tracker.") + +/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 diff --git a/code/modules/logging/logging.dm b/code/modules/logging/logging.dm index dd3ca429729..9e9ea53f135 100644 --- a/code/modules/logging/logging.dm +++ b/code/modules/logging/logging.dm @@ -4,6 +4,7 @@ //! New action logging file. Global helpers for things like attack, construction, say, etc, will go in here. !// // todo: flesh this file out +// todo: redo everything again lmao we need structured logging /** * Logs a construction action / step @@ -26,7 +27,8 @@ * todo: log initiator */ /proc/log_construction(datum/event_args/actor/e_args, atom/target, message) - log_game("CONSTRUCTION: [key_name(e_args.performer)] [COORD(e_args.performer)] -> [target] [COORD(target)]: [message]") + // todo: better handling + log_game("CONSTRUCTION: [key_name(e_args?.performer)] [COORD(e_args?.performer)] -> [target] [COORD(target)]: [message]") /** * log click - context menu diff --git a/code/modules/materials/definitions/metals/steel.dm b/code/modules/materials/definitions/metals/steel.dm index e433859ba4f..ce337ac483f 100644 --- a/code/modules/materials/definitions/metals/steel.dm +++ b/code/modules/materials/definitions/metals/steel.dm @@ -100,6 +100,18 @@ cost = 5, time = 2 SECONDS, ) + for(var/datum/frame2/frame_datum as anything in GLOB.frame_datum_lookup) + if(!frame_datum.material_buildable) + continue + . += create_stack_recipe_datum( + category = "frames", + cost = frame_datum.material_cost, + name = frame_datum.name, + recipe_type = /datum/stack_recipe/frame, + recipe_args = list( + frame_datum.type, + ), + ) . += new /datum/stack_recipe/railing . += create_stack_recipe_datum(category = "sofas", cost = 1, name = "sofa middle", product = /obj/structure/bed/chair/sofa, exclusitivity = /obj/structure/bed) . += create_stack_recipe_datum(category = "sofas", cost = 1, name = "sofa left", product = /obj/structure/bed/chair/sofa/left, exclusitivity = /obj/structure/bed) @@ -113,13 +125,6 @@ cost = 2, ) // todo: frame rework - . += create_stack_recipe_datum( - category = "frames", - name = "apc frame", - product = /obj/item/frame/apc, - cost = 2, - ) - // todo: frame rework . += create_stack_recipe_datum( category = "frames", name = "mirror frame", diff --git a/code/modules/mob/gender.dm b/code/modules/mob/gender.dm index 10d119bba55..2bf07e8d991 100644 --- a/code/modules/mob/gender.dm +++ b/code/modules/mob/gender.dm @@ -30,6 +30,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection()) var/himself = "themselves" var/s = "" var/hes = "they're" + var/Hes = "They're" /datum/gender/male key = "male" @@ -47,6 +48,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection()) himself = "himself" s = "s" hes = "he's" + Hes = "He's" /datum/gender/female key = "female" @@ -64,6 +66,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection()) himself = "herself" s = "s" hes = "she's" + Hes = "She's" /datum/gender/neuter key = "neuter" @@ -81,6 +84,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection()) himself = "itself" s = "s" hes = "it's" + Hes = "It's" /datum/gender/herm key = "herm" @@ -98,6 +102,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection()) himself = "hirself" s = "s" hes = "shi's" + Hes = "Shi's" /mob/proc/p_they() var/datum/gender/G = GLOB.gender_datums[gender] @@ -122,3 +127,11 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection()) /mob/proc/p_Their() var/datum/gender/G = GLOB.gender_datums[gender] return G.His + +/mob/proc/p_theyre() + var/datum/gender/G = GLOB.gender_datums[gender] + return G.hes + +/mob/proc/p_Theyre() + var/datum/gender/G = GLOB.gender_datums[gender] + return G.Hes diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index e154c1b4602..92209ce6794 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -698,7 +698,7 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/power/apc, 22) "You disassembled the broken APC frame.",\ "You hear welding.") else - new /obj/item/frame/apc(loc) + new /obj/item/frame2/apc(loc) user.visible_message(\ "[src] has been cut from the wall by [user.name] with the [WT.name].",\ "You cut the APC frame from the wall.",\ @@ -706,7 +706,7 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/power/apc, 22) qdel(src) return else if (opened && ((machine_stat & BROKEN) || hacker || emagged)) - if (istype(W, /obj/item/frame/apc) && (machine_stat & BROKEN)) + if (istype(W, /obj/item/frame2/apc) && (machine_stat & BROKEN)) if(cell) to_chat(user, "You need to remove the power cell first.") return diff --git a/code/modules/power/solar/solar.dm b/code/modules/power/solar/solar.dm new file mode 100644 index 00000000000..0fd562b271b --- /dev/null +++ b/code/modules/power/solar/solar.dm @@ -0,0 +1,135 @@ +#define SOLAR_MAX_DIST 40 +/// Will start itself if config allows it (default is no). +#define SOLAR_AUTO_START_CONFIG 2 +GLOBAL_VAR_INIT(solar_gen_rate, 1500) +GLOBAL_LIST_EMPTY(solars_list) + +/obj/machinery/power/solar + name = "solar panel" + desc = "A solar electrical generator." + icon = 'icons/obj/power.dmi' + icon_state = "sp_base" + anchored = 1 + density = 1 + use_power = USE_POWER_OFF + idle_power_usage = 0 + active_power_usage = 0 + integrity = 100 + integrity_max = 100 + + var/id = 0 + var/sunfrac = 0 + var/adir = SOUTH // actual dir + var/ndir = SOUTH // target dir + var/turn_angle = 0 + var/obj/machinery/power/solar_control/control = null + +/obj/machinery/power/solar/Initialize(mapload) + . = ..() + connect_to_network() + update_icon() + +/obj/machinery/power/solar/Destroy() + unset_control() //remove from control computer + return ..() + +//set the control of the panel to a given computer if closer than SOLAR_MAX_DIST +/obj/machinery/power/solar/proc/set_control(var/obj/machinery/power/solar_control/SC) + if(SC && (get_dist(src, SC) > SOLAR_MAX_DIST)) + return 0 + control = SC + return 1 + +//set the control of the panel to null and removes it from the control list of the previous control computer if needed +/obj/machinery/power/solar/proc/unset_control() + if(control) + control.connected_panels.Remove(src) + control = null + +/obj/machinery/power/solar/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier) + // todo: tool act + if(I.is_crowbar()) + playsound(src.loc, 'sound/machines/click.ogg', 50, 1) + user.visible_message("[user] begins to take the glass off the solar panel.") + if(do_after(user, 50)) + playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) + user.visible_message("[user] takes the glass off the solar panel.") + deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED) + return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING + return CLICKCHAIN_DO_NOT_PROPAGATE + return ..() + +/obj/machinery/solar/drop_products(method, atom/where) + . = ..() + switch(method) + if(ATOM_DECONSTRUCT_DISASSEMBLED) + drop_product(method, new /obj/structure/frame2/solar_panel/anchored, where) + else + for(var/i in 1 to 2) + new /obj/item/material/shard(where) + +/obj/machinery/power/solar/update_icon() + ..() + cut_overlays() + if(machine_stat & BROKEN) + add_overlay(image('icons/obj/power.dmi', icon_state = "solar_panel-b", layer = FLY_LAYER)) + else + add_overlay(image('icons/obj/power.dmi', icon_state = "solar_panel", layer = FLY_LAYER)) + setDir(angle2dir(adir)) + return + +//calculates the fraction of the SSsun.sunlight that the panel recieves +/obj/machinery/power/solar/proc/update_solar_exposure() + var/p_angle = 180 + var/solar_brightness = 1 + + var/datum/planet/our_planet = null + var/turf/T = get_turf(src) + if(T.outdoors && (T.z <= SSplanets.z_to_planet.len)) + our_planet = SSplanets.z_to_planet[z] + + if(our_planet && istype(our_planet)) + solar_brightness = our_planet.sun_apparent_brightness * 1.3 + var/time_num = text2num(our_planet.current_time.show_time("hh")) + text2num(our_planet.current_time.show_time("mm")) / 60 + var/hours_in_day = our_planet.current_time.seconds_in_day / (1 HOURS) + var/sunangle_by_time = (time_num / hours_in_day) * 360 // day as progress from 0 to 1 * 360 + p_angle = abs(adir - sunangle_by_time) + else + if(!SSsun.sun) + return + //find the smaller angle between the direction the panel is facing and the direction of the SSsun.sun (the sign is not important here) + p_angle = min(abs(adir - SSsun.sun.angle), 360 - abs(adir - SSsun.sun.angle)) + + sunfrac = max(cos(p_angle), 0) * solar_brightness + +/obj/machinery/power/solar/process(delta_time)//TODO: remove/add this from machines to save on processing as needed ~Carn PRIORITY + if(machine_stat & BROKEN) + return + if(!control) //if there's the panel is not linked to a solar control computer, no need to proceed + return + + if(!sunfrac) //Not getting any sun, so why process + return + + if(powernet) + if(powernet == control.powernet)//check if the panel is still connected to the computer + var/sgen = GLOB.solar_gen_rate * sunfrac + add_avail(sgen * 0.001) + control.gen += sgen + else //if we're no longer on the same powernet, remove from control computer + unset_control() + +/obj/machinery/power/solar/atom_break() + . = ..() + machine_stat |= BROKEN + unset_control() + update_icon() + +/obj/machinery/power/solar/atom_fix() + . = ..() + machine_stat &= ~(BROKEN) + update_icon() + +/obj/item/paper/solar + name = "paper- 'Going green! Setup your own solar array instructions.'" + info = "

Welcome

At greencorps we love the environment, and space. With this package you are able to help mother nature and produce energy without any usage of fossil fuel or phoron! Singularity energy is dangerous while solar energy is safe, which is why it's better. Now here is how you setup your own solar array.

You can make a solar panel by wrenching the solar assembly onto a cable node. Adding a glass panel, reinforced or regular glass will do, will finish the construction of your solar panel. It is that easy!

Now after setting up 19 more of these solar panels you will want to create a solar tracker to keep track of our mother nature's gift, the SSsun.sun. These are the same steps as before except you insert the tracker equipment circuit into the assembly before performing the final step of adding the glass. You now have a tracker! Now the last step is to add a computer to calculate the SSsun.sun's movements and to send commands to the solar panels to change direction with the SSsun.sun. Setting up the solar computer is the same as setting up any computer, so you should have no trouble in doing that. You do need to put a wire node under the computer, and the wire needs to be connected to the tracker.

Congratulations, you should have a working solar array. If you are having trouble, here are some tips. Make sure all solar equipment are on a cable node, even the computer. You can always deconstruct your creations if you make a mistake.

That's all to it, be safe, be green!

" diff --git a/code/modules/power/solar.dm b/code/modules/power/solar/solar_control.dm similarity index 52% rename from code/modules/power/solar.dm rename to code/modules/power/solar/solar_control.dm index c6fc72abc0a..cd503e9daa7 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar/solar_control.dm @@ -1,238 +1,3 @@ -#define SOLAR_MAX_DIST 40 -/// Will start itself if config allows it (default is no). -#define SOLAR_AUTO_START_CONFIG 2 -GLOBAL_VAR_INIT(solar_gen_rate, 1500) -GLOBAL_LIST_EMPTY(solars_list) - -/obj/machinery/power/solar - name = "solar panel" - desc = "A solar electrical generator." - icon = 'icons/obj/power.dmi' - icon_state = "sp_base" - anchored = 1 - density = 1 - use_power = USE_POWER_OFF - idle_power_usage = 0 - active_power_usage = 0 - integrity = 100 - integrity_max = 100 - - var/id = 0 - var/sunfrac = 0 - var/adir = SOUTH // actual dir - var/ndir = SOUTH // target dir - var/turn_angle = 0 - var/obj/machinery/power/solar_control/control = null - -/obj/machinery/power/solar/can_drain_energy(datum/actor, flags) - return FALSE - -/obj/machinery/power/solar/Initialize(mapload, obj/item/solar_assembly/S) - . = ..() - Make(S) - connect_to_network() - -/obj/machinery/power/solar/Destroy() - unset_control() //remove from control computer - ..() - -//set the control of the panel to a given computer if closer than SOLAR_MAX_DIST -/obj/machinery/power/solar/proc/set_control(var/obj/machinery/power/solar_control/SC) - if(SC && (get_dist(src, SC) > SOLAR_MAX_DIST)) - return 0 - control = SC - return 1 - -//set the control of the panel to null and removes it from the control list of the previous control computer if needed -/obj/machinery/power/solar/proc/unset_control() - if(control) - control.connected_panels.Remove(src) - control = null - -/obj/machinery/power/solar/proc/Make(var/obj/item/solar_assembly/S) - if(!S) - S = new /obj/item/solar_assembly(src) - S.glass_type = /obj/item/stack/material/glass - else - S.forceMove(src) - if(S.glass_type == /obj/item/stack/material/glass/reinforced) //if the panel is in reinforced glass - set_max_integrity(integrity_max * 2) - set_integrity(integrity * 2) - update_icon() - -/obj/machinery/power/solar/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier) - // todo: tool act - if(I.is_crowbar()) - playsound(src.loc, 'sound/machines/click.ogg', 50, 1) - user.visible_message("[user] begins to take the glass off the solar panel.") - if(do_after(user, 50)) - var/obj/item/solar_assembly/S = locate() in src - if(S) - S.loc = src.loc - S.give_glass() - playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) - user.visible_message("[user] takes the glass off the solar panel.") - new /obj/item/solar_assembly(get_turf(src)) - qdel(src) - return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING - return CLICKCHAIN_DO_NOT_PROPAGATE - -/obj/machinery/solar/drop_products(method, atom/where) - . = ..() - switch(method) - if(ATOM_DECONSTRUCT_DISASSEMBLED) - new /obj/item/stack/material/glass(where, 2) - else - for(var/i in 1 to 2) - new /obj/item/material/shard(where) - -/obj/machinery/power/solar/update_icon() - ..() - cut_overlays() - if(machine_stat & BROKEN) - add_overlay(image('icons/obj/power.dmi', icon_state = "solar_panel-b", layer = FLY_LAYER)) - else - add_overlay(image('icons/obj/power.dmi', icon_state = "solar_panel", layer = FLY_LAYER)) - setDir(angle2dir(adir)) - return - -//calculates the fraction of the SSsun.sunlight that the panel recieves -/obj/machinery/power/solar/proc/update_solar_exposure() - var/p_angle = 180 - var/solar_brightness = 1 - - var/datum/planet/our_planet = null - var/turf/T = get_turf(src) - if(T.outdoors && (T.z <= SSplanets.z_to_planet.len)) - our_planet = SSplanets.z_to_planet[z] - - if(our_planet && istype(our_planet)) - solar_brightness = our_planet.sun_apparent_brightness * 1.3 - var/time_num = text2num(our_planet.current_time.show_time("hh")) + text2num(our_planet.current_time.show_time("mm")) / 60 - var/hours_in_day = our_planet.current_time.seconds_in_day / (1 HOURS) - var/sunangle_by_time = (time_num / hours_in_day) * 360 // day as progress from 0 to 1 * 360 - p_angle = abs(adir - sunangle_by_time) - else - if(!SSsun.sun) - return - //find the smaller angle between the direction the panel is facing and the direction of the SSsun.sun (the sign is not important here) - p_angle = min(abs(adir - SSsun.sun.angle), 360 - abs(adir - SSsun.sun.angle)) - - sunfrac = max(cos(p_angle), 0) * solar_brightness - -/obj/machinery/power/solar/process(delta_time)//TODO: remove/add this from machines to save on processing as needed ~Carn PRIORITY - if(machine_stat & BROKEN) - return - if(!control) //if there's the panel is not linked to a solar control computer, no need to proceed - return - - if(!sunfrac) //Not getting any sun, so why process - return - - if(powernet) - if(powernet == control.powernet)//check if the panel is still connected to the computer - var/sgen = GLOB.solar_gen_rate * sunfrac - add_avail(sgen * 0.001) - control.gen += sgen - else //if we're no longer on the same powernet, remove from control computer - unset_control() - -/obj/machinery/power/solar/atom_break() - . = ..() - machine_stat |= BROKEN - unset_control() - update_icon() - -/obj/machinery/power/solar/atom_fix() - . = ..() - machine_stat &= ~(BROKEN) - update_icon() - -/obj/machinery/power/solar/fake/Initialize(mapload, obj/item/solar_assembly/S) - . = ..(mapload, S, FALSE) - -/obj/machinery/power/solar/fake/process(delta_time) - return PROCESS_KILL - -// -// Solar Assembly - For construction of solar arrays. -// - -/obj/item/solar_assembly - name = "solar panel assembly" - desc = "A solar panel assembly kit, allows constructions of a solar panel, or with a tracking circuit board, a solar tracker" - icon = 'icons/obj/power.dmi' - icon_state = "sp_base" - item_state = "camera" - w_class = WEIGHT_CLASS_BULKY // Pretty big! - anchored = 0 - var/tracker = 0 - var/glass_type = null - -/obj/item/solar_assembly/attack_hand(mob/user, list/params) - if(!anchored || !isturf(loc)) // You can't pick it up - ..() - -// Give back the glass type we were supplied with -/obj/item/solar_assembly/proc/give_glass() - if(glass_type) - var/obj/item/stack/material/S = new glass_type(src.loc) - S.amount = 2 - glass_type = null - - -/obj/item/solar_assembly/attackby(var/obj/item/W, var/mob/user) - if (!isturf(loc)) - return 0 - if(!anchored) - if(W.is_wrench()) - anchored = 1 - user.visible_message("[user] wrenches the solar assembly into place.") - playsound(src, W.tool_sound, 75, 1) - return 1 - else - if(W.is_wrench()) - anchored = 0 - user.visible_message("[user] unwrenches the solar assembly from it's place.") - playsound(src, W.tool_sound, 75, 1) - return 1 - - // todo: phoronglass solars - if(istype(W, /obj/item/stack/material) && istype(W.get_primary_material(), /datum/material/glass)) - var/obj/item/stack/material/S = W - if(S.use(2)) - glass_type = W.type - playsound(src.loc, 'sound/machines/click.ogg', 50, 1) - user.visible_message("[user] places the glass on the solar assembly.") - if(tracker) - new /obj/machinery/power/tracker(get_turf(src), src) - else - new /obj/machinery/power/solar(get_turf(src), src) - qdel(src) - else - to_chat(user, "You need two sheets of glass to put them into a solar panel.") - return - return 1 - - if(!tracker) - if(istype(W, /obj/item/tracker_electronics)) - if(!user.attempt_consume_item_for_construction(W)) - return - tracker = 1 - user.visible_message("[user] inserts the electronics into the solar assembly.") - return 1 - else - if(W.is_crowbar()) - new /obj/item/tracker_electronics(src.loc) - tracker = 0 - user.visible_message("[user] takes out the electronics from the solar assembly.") - return 1 - ..() - -// -// Solar Control Computer -// - /obj/machinery/power/solar_control name = "solar panel control" desc = "A controller for solar panel arrays." @@ -528,14 +293,6 @@ GLOBAL_LIST_EMPTY(solars_list) broken() return -// -// MISC -// - -/obj/item/paper/solar - name = "paper- 'Going green! Setup your own solar array instructions.'" - info = "

Welcome

At greencorps we love the environment, and space. With this package you are able to help mother nature and produce energy without any usage of fossil fuel or phoron! Singularity energy is dangerous while solar energy is safe, which is why it's better. Now here is how you setup your own solar array.

You can make a solar panel by wrenching the solar assembly onto a cable node. Adding a glass panel, reinforced or regular glass will do, will finish the construction of your solar panel. It is that easy!

Now after setting up 19 more of these solar panels you will want to create a solar tracker to keep track of our mother nature's gift, the SSsun.sun. These are the same steps as before except you insert the tracker equipment circuit into the assembly before performing the final step of adding the glass. You now have a tracker! Now the last step is to add a computer to calculate the SSsun.sun's movements and to send commands to the solar panels to change direction with the SSsun.sun. Setting up the solar computer is the same as setting up any computer, so you should have no trouble in doing that. You do need to put a wire node under the computer, and the wire needs to be connected to the tracker.

Congratulations, you should have a working solar array. If you are having trouble, here are some tips. Make sure all solar equipment are on a cable node, even the computer. You can always deconstruct your creations if you make a mistake.

That's all to it, be safe, be green!

" - /proc/rate_control(var/S, var/V, var/C, var/Min=1, var/Max=5, var/Limit=null) //How not to name vars var/href = "-[href]=-[Min]'>- [(C?C : 0)] [href]=[Min]'>+[href]=[Max]'>+" diff --git a/code/modules/power/tracker.dm b/code/modules/power/solar/tracker.dm similarity index 72% rename from code/modules/power/tracker.dm rename to code/modules/power/solar/tracker.dm index 0b038579244..7263733e874 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/solar/tracker.dm @@ -8,17 +8,17 @@ desc = "A solar directional tracker." icon = 'icons/obj/power.dmi' icon_state = "tracker" - anchored = 1 - density = 1 + anchored = TRUE + density = TRUE use_power = USE_POWER_OFF var/id = 0 var/sun_angle = 0 // sun angle as set by sun datum var/obj/machinery/power/solar_control/control = null -/obj/machinery/power/tracker/Initialize(mapload, obj/item/solar_assembly/S) +/obj/machinery/power/tracker/Initialize(mapload) . = ..() - Make(S) + update_icon() connect_to_network() /obj/machinery/power/tracker/Destroy() @@ -38,15 +38,6 @@ control.connected_tracker = null control = null -/obj/machinery/power/tracker/proc/Make(var/obj/item/solar_assembly/S) - if(!S) - S = new /obj/item/solar_assembly(src) - S.glass_type = /obj/item/stack/material/glass - S.tracker = 1 - S.anchored = 1 - S.loc = src - update_icon() - //updates the tracker icon and the facing angle for the control computer /obj/machinery/power/tracker/proc/set_angle(var/angle) sun_angle = angle @@ -57,21 +48,20 @@ if(powernet && (powernet == control.powernet)) //update if we're still in the same powernet control.cdir = angle -/obj/machinery/power/tracker/attackby(var/obj/item/W, var/mob/user) +/obj/machinery/power/tracker/drop_products(method, atom/where) + . = ..() + drop_product(method, new /obj/structure/frame2/solar_panel/tracker/anchored, where) - if(W.is_crowbar()) +/obj/machinery/power/tracker/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier) + // todo: tool system + if(I.is_crowbar()) playsound(src.loc, 'sound/machines/click.ogg', 50, 1) user.visible_message("[user] begins to take the glass off the solar tracker.") if(do_after(user, 50)) - var/obj/item/solar_assembly/S = locate() in src - if(S) - S.loc = src.loc - S.give_glass() - playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) - user.visible_message("[user] takes the glass off the tracker.") - qdel(src) - return - ..() + deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED) + return CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE + return CLICKCHAIN_DO_NOT_PROPAGATE + return ..() // Tracker Electronic diff --git a/code/modules/unit_tests/wallmounted_obj_dirs/cameras.dm b/code/modules/unit_tests/wallmounted_obj_dirs/cameras.dm deleted file mode 100644 index cf510ae99e5..00000000000 --- a/code/modules/unit_tests/wallmounted_obj_dirs/cameras.dm +++ /dev/null @@ -1,10 +0,0 @@ -/datum/unit_test/camera_direction/Run() - var/list/bad_cameras - for(var/obj/machinery/camera/C in world) - if((C.dir == NORTHWEST || SOUTHWEST || NORTHEAST || NORTHWEST) || (C.dir != NORTH||SOUTH||EAST||WEST)) //specifically checking for non NESW dirs.. - bad_cameras[C] = list("X"=C.x, "Y"=C.y, "Z"=C.z, "A"=C.loc.loc) // loc.loc is a lazy way of getting an area. - if(length(bad_cameras)) - TEST_FAIL("One or more cameras had a non-cardinal direction!") - for(var/obj/I in bad_cameras) - TEST_FAIL("[I] at X [bad_cameras[I]["X"]] Y [bad_cameras[I]["Y"]] Z [bad_cameras[I]["Z"]] in area [bad_cameras[I]["A"]]") - Fail("There were [length(bad_cameras)] cameras with non-cardinal directions.") diff --git a/code/modules/unit_tests/wallmounted_obj_dirs/fire_alarms.dm b/code/modules/unit_tests/wallmounted_obj_dirs/fire_alarms.dm deleted file mode 100644 index 5e17b8d2e28..00000000000 --- a/code/modules/unit_tests/wallmounted_obj_dirs/fire_alarms.dm +++ /dev/null @@ -1,10 +0,0 @@ -/datum/unit_test/fire_alarm_direction/proc/Run() - var/list/bad_alarms - for(var/obj/machinery/firealarm/C in world) - if((C.dir == NORTHWEST || SOUTHWEST || NORTHEAST || NORTHWEST) || (C.dir != NORTH||SOUTH||EAST||WEST)) //specifically checking for non NESW dirs.. - bad_alarms[C] = list("X"=C.x, "Y"=C.y, "Z"=C.z, "A"=C.loc.loc) // loc.loc is a lazy way of getting an area. - if(length(bad_alarms)) - TEST_FAIL("One or more alarms had a non-cardinal direction!") - for(var/obj/I in bad_alarms) - TEST_FAIL("[I] at X [bad_alarms[I]["X"]] Y [bad_alarms[I]["Y"]] Z [bad_alarms[I]["Z"]] in area [bad_alarms[I]["A"]]") - Fail("There were [length(bad_alarms)] alarms with non-cardinal directions.") diff --git a/icons/machinery/fire_alarm.dmi b/icons/machinery/fire_alarm.dmi new file mode 100644 index 00000000000..258e923c0e9 Binary files /dev/null and b/icons/machinery/fire_alarm.dmi differ diff --git a/icons/machinery/power/apc.dmi b/icons/machinery/power/apc.dmi new file mode 100644 index 00000000000..ffef6e4e2fe Binary files /dev/null and b/icons/machinery/power/apc.dmi differ diff --git a/icons/machinery/power/solar/panel.dmi b/icons/machinery/power/solar/panel.dmi new file mode 100644 index 00000000000..8eae3405ba1 Binary files /dev/null and b/icons/machinery/power/solar/panel.dmi differ diff --git a/icons/modules/frames/base.dmi b/icons/modules/frames/base.dmi new file mode 100644 index 00000000000..e74dd2c5791 Binary files /dev/null and b/icons/modules/frames/base.dmi differ diff --git a/maps/away_missions/140x140/snow_outpost.dmm b/maps/away_missions/140x140/snow_outpost.dmm index 9eb53929a4d..8c7ae8e8ffd 100644 --- a/maps/away_missions/140x140/snow_outpost.dmm +++ b/maps/away_missions/140x140/snow_outpost.dmm @@ -662,7 +662,7 @@ /turf/simulated/wall, /area/awaymission/snow_outpost/dark) "cw" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /obj/item/module/power_control, /turf/simulated/floor/plating/external, /area/awaymission/snow_outpost/dark) diff --git a/maps/away_missions/140x140/zoo.dmm b/maps/away_missions/140x140/zoo.dmm index c18ec430314..21cd0d63ed6 100644 --- a/maps/away_missions/140x140/zoo.dmm +++ b/maps/away_missions/140x140/zoo.dmm @@ -8545,7 +8545,7 @@ /obj/structure/cable{ icon_state = "0-2" }, -/obj/machinery/power/solar/fake, +/obj/machinery/power/solar, /turf/simulated/floor/airless{ icon_state = "solarpanel" }, @@ -8630,7 +8630,7 @@ /area/awaymission/zoo/solars) "xt" = ( /obj/structure/cable, -/obj/machinery/power/solar/fake, +/obj/machinery/power/solar, /turf/simulated/floor/airless{ icon_state = "solarpanel" }, diff --git a/maps/away_missions/archive/blackmarketpackers.dmm b/maps/away_missions/archive/blackmarketpackers.dmm index dcd9d3d2f1d..d3d8cb18dd8 100644 --- a/maps/away_missions/archive/blackmarketpackers.dmm +++ b/maps/away_missions/archive/blackmarketpackers.dmm @@ -1855,7 +1855,7 @@ /turf/simulated/floor/plating, /area/awaymission) "hK" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor/airless, /area/awaymission) "hL" = ( diff --git a/maps/away_missions/archive/stationCollision.dmm b/maps/away_missions/archive/stationCollision.dmm index 3438b17126f..14eb7f5c755 100644 --- a/maps/away_missions/archive/stationCollision.dmm +++ b/maps/away_missions/archive/stationCollision.dmm @@ -11,7 +11,7 @@ /turf/simulated/floor/plating, /area/awaymission/northblock) "ad" = ( -/obj/machinery/power/solar/fake, +/obj/machinery/power/solar, /turf/simulated/floor/airless{ icon_state = "solarpanel" }, @@ -29,7 +29,7 @@ }, /area/awaymission/northblock) "ai" = ( -/obj/machinery/power/solar/fake, +/obj/machinery/power/solar, /turf/space, /area/awaymission/northblock) "aj" = ( diff --git a/maps/away_missions/archive/zresearchlabs.dmm b/maps/away_missions/archive/zresearchlabs.dmm index 3a7a99ef376..89c7035ba63 100644 --- a/maps/away_missions/archive/zresearchlabs.dmm +++ b/maps/away_missions/archive/zresearchlabs.dmm @@ -240,7 +240,7 @@ }, /area/awaymission/labs/cave) "bb" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor{ icon_state = "white" }, diff --git a/maps/euthenia/levels/deck4.dmm b/maps/euthenia/levels/deck4.dmm index e604b9aa960..a5094153ae0 100644 --- a/maps/euthenia/levels/deck4.dmm +++ b/maps/euthenia/levels/deck4.dmm @@ -11007,7 +11007,7 @@ /turf/simulated/floor/tiled, /area/rnd/secure_storage/critical) "Ql" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor/airless, /area/main_map/maintenance/deck_four/starboard) "Qm" = ( diff --git a/maps/generic/misc.dm b/maps/generic/misc.dm index df4cfe92d06..3af8d819c54 100644 --- a/maps/generic/misc.dm +++ b/maps/generic/misc.dm @@ -161,8 +161,6 @@ layer = ABOVE_WINDOW_LAYER /obj/machinery/embedded_controller layer = ABOVE_WINDOW_LAYER -/obj/machinery/firealarm - layer = ABOVE_WINDOW_LAYER /obj/machinery/flasher layer = ABOVE_WINDOW_LAYER /obj/machinery/keycard_auth @@ -216,8 +214,6 @@ layer = ABOVE_WINDOW_LAYER /obj/machinery/embedded_controller layer = ABOVE_WINDOW_LAYER -/obj/machinery/firealarm - layer = ABOVE_WINDOW_LAYER /obj/machinery/flasher layer = ABOVE_WINDOW_LAYER /obj/machinery/keycard_auth diff --git a/maps/submaps/level_specific/class_h/AuxiliaryResearchFacility.dmm b/maps/submaps/level_specific/class_h/AuxiliaryResearchFacility.dmm index 12a8cd0836a..6252eec547c 100644 --- a/maps/submaps/level_specific/class_h/AuxiliaryResearchFacility.dmm +++ b/maps/submaps/level_specific/class_h/AuxiliaryResearchFacility.dmm @@ -966,7 +966,7 @@ /area/class_h/POIs/AuxiliaryResearchFacility) "wc" = ( /obj/effect/debris/cleanable/dirt, -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor/plating, /area/class_h/POIs/AuxiliaryResearchFacility) "wg" = ( diff --git a/maps/submaps/level_specific/debrisfield_vr/gecko_cr_wreck.dmm b/maps/submaps/level_specific/debrisfield_vr/gecko_cr_wreck.dmm index 9f9f1729236..d3ef16c0afb 100644 --- a/maps/submaps/level_specific/debrisfield_vr/gecko_cr_wreck.dmm +++ b/maps/submaps/level_specific/debrisfield_vr/gecko_cr_wreck.dmm @@ -1899,7 +1899,7 @@ /obj/structure/cable{ icon_state = "0-8" }, -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor/tiled/techfloor{ initial_gas_mix = "TEMP=2.7" }, diff --git a/maps/submaps/level_specific/debrisfield_vr/mackerel_lc_wreck.dmm b/maps/submaps/level_specific/debrisfield_vr/mackerel_lc_wreck.dmm index 582d630398c..481110ca857 100644 --- a/maps/submaps/level_specific/debrisfield_vr/mackerel_lc_wreck.dmm +++ b/maps/submaps/level_specific/debrisfield_vr/mackerel_lc_wreck.dmm @@ -290,7 +290,7 @@ /obj/structure/cable{ icon_state = "0-8" }, -/obj/item/frame/apc, +/obj/item/frame2/apc, /obj/item/integrated_circuit, /turf/simulated/floor/tiled/techfloor, /area/shuttle/mackerel_lc_wreck) diff --git a/maps/submaps/level_specific/debrisfield_vr/old_satellite.dmm b/maps/submaps/level_specific/debrisfield_vr/old_satellite.dmm index 464bb53d983..47500768eda 100644 --- a/maps/submaps/level_specific/debrisfield_vr/old_satellite.dmm +++ b/maps/submaps/level_specific/debrisfield_vr/old_satellite.dmm @@ -45,7 +45,7 @@ /obj/structure/cable{ icon_state = "0-4" }, -/obj/machinery/power/solar/fake{ +/obj/machinery/power/solar{ adir = 135 }, /turf/simulated/floor/airless, @@ -57,7 +57,7 @@ /obj/structure/cable{ icon_state = "0-4" }, -/obj/machinery/power/solar/fake{ +/obj/machinery/power/solar{ adir = 135 }, /turf/simulated/floor/airless, @@ -97,7 +97,7 @@ /obj/structure/cable{ icon_state = "0-8" }, -/obj/machinery/power/solar/fake, +/obj/machinery/power/solar, /turf/simulated/floor/airless, /area/submap/debrisfield_vr/old_sat) "n" = ( diff --git a/maps/submaps/level_specific/underdark/abandonded_outpost.dmm b/maps/submaps/level_specific/underdark/abandonded_outpost.dmm index ddf6663ec9f..81854381bf9 100644 --- a/maps/submaps/level_specific/underdark/abandonded_outpost.dmm +++ b/maps/submaps/level_specific/underdark/abandonded_outpost.dmm @@ -171,7 +171,7 @@ /turf/simulated/wall, /area/mine/explored/underdark) "aR" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor/tiled/steel_dirty/virgo3b, /area/mine/explored/underdark) "aS" = ( diff --git a/maps/submaps/mountains/digsite.dmm b/maps/submaps/mountains/digsite.dmm index 0a1a9391840..5386debe8e7 100644 --- a/maps/submaps/mountains/digsite.dmm +++ b/maps/submaps/mountains/digsite.dmm @@ -70,7 +70,7 @@ /turf/simulated/wall, /area/submap/cave/digsite) "t" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /obj/item/module/power_control, /turf/simulated/floor/plating/external, /area/submap/cave/digsite) diff --git a/maps/submaps/plains/Diner.dmm b/maps/submaps/plains/Diner.dmm index d37293b6c91..529663e861f 100644 --- a/maps/submaps/plains/Diner.dmm +++ b/maps/submaps/plains/Diner.dmm @@ -346,7 +346,7 @@ /turf/simulated/floor/tiled/white, /area/submap/Diner) "bf" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor/lino, /area/submap/Diner) "bg" = ( diff --git a/maps/submaps/plains/Diner_vr.dmm b/maps/submaps/plains/Diner_vr.dmm index 67d63b084b6..0d5b25d8a69 100644 --- a/maps/submaps/plains/Diner_vr.dmm +++ b/maps/submaps/plains/Diner_vr.dmm @@ -358,7 +358,7 @@ /turf/simulated/floor/tiled/white, /area/submap/Diner) "bi" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor/lino, /area/submap/Diner) "bj" = ( diff --git a/maps/tether/levels/station1.dmm b/maps/tether/levels/station1.dmm index f6250238be8..bc7c78fb624 100644 --- a/maps/tether/levels/station1.dmm +++ b/maps/tether/levels/station1.dmm @@ -19511,7 +19511,7 @@ /area/tether/exploration/crew) "Os" = ( /obj/item/cell/potato, -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor, /area/vacant/vacant_restaurant_lower) "Ot" = ( @@ -24655,7 +24655,7 @@ /turf/simulated/floor/tiled, /area/engineering/hallway) "YM" = ( -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor, /area/constructionsite) "YN" = ( diff --git a/maps/tether/levels/station2.dmm b/maps/tether/levels/station2.dmm index f33312e40cb..5ea17f5650e 100644 --- a/maps/tether/levels/station2.dmm +++ b/maps/tether/levels/station2.dmm @@ -22566,7 +22566,7 @@ /area/shuttle/excursion/general) "Uh" = ( /obj/effect/floor_decal/rust, -/obj/item/frame/apc, +/obj/item/frame2/apc, /turf/simulated/floor/plating, /area/vacant/vacant_restaurant_upper) "Ui" = (