From 6957bc34f151b8a4e4b3d3bb2a3aa953607c79ad Mon Sep 17 00:00:00 2001
From: RustingWithYou <63625389+RustingWithYou@users.noreply.github.com>
Date: Sat, 20 Apr 2024 09:56:26 +1200
Subject: [PATCH] Blueprints Rework (#18947)
Reworks blueprints to use an eye component for area selection and
changing. Blueprints will now work on whichever overmap site they spawn
on if overmap is enabled, though currently they have only been added to
the Horizon.
Adds shuttle-modifying blueprints for altering shuttle areas and
exoplanet outpost blueprints for creating areas on exoplanets. A set of
outpost blueprints and a lockbox containing blueprints for the Horizon's
shuttles have been added to the CE's locker.
Moves eye creation to a component.
Ported from:
https://github.com/NebulaSS13/Nebula/pull/465
https://github.com/NebulaSS13/Nebula/pull/564
https://github.com/NebulaSS13/Nebula/pull/3046
---
aurorastation.dme | 4 +
code/__DEFINES/misc.dm | 1 +
code/__HELPERS/_lists.dm | 2 +
code/_onclick/click.dm | 9 +
code/_onclick/hud/action.dm | 31 ++
code/_onclick/hud/fullscreen.dm | 6 +
code/datums/components/eye/_eye.dm | 84 ++++
code/datums/components/eye/blueprints.dm | 48 +++
code/datums/components/eye/freelook.dm | 13 +
code/game/atoms.dm | 1 +
code/game/objects/items/blueprints.dm | 390 ++++++------------
.../closets/secure/engineering.dm | 2 +
code/modules/clothing/masks/miscellaneous.dm | 52 +--
code/modules/mob/abstract/freelook/ai/eye.dm | 2 +
.../freelook/blueprints/blueprints.dm | 330 +++++++++++++++
code/modules/mob/abstract/freelook/eye.dm | 40 ++
code/modules/mob/living/carbon/human/human.dm | 2 +
code/modules/mob/living/life.dm | 3 +-
.../overmap/exoplanets/decor/_areas.dm | 2 +-
code/modules/overmap/overmap_shuttle.dm | 4 +
code/modules/overmap/sectors.dm | 1 +
code/modules/shuttles/shuttle.dm | 8 +
html/changelogs/RustingWithYou - iseeyou.yml | 60 +++
icons/effects/blueprints.dmi | Bin 0 -> 486 bytes
icons/obj/action_buttons/actions.dmi | Bin 30912 -> 32461 bytes
icons/obj/item/tools/blueprints.dmi | Bin 0 -> 516 bytes
maps/_common/areas/areas.dm | 1 +
maps/_common/areas/asteroid_areas.dm | 1 +
maps/runtime/runtime-1.dmm | 15 +-
29 files changed, 810 insertions(+), 302 deletions(-)
create mode 100644 code/datums/components/eye/_eye.dm
create mode 100644 code/datums/components/eye/blueprints.dm
create mode 100644 code/datums/components/eye/freelook.dm
create mode 100644 code/modules/mob/abstract/freelook/blueprints/blueprints.dm
create mode 100644 html/changelogs/RustingWithYou - iseeyou.yml
create mode 100644 icons/effects/blueprints.dmi
create mode 100644 icons/obj/item/tools/blueprints.dmi
diff --git a/aurorastation.dme b/aurorastation.dme
index 2a7647edd94..9f0122c6294 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -424,6 +424,9 @@
#include "code\datums\components\local_network.dm"
#include "code\datums\components\armor\armor.dm"
#include "code\datums\components\base_name\base_name.dm"
+#include "code\datums\components\eye\_eye.dm"
+#include "code\datums\components\eye\blueprints.dm"
+#include "code\datums\components\eye\freelook.dm"
#include "code\datums\components\multitool\_multitool.dm"
#include "code\datums\components\multitool\multitool.dm"
#include "code\datums\components\multitool\circuitboards\circuitboards.dm"
@@ -2458,6 +2461,7 @@
#include "code\modules\mob\abstract\freelook\ai\chunk.dm"
#include "code\modules\mob\abstract\freelook\ai\eye.dm"
#include "code\modules\mob\abstract\freelook\ai\update_triggers.dm"
+#include "code\modules\mob\abstract\freelook\blueprints\blueprints.dm"
#include "code\modules\mob\abstract\new_player\character_traits.dm"
#include "code\modules\mob\abstract\new_player\login.dm"
#include "code\modules\mob\abstract\new_player\logout.dm"
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 13a4c60d3ef..368493a4dac 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -135,6 +135,7 @@
#define AREA_FLAG_PRISON BITFLAG(6) // Marks prison area for purposes of checking if brigged/imprisoned
#define AREA_FLAG_NO_GHOST_TELEPORT_ACCESS BITFLAG(7) // Marks whether ghosts should not have teleport access to this area
#define AREA_FLAG_INDESTRUCTIBLE_TURFS BITFLAG(8) //Marks whether or not turfs in this area can be destroyed by explosions
+#define AREA_FLAG_IS_BACKGROUND BITFLAG(9) //Marks whether or not blueprints can create areas on top of this area
// Convoluted setup so defines can be supplied by Bay12 main server compile script.
// Should still work fine for people jamming the icons into their repo.
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index e1762023d41..166460940b0 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -104,6 +104,8 @@
#define LAZYISIN(L, I) (L ? (I in L) : FALSE)
#define LAZYDISTINCTADD(L, I) if(!L) { L = list(); } L |= I;
#define LAZYREPLACEKEY(L, K, NK) if(L) { if(L[K]) { L[NK] = L[K] } else {L += NK} L -= K; }
+///Inserts an item into the list at position X, if the list is null it will initialize it
+#define LAZYINSERT(L, I, X) if(!L) { L = list(); } L.Insert(X, I);
// Shims for some list procs in lists.dm.
#define isemptylist(L) (!LAZYLEN(L))
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index f1009056b30..0e4dad6f917 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -83,6 +83,9 @@
return B.ClickOn(A, params)
var/list/modifiers = params2list(params)
+ if(modifiers["right"])
+ RightClickOn(A)
+ return TRUE
if(modifiers["shift"] && modifiers["ctrl"])
CtrlShiftClickOn(A)
return TRUE
@@ -306,6 +309,12 @@
/mob/proc/TurfAdjacent(var/turf/T)
return T.AdjacentQuick(src)
+/mob/proc/RightClickOn(atom/A)
+ A.RightClick(src)
+
+/atom/proc/RightClick(mob/user)
+ return
+
/*
Control+Shift click
Unused except for AI
diff --git a/code/_onclick/hud/action.dm b/code/_onclick/hud/action.dm
index 9e760cefd0b..392ba4bbb57 100644
--- a/code/_onclick/hud/action.dm
+++ b/code/_onclick/hud/action.dm
@@ -10,6 +10,12 @@
#define AB_CHECK_ALIVE 8
#define AB_CHECK_INSIDE 16
+///Eye action targets the parent datum.
+#define PARENT_TARGET 0
+///Eye action targets the eye mob itself.
+#define EYE_TARGET 1
+///Eye action targets the eye component.
+#define COMPONENT_TARGET 2
/datum/action
var/name = "Generic Action"
@@ -288,6 +294,31 @@
to_chat(usr, SPAN_NOTICE("You press the button on the exterior of \the [target_clothing]."))
target_clothing.action_circuit.activate_pin(1)
+/datum/action/eye
+ action_type = AB_GENERIC
+ check_flags = AB_CHECK_LYING|AB_CHECK_STUNNED
+ ///The type of /mob/abstract/eye used by the action.
+ var/eye_type = /mob/abstract/eye
+ ///The relevant owner of the proc to be called by the eye action.
+ var/target_type = PARENT_TARGET
+
+/datum/action/eye/New(var/datum/component/eye/eye_component)
+ if(!istype(eye_component))
+ crash_with("Attempted to generate eye action [src], but no eye component was provided!")
+ switch(target_type)
+ if(PARENT_TARGET)
+ return ..(eye_component.parent)
+ if(EYE_TARGET)
+ return ..(eye_component.component_eye)
+ if(COMPONENT_TARGET)
+ return ..(eye_component)
+ else
+ crash_with("Attempted to generate eye action [src] but an improper target_type ([target_type]) was defined.")
+
+/datum/action/eye/CheckRemoval(mob/living/user)
+ if(!user.eyeobj || !istype(user.eyeobj, eye_type))
+ return TRUE
+
#undef AB_WEST_OFFSET
#undef AB_NORTH_OFFSET
#undef AB_MAX_COLUMNS
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
index d004285be86..8f571ed64b9 100644
--- a/code/_onclick/hud/fullscreen.dm
+++ b/code/_onclick/hud/fullscreen.dm
@@ -151,3 +151,9 @@
/obj/screen/fullscreen/teleport
icon_state = "teleport"
+
+/obj/screen/fullscreen/blueprints
+ icon = 'icons/effects/blueprints.dmi'
+ icon_state = "base"
+ screen_loc = ui_entire_screen
+ alpha = 100
diff --git a/code/datums/components/eye/_eye.dm b/code/datums/components/eye/_eye.dm
new file mode 100644
index 00000000000..a2b36e207a2
--- /dev/null
+++ b/code/datums/components/eye/_eye.dm
@@ -0,0 +1,84 @@
+/datum/component/eye
+ ///The actual eye mob linked to the component
+ var/mob/abstract/eye/component_eye
+ ///What type of /mob/abstract/eye is used
+ var/eye_type = /mob/abstract/eye
+ ///The mob currently looking through the eye
+ var/mob/current_looker
+
+ ///Actions used to pass commands from the eye to the holder. Must be subtypes of /datum/action/eye
+ ///Base type of the action, all subtypes of this are added to actions.
+ var/action_type
+ ///The actions available to the eye
+ var/list/actions = list()
+
+/datum/component/eye/Destroy()
+ unlook()
+ QDEL_LIST(actions)
+ . = ..()
+
+/datum/component/eye/proc/look(var/mob/new_looker, var/list/eye_args)
+ if(new_looker.eyeobj || current_looker)
+ return FALSE
+ LAZYINSERT(eye_args, get_turf(current_looker), 1) //Make sure that a loc is provided to the eye
+ if(!component_eye)
+ component_eye = new eye_type(arglist(eye_args))
+ current_looker = new_looker
+ component_eye.possess(current_looker)
+
+ if(action_type)
+ for(var/atype in subtypesof(action_type))
+ var/datum/action/eye/action = new atype(src)
+ actions += action
+ action.Grant(current_looker)
+
+ //Manual unlooking for the looker
+ var/datum/action/eye/unlook/unlook_action = new(src)
+ actions += unlook_action
+ unlook_action.Grant(current_looker)
+
+ //Checks for removing the user from the eye outside of unlook actions.
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(unlook))
+ RegisterSignal(current_looker, COMSIG_MOVABLE_MOVED, PROC_REF(unlook))
+
+ RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(unlook))
+ RegisterSignal(current_looker, COMSIG_QDELETING, PROC_REF(unlook))
+ RegisterSignal(component_eye, COMSIG_QDELETING, PROC_REF(unlook))
+
+ RegisterSignal(current_looker, COMSIG_MOB_LOGOUT, PROC_REF(unlook))
+ RegisterSignal(current_looker, COMSIG_GLOB_MOB_DEATH, PROC_REF(unlook))
+
+ return TRUE
+
+/datum/component/eye/proc/unlook()
+ if(!isnull(parent))
+ UnregisterSignal(parent, COMSIG_QDELETING)
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+
+ if(!isnull(component_eye))
+ UnregisterSignal(component_eye, COMSIG_QDELETING)
+ component_eye.release(current_looker)
+
+ if(!isnull(current_looker))
+ UnregisterSignal(current_looker, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(current_looker, COMSIG_QDELETING)
+ UnregisterSignal(current_looker, COMSIG_MOB_LOGOUT)
+ UnregisterSignal(current_looker, COMSIG_GLOB_MOB_DEATH)
+ if(current_looker.client)
+ current_looker.client.eye = current_looker
+ current_looker.eyeobj = null
+ QDEL_NULL(component_eye)
+ if(current_looker)
+ for(var/datum/action/A in actions)
+ A.Remove(current_looker)
+ current_looker = null
+
+/datum/component/eye/proc/get_eye_turf()
+ return get_turf(component_eye)
+
+//Every eye created using a subtype of this extension will have this action added for manual unlooking.
+/datum/action/eye/unlook
+ name = "Stop looking"
+ procname = "unlook"
+ button_icon_state = "cancel"
+ target_type = COMPONENT_TARGET
diff --git a/code/datums/components/eye/blueprints.dm b/code/datums/components/eye/blueprints.dm
new file mode 100644
index 00000000000..da5b1fccf06
--- /dev/null
+++ b/code/datums/components/eye/blueprints.dm
@@ -0,0 +1,48 @@
+/datum/component/eye/blueprints
+ eye_type = /mob/abstract/eye/blueprints
+ action_type = /datum/action/eye/blueprints
+
+/datum/action/eye/blueprints
+ eye_type = /mob/abstract/eye/blueprints
+
+/datum/action/eye/blueprints/mark_new_area
+ name = "Mark New Area"
+ procname = "create_area"
+ button_icon_state = "pencil"
+ target_type = EYE_TARGET
+
+/datum/action/eye/blueprints/remove_selection
+ name = "Remove Selection"
+ procname = "remove_selection"
+ button_icon_state = "eraser"
+ target_type = EYE_TARGET
+
+/datum/action/eye/blueprints/edit_area
+ name = "Edit Area"
+ procname = "edit_area"
+ button_icon_state = "edit_area"
+ target_type = EYE_TARGET
+
+/datum/action/eye/blueprints/remove_area
+ name = "Remove Area"
+ procname = "remove_area"
+ button_icon_state = "remove_area"
+ target_type = EYE_TARGET
+
+/datum/action/eye/blueprints/help
+ name = "Help"
+ procname = "help"
+ button_icon_state = "help"
+ target_type = COMPONENT_TARGET
+
+/datum/component/eye/blueprints/proc/help()
+ if(!current_looker)
+ return
+ to_chat(current_looker, SPAN_NOTICE("***********************************************************"))
+ to_chat(current_looker, SPAN_NOTICE("Left Click = Select Area"))
+ to_chat(current_looker, SPAN_NOTICE("Shift Click = Deselect Area"))
+ to_chat(current_looker, SPAN_NOTICE("Control Click = Clear Selection"))
+ to_chat(current_looker, SPAN_NOTICE("***********************************************************"))
+
+/datum/component/eye/blueprints/shuttle
+ eye_type = /mob/abstract/eye/blueprints/shuttle
diff --git a/code/datums/components/eye/freelook.dm b/code/datums/components/eye/freelook.dm
new file mode 100644
index 00000000000..427eb869699
--- /dev/null
+++ b/code/datums/components/eye/freelook.dm
@@ -0,0 +1,13 @@
+//For mobs connecting to the station's cameranet without needing AI procs
+/datum/component/eye/cameranet
+ eye_type = /mob/abstract/eye/cameranet
+
+//For mobs connecting to other visualnets. Pass visualnet as eye_args in look()
+/datum/component/eye/freelook
+ eye_type = /mob/abstract/eye
+
+/datum/component/eye/freelook/proc/set_visualnet(var/datum/visualnet/net)
+ if(component_eye)
+ var/mob/abstract/eye/eye = component_eye
+ if(istype(eye))
+ eye.visualnet = net
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index d279182320c..730f7a6dd74 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -59,6 +59,7 @@
return
/atom/proc/additional_sight_flags()
+ SHOULD_BE_PURE(TRUE)
return 0
/atom/proc/additional_see_invisible()
diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm
index 91a01c93884..a1371121b6c 100644
--- a/code/game/objects/items/blueprints.dm
+++ b/code/game/objects/items/blueprints.dm
@@ -1,275 +1,151 @@
/obj/item/blueprints
- name = "station blueprints"
+ name = "blueprints"
desc = "Blueprints of the station. There is a \"Classified\" stamp and several coffee stains on it."
- icon = 'icons/obj/items.dmi'
+ icon = 'icons/obj/item/tools/blueprints.dmi'
icon_state = "blueprints"
attack_verb = list("attacked", "bapped", "hit")
- var/datum/wires/airlock/blueprint/airlock_wires
- var/datum/wires/vending/blueprint/vending_wires
-
- var/const/AREA_ERRNONE = 0
- var/const/AREA_STATION = 1
- var/const/AREA_SPACE = 2
- var/const/AREA_SPECIAL = 3
-
- var/const/BORDER_ERROR = 0
- var/const/BORDER_NONE = 1
- var/const/BORDER_BETWEEN = 2
- var/const/BORDER_2NDTILE = 3
- var/const/BORDER_SPACE = 4
-
- var/const/ROOM_ERR_LOLWAT = 0
- var/const/ROOM_ERR_SPACE = -1
- var/const/ROOM_ERR_TOOLARGE = -2
-
- var/list/active_blueprints
+ w_class = ITEMSIZE_SMALL
+ var/list/valid_z_levels = list()
+ var/area_prefix
/obj/item/blueprints/Initialize(mapload, ...)
. = ..()
- airlock_wires = new(src)
- vending_wires = new(src)
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/item/blueprints/LateInitialize()
+ . = ..()
+ desc = "Blueprints of the [station_name()]. There is a \"Classified\" stamp and several coffee stains on it."
+ if(set_valid_z_levels())
+ create_blueprint_component()
/obj/item/blueprints/attack_self(mob/user)
- if(use_check_and_message(user, USE_DISALLOW_SILICONS))
- return
- add_fingerprint(user)
- interact()
-
-/obj/item/blueprints/equipped(var/mob/user, var/slot)
- . = ..()
- if(slot == slot_l_hand || slot == slot_r_hand)
- START_PROCESSING(SSprocessing, src)
- LAZYINITLIST(active_blueprints)
-
-/obj/item/blueprints/dropped(mob/user)
- . = ..()
- STOP_PROCESSING(SSprocessing, src)
- if(user.client)
- for(var/thing in active_blueprints)
- user.client.images -= thing
- LAZYCLEARLIST(active_blueprints)
-
-/obj/item/blueprints/process()
- var/mob/user = loc
- if(!istype(user))
- return PROCESS_KILL
-
- var/turf/origin_turf = get_turf(src)
-
- var/list/new_prints = list()
- for(var/turf/T as anything in RANGE_TURFS(world.view, origin_turf))
- for(var/image/I as anything in T.blueprints)
- new_prints += I
-
- var/list/update_remove = active_blueprints - new_prints
- active_blueprints = new_prints - update_remove
-
- for(var/thing in update_remove)
- user.client.images -= thing
-
- for(var/thing in active_blueprints)
- user.client.images += thing
-
-/obj/item/blueprints/Topic(href, href_list)
- ..()
if(use_check_and_message(usr, USE_DISALLOW_SILICONS) || usr.get_active_hand() != src)
return
- if(!href_list["action"])
+ add_fingerprint(user)
+ var/datum/component/eye/blueprints = GetComponent(/datum/component/eye)
+ if(!(user.z in valid_z_levels))
+ to_chat(user, SPAN_WARNING("The markings on this are entirely irrelevant to your whereabouts!"))
return
- switch(href_list["action"])
- if ("create_area")
- if(get_area_type() != AREA_SPACE)
- interact()
- return
- create_area()
- if ("edit_area")
- if (get_area_type()!=AREA_STATION)
- interact()
- return
- edit_area()
- if("airlock_wires")
- airlock_wires.get_wire_diagram(usr)
- if("vending_wires")
- vending_wires.get_wire_diagram(usr)
+ if(blueprints)
+ if(blueprints.look(user, get_look_args())) // Abandon all peripheral vision, ye who enter here.
+ to_chat(user, SPAN_NOTICE("You start peering closely at \the [src]"))
+ return
+ else
+ to_chat(user, SPAN_WARNING("You couldn't get a good look at \the [src]. Maybe someone else is using it?"))
+ return
+ to_chat(user, SPAN_WARNING("The markings on this are useless!"))
-/obj/item/blueprints/interact()
- var/area/A = get_area(src)
- var/text = {"
[src]
- [station_name()] blueprints
- Property of [SSatlas.current_map.company_name]. For heads of staff only. Store in high-secure storage.
- "}
- switch (get_area_type())
- if (AREA_SPACE)
- text += {"
- According the blueprints, you are now in outer space. Hold your breath.
- Mark this place as new area.
- "}
- if (AREA_STATION)
- text += {"
- According the blueprints, you are now in \"[A.name]\".
- You may
- move an amendment to the drawing.
- "}
- if (AREA_SPECIAL)
- text += {"
- This place isn't noted on the blueprint.
- "}
+/obj/item/blueprints/proc/set_valid_z_levels()
+ if(SSatlas.current_map.use_overmap)
+ var/obj/effect/overmap/visitable/sector/S = GLOB.map_sectors["[GET_Z(src)]"]
+ if(!S) //Blueprints are useless now, but keep them around for fluff
+ desc = "Some dusty old blueprints. The markings are old, and seem entirely irrelevant for your wherabouts."
+ return FALSE
+ name += " - [S.name]"
+ desc = "Blueprints of \the [S.name]. There is a \"Classified\" stamp and several coffee stains on it."
+ valid_z_levels += S.map_z
+ area_prefix = S.name
+ return TRUE
+ desc = "Blueprints of the [station_name()]. There is a \"Classified\" stamp and several coffee stains on it."
+ area_prefix = station_name()
+ valid_z_levels = SSatlas.current_map.station_levels
- text += "
View Airlock Wire Schema
"
- text += "
View Vending Machine Wire Schema
"
+/obj/item/blueprints/proc/create_blueprint_component() //So that subtypes can override
+ AddComponent(/datum/component/eye/blueprints)
- text += ""
+/obj/item/blueprints/proc/get_look_args() // ditto
+ return list(valid_z_levels, area_prefix)
- var/datum/browser/blueprints_win = new(usr, "blueprints", capitalize_first_letters(name))
- blueprints_win.set_content(text)
- blueprints_win.open()
+//Blueprints for building exoplanet outposts
+/obj/item/blueprints/outpost
+ name = "outpost blueprints"
+ icon_state = "blueprints2"
-/obj/item/blueprints/proc/get_area_type(var/area/A = get_area(src))
- if(istype(A, /area/space) || istype(A, /area/mine/unexplored))
- return AREA_SPACE
- var/list/SPECIALS = list(
- /area/shuttle,
- /area/centcom,
- /area/tdome,
- /area/antag
+/obj/item/blueprints/outpost/attack_self(mob/user)
+ var/obj/effect/overmap/visitable/sector/S = GLOB.map_sectors["[GET_Z(user)]"]
+ area_prefix = S.name
+ . = ..()
+
+/obj/item/blueprints/outpost/set_valid_z_levels()
+ if(!SSatlas.current_map.use_overmap)
+ desc = "Some dusty old blueprints. The markings are old, and seem entirely irrelevant for your wherabouts."
+ return FALSE
+ desc = "Blueprints for the daring souls wanting to establish a planetary outpost. Has some sketchy looking stains and what appears to be bite holes."
+ var/area/overmap/map = global.map_overmap
+ for(var/obj/effect/overmap/visitable/sector/exoplanet/E in map)
+ valid_z_levels += E.map_z
+ return TRUE
+
+/obj/item/blueprints/shuttle
+ desc_info = "These blueprints can be used to modify a shuttle. In order to be used, the shuttle must be located on its \"Open Space\" z-level. Newly-created areas will be automatically added to the shuttle. If all shuttle areas are removed, the shuttle will be destroyed!"
+ ///Name of the blueprints' linked shuttle. Mapped-in versions should have this preset, or be mapped into the shuttle area itself.
+ var/shuttle_name
+ ///The actual overmap shuttle type, for setting on preset blueprints.
+ var/obj/effect/overmap/visitable/ship/landable/shuttle_type
+
+/obj/item/blueprints/shuttle/set_valid_z_levels()
+ if(SSatlas.current_map.use_overmap)
+ var/area/A = get_area(src)
+
+ if(!shuttle_type)
+ for(var/obj/effect/overmap/visitable/ship/landable/landable in SSshuttle.ships)
+ var/datum/shuttle/shuttle = SSshuttle.shuttles[landable.shuttle]
+ if(shuttle && (A in shuttle.shuttle_area))
+ shuttle_type = landable
+ break
+ else
+ var/obj/effect/overmap/visitable/ship/landable/S = SSshuttle.ship_by_type(shuttle_type)
+ shuttle_type = S
+
+ if(istype(shuttle_type) && !isnull(shuttle_type))
+ if(isnull(shuttle_name))
+ shuttle_name = shuttle_type.shuttle
+ update_linked_name(shuttle_type, null, shuttle_type.name)
+ RegisterSignal(shuttle_type, COMSIG_QDELETING, PROC_REF(on_shuttle_destroy))
+ RegisterSignal(shuttle_type, COMSIG_BASENAME_SETNAME, PROC_REF(update_linked_name))
+ valid_z_levels += shuttle_type.map_z
+ area_prefix = shuttle_type.name
+ return TRUE
+ // The blueprints are useless now, but keep them around for fluff.
+ desc = "Some dusty old blueprints. The markings are old, and seem entirely irrelevant for your wherabouts."
+ return FALSE
+
+/obj/item/blueprints/shuttle/proc/update_linked_name(atom/namee, old_name, new_name)
+ name = "\improper [new_name] blueprints"
+ desc = "Blueprints of \the [new_name]. There are several coffee stains on it."
+ area_prefix = new_name
+
+/obj/item/blueprints/shuttle/proc/on_shuttle_destroy(datum/destroyed)
+ UnregisterSignal(destroyed, COMSIG_QDELETING)
+ UnregisterSignal(destroyed, COMSIG_BASENAME_SETNAME)
+ name = initial(name)
+ desc = "Some dusty old blueprints. The markings are old, and seem entirely irrelevant for your wherabouts."
+ valid_z_levels = list()
+ area_prefix = null
+
+/obj/item/blueprints/shuttle/create_blueprint_component()
+ AddComponent(/datum/component/eye/blueprints/shuttle)
+
+/obj/item/blueprints/shuttle/get_look_args()
+ return list(valid_z_levels, area_prefix, shuttle_name)
+
+/obj/item/blueprints/shuttle/intrepid
+ shuttle_name = "Intrepid"
+ shuttle_type = /obj/effect/overmap/visitable/ship/landable/intrepid
+
+/obj/item/blueprints/shuttle/mining_shuttle
+ shuttle_name = "Spark"
+ shuttle_type = /obj/effect/overmap/visitable/ship/landable/mining_shuttle
+
+/obj/item/blueprints/shuttle/canary
+ shuttle_name = "Canary"
+ shuttle_type = /obj/effect/overmap/visitable/ship/landable/canary
+
+/obj/item/storage/lockbox/shuttle_blueprints //Blueprints for modifying the Horizon's shuttles.
+ name = "shuttle blueprints lockbox"
+ req_access = list(ACCESS_CE)
+ starts_with = list(
+ /obj/item/blueprints/shuttle/intrepid = 1,
+ /obj/item/blueprints/shuttle/mining_shuttle = 1,
+ /obj/item/blueprints/shuttle/canary = 1
)
- for (var/type in SPECIALS)
- if ( istype(A,type) )
- return AREA_SPECIAL
- return AREA_STATION
-
-/obj/item/blueprints/proc/create_area()
- var/res = detect_room(get_turf(usr))
- if(!istype(res,/list))
- switch(res)
- if(ROOM_ERR_SPACE)
- to_chat(usr, "The new area must be completely airtight!")
- return
- if(ROOM_ERR_TOOLARGE)
- to_chat(usr, "The new area too large!")
- return
- else
- to_chat(usr, "Error! Please notify administration!")
- return
- var/list/turf/turfs = res
- var/str = sanitizeSafe(input("New area name:","Blueprint Editing", ""), MAX_NAME_LEN)
- if(!str || !length(str)) //cancel
- return
- if(length(str) > 50)
- to_chat(usr, "Name too long.")
- return
- var/area/A = new
- A.name = str
- A.power_equip = 0
- A.power_light = 0
- A.power_environ = 0
- A.always_unpowered = 0
- move_turfs_to_area(turfs, A)
-
- A.always_unpowered = 0
-
- sorted_add_area(A)
-
- addtimer(CALLBACK(src, PROC_REF(interact)), 5)
- return
-
-
-/obj/item/blueprints/proc/move_turfs_to_area(var/list/turf/turfs, var/area/A)
- A.contents.Add(turfs)
- //oldarea.contents.Remove(usr.loc) // not needed
- //T.forceMove(A) //error: cannot change constant value
-
-
-/obj/item/blueprints/proc/edit_area()
- var/area/A = get_area(src)
- var/prevname = "[A.name]"
- var/str = sanitizeSafe(input("New area name:","Blueprint Editing", prevname), MAX_NAME_LEN)
- if(!str || !length(str) || str==prevname) //cancel
- return
- if(length(str) > 50)
- to_chat(usr, "Text too long.")
- return
- INVOKE_ASYNC(src, PROC_REF(set_area_machinery_title), A, str, prevname)
- A.name = str
- sortTim(GLOB.all_areas, GLOBAL_PROC_REF(cmp_text_asc))
- to_chat(usr, "You set the area '[prevname]' title to '[str]'.")
- interact()
- return
-
-
-
-/obj/item/blueprints/proc/set_area_machinery_title(var/area/A,var/title,var/oldtitle)
- if (!oldtitle) // or replacetext goes to infinite loop
- return
-
- var/static/list/types_to_rename = list(
- /obj/machinery/alarm,
- /obj/machinery/power/apc,
- /obj/machinery/atmospherics/unary/vent_scrubber,
- /obj/machinery/atmospherics/unary/vent_pump,
- /obj/machinery/door
- )
-
- for(var/obj/machinery/M in A)
- if (is_type_in_list(M, types_to_rename))
- M.name = replacetext(M.name, oldtitle, title)
-
- CHECK_TICK
-
-/obj/item/blueprints/proc/check_tile_is_border(var/turf/T2,var/dir)
- if (istype(T2, /turf/space) || istype(T2, /turf/unsimulated/floor/asteroid))
- return BORDER_SPACE //omg hull breach we all going to die here
- if (get_area_type(T2.loc)!=AREA_SPACE)
- return BORDER_BETWEEN
- if (istype(T2, /turf/simulated/wall))
- return BORDER_2NDTILE
- if (!istype(T2, /turf/simulated))
- return BORDER_BETWEEN
-
- for (var/obj/structure/window/W in T2)
- if(turn(dir,180) == W.dir)
- return BORDER_BETWEEN
- if (W.dir in list(NORTHEAST,SOUTHEAST,NORTHWEST,SOUTHWEST))
- return BORDER_2NDTILE
- for(var/obj/machinery/door/window/D in T2)
- if(turn(dir,180) == D.dir)
- return BORDER_BETWEEN
- if (locate(/obj/machinery/door) in T2)
- return BORDER_2NDTILE
-
- return BORDER_NONE
-
-/obj/item/blueprints/proc/detect_room(var/turf/first)
- var/list/turf/found = new
- var/list/turf/pending = list(first)
- while(pending.len)
- if (found.len+pending.len > 300)
- return ROOM_ERR_TOOLARGE
- var/turf/T = pending[1] //why byond havent list::pop()?
- pending -= T
- for (var/dir in GLOB.cardinal)
- var/skip = 0
- for (var/obj/structure/window/W in T)
- if(dir == W.dir || (W.dir in list(NORTHEAST,SOUTHEAST,NORTHWEST,SOUTHWEST)))
- skip = 1; break
- if (skip) continue
- for(var/obj/machinery/door/window/D in T)
- if(dir == D.dir)
- skip = 1; break
- if (skip) continue
-
- var/turf/NT = get_step(T,dir)
- if (!isturf(NT) || (NT in found) || (NT in pending))
- continue
-
- var/tile_is_border = check_tile_is_border(NT,dir)
- if(tile_is_border != BORDER_BETWEEN)
- switch(tile_is_border)
- if(BORDER_NONE)
- pending+=NT
- if(BORDER_2NDTILE)
- found+=NT //tile included to new area, but we dont seek more
- if(BORDER_SPACE)
- return ROOM_ERR_SPACE
- found+=T
- return found
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
index de5d586403f..2f09dc0786b 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
@@ -29,6 +29,8 @@
new /obj/item/crowbar/rescue_axe/red(src)
new /obj/item/device/radio/eng/off(src)
new /obj/item/grenade/chem_grenade/antifuel(src)
+ new /obj/item/storage/lockbox/shuttle_blueprints(src)
+ new /obj/item/blueprints/outpost(src)
// Chief Engineer - Clothing Satchel
// This satchel is used nowhere except in conjunction with the locker above,
diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm
index 1c1c676b0ca..99ce771491c 100644
--- a/code/modules/clothing/masks/miscellaneous.dm
+++ b/code/modules/clothing/masks/miscellaneous.dm
@@ -144,55 +144,27 @@
body_parts_covered = FACE|EYES
action_button_name = "Toggle MIU"
origin_tech = list(TECH_DATA = 5, TECH_ENGINEERING = 5)
- var/active = FALSE
- var/mob/abstract/eye/cameranet/eye
/obj/item/clothing/mask/ai/Initialize()
- eye = new(src)
- eye.name_suffix = "camera MIU"
- . = ..()
-
-/obj/item/clothing/mask/ai/Destroy()
- if(eye)
- if(active)
- disengage_mask(eye.owner)
- qdel(eye)
- eye = null
-
. = ..()
+ AddComponent(/datum/component/eye/cameranet)
/obj/item/clothing/mask/ai/attack_self(mob/user)
if(user.incapacitated())
return
- active = !active
- to_chat(user, SPAN_NOTICE("You [active ? "" : "dis"]engage \the [src]."))
- if(active)
- engage_mask(user)
- else
- disengage_mask(user)
-
-/obj/item/clothing/mask/ai/equipped(mob/user, slot)
- ..(user, slot)
- engage_mask(user)
-
-/obj/item/clothing/mask/ai/dropped(mob/user)
- ..()
- disengage_mask(user)
-
-/obj/item/clothing/mask/ai/proc/engage_mask(mob/user)
- if(!active)
- return
if(user.get_equipped_item(slot_wear_mask) != src)
+ to_chat(user, SPAN_WARNING("You must be wearing \the [src] to activate it!"))
return
-
- eye.possess(user)
- to_chat(eye.owner, SPAN_NOTICE("You feel disoriented for a moment as your mind connects to the camera network."))
-
-/obj/item/clothing/mask/ai/proc/disengage_mask(mob/user)
- if(user == eye.owner)
- to_chat(eye.owner, SPAN_NOTICE("You feel disoriented for a moment as your mind disconnects from the camera network."))
- eye.release(eye.owner)
- eye.forceMove(src)
+ var/datum/component/eye/cameranet/CN = GetComponent(/datum/component/eye)
+ if(!CN)
+ to_chat(user, SPAN_WARNING("\The [src] doesn't respond!"))
+ return
+ if(CN.current_looker)
+ CN.unlook()
+ to_chat(user, SPAN_NOTICE("You deactivate \the [src]."))
+ else
+ CN.look(user)
+ to_chat(user, SPAN_NOTICE("You activate \the [src]."))
/obj/item/clothing/mask/offworlder
name = "scarab scarf"
diff --git a/code/modules/mob/abstract/freelook/ai/eye.dm b/code/modules/mob/abstract/freelook/ai/eye.dm
index 2087eaff9d6..f7eab190687 100644
--- a/code/modules/mob/abstract/freelook/ai/eye.dm
+++ b/code/modules/mob/abstract/freelook/ai/eye.dm
@@ -7,6 +7,7 @@
// Generic version of the AI eye without the AI-specific handling, for things like the Camera MIU mask.
name = "Inactive Camera Eye"
name_suffix = "Camera Eye"
+ living_eye = FALSE
/mob/abstract/eye/cameranet/Initialize()
. = ..()
@@ -16,6 +17,7 @@
name = "Inactive AI Eye"
name_suffix = "AI Eye"
icon_state = "AI-eye"
+ living_eye = FALSE
/mob/abstract/eye/aiEye/Initialize()
. = ..()
diff --git a/code/modules/mob/abstract/freelook/blueprints/blueprints.dm b/code/modules/mob/abstract/freelook/blueprints/blueprints.dm
new file mode 100644
index 00000000000..aaf62c350ab
--- /dev/null
+++ b/code/modules/mob/abstract/freelook/blueprints/blueprints.dm
@@ -0,0 +1,330 @@
+#define MAX_AREA_SIZE 300
+
+/mob/abstract/eye/blueprints
+ /// Associative list of turfs -> boolean validity that the player has selected for new area creation.
+ var/list/selected_turfs = list()
+ ///The overlayed images of the user's selection.
+ var/list/selection_images = list()
+ ///The last turf selected.
+ var/turf/last_selected_turf
+ ///The image overlayed on the last selected turf
+ var/image/last_selected_image
+
+ ///On what z-levels the blueprints can be used to modify or create areas.
+ var/list/valid_z_levels = list()
+
+ ///Displayed to the user to allow them to see what area they're hovering over
+ var/obj/effect/overlay/area_name_effect
+ ///The prefix of the area name effect display
+ var/area_prefix
+
+ ///Displayed to the user on failed area creation
+ var/list/errors = list()
+
+/mob/abstract/eye/blueprints/Initialize(mapload, var/list/valid_zs, var/area_p)
+ . = ..(mapload)
+ if(valid_zs)
+ valid_z_levels = valid_zs.Copy()
+ area_prefix = area_p
+ area_name_effect = new(src)
+
+ area_name_effect.icon_state = "nothing"
+ area_name_effect.maptext_height = 64
+ area_name_effect.maptext_width = 128
+ area_name_effect.layer = FLOAT_LAYER
+ area_name_effect.plane = HUD_PLANE
+ area_name_effect.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ area_name_effect.screen_loc = "LEFT+1,BOTTOM+2"
+
+ last_selected_image = image('icons/effects/blueprints.dmi', "selected")
+ last_selected_image.plane = HUD_PLANE
+ last_selected_image.appearance_flags = NO_CLIENT_COLOR|RESET_COLOR
+
+/mob/abstract/eye/blueprints/Destroy()
+ QDEL_NULL(area_name_effect)
+ errors = null
+ selected_turfs = null
+ valid_z_levels = null
+ last_selected_turf = null
+ . = ..()
+
+/mob/abstract/eye/blueprints/release(var/mob/user)
+ if(owner && owner.client && user == owner)
+ owner.client.images.Cut()
+ . = ..()
+
+/mob/abstract/eye/blueprints/proc/create_area()
+ var/area_name = sanitizeSafe(tgui_input_text(owner, "New area name: ", "Area Creation", "", MAX_NAME_LEN))
+ if(!area_name || !length(area_name))
+ return
+ if(length(area_name) > 50)
+ to_chat(owner, SPAN_WARNING("That name is too long!"))
+ return
+
+ if(!check_selection_validity())
+ to_chat(owner, SPAN_WARNING("Could not mark area: [english_list(errors)]!"))
+ return
+
+ var/area/A = finalize_area(area_name)
+ for(var/turf/T in selected_turfs)
+ ChangeArea(T, A)
+ remove_selection() // Reset the selection for clarity.
+
+/mob/abstract/eye/blueprints/proc/finalize_area(var/area_name)
+ var/area/A = new()
+ A.name = area_name
+ var/area/old_area = get_area(selected_turfs[1])
+ if(old_area.area_flags & AREA_FLAG_INDESTRUCTIBLE_TURFS) //to prevent new areas on exoplanets being ventable
+ A.area_flags |= AREA_FLAG_INDESTRUCTIBLE_TURFS
+ A.power_equip = FALSE
+ A.power_light = FALSE
+ A.power_environ = FALSE
+ A.always_unpowered = FALSE
+ return A
+
+/mob/abstract/eye/blueprints/proc/remove_area()
+ var/area/A = get_area(src)
+ if(!check_modification_validity())
+ return
+ if(A.apc)
+ to_chat(owner, SPAN_WARNING("You must remove the APC from this area before you can remove it from the blueprints!"))
+ return
+ to_chat(owner, SPAN_NOTICE("You scrub [A.name] off the blueprints."))
+ log_and_message_admins("deleted area [A.name] via station blueprints.")
+ var/background_area = /area/space
+ var/obj/effect/overmap/visitable/sector/sector = GLOB.map_sectors["[A.z]"]
+ var/obj/effect/overmap/visitable/sector/exoplanet/exoplanet = sector
+ if(istype(exoplanet))
+ background_area = exoplanet.planetary_area
+ for(var/turf/T in A.contents)
+ ChangeArea(T, background_area)
+ if(!locate(/turf) in A)
+ qdel(A)
+
+/mob/abstract/eye/blueprints/proc/edit_area()
+ var/area/A = get_area(src)
+ if(!check_modification_validity())
+ return
+ var/prevname = A.name
+ var/new_area_name = sanitizeSafe(input("Edit area name:","Area Editing", prevname), MAX_NAME_LEN)
+ if(!new_area_name || !LAZYLEN(new_area_name) || new_area_name==prevname)
+ return
+ if(length(new_area_name) > 50)
+ to_chat(owner, SPAN_WARNING("Text too long."))
+ return
+
+ // Adjusting titles in the old area.
+ var/static/list/types_to_rename = list(
+ /obj/machinery/alarm,
+ /obj/machinery/power/apc,
+ /obj/machinery/atmospherics/unary/vent_pump,
+ /obj/machinery/atmospherics/unary/vent_pump,
+ /obj/machinery/door
+ )
+ for(var/obj/machinery/M in A)
+ if(is_type_in_list(M, types_to_rename))
+ M.name = replacetext(M.name, prevname, new_area_name)
+ A.name = new_area_name
+ to_chat(owner, SPAN_NOTICE("You set the area '[prevname]' title to '[new_area_name]'."))
+
+/mob/abstract/eye/blueprints/ClickOn(atom/A, params)
+ params = params2list(params)
+ if(!canClick())
+ return
+ if(params["left"])
+ update_selected_turfs(get_turf(A), params)
+ if(params["ctrl"]) //Shift-click to clear the selection
+ remove_selection()
+
+/mob/abstract/eye/blueprints/proc/update_selected_turfs(var/turf/next_selected_turf, var/list/params)
+ if(!next_selected_turf)
+ return
+ if(!last_selected_turf) //The player has only placed one corner of the block
+ last_selected_turf = next_selected_turf
+ last_selected_image.loc = last_selected_turf
+ return
+ if(last_selected_turf.z != next_selected_turf.z) // No multi-Z areas. Contiguity checks this as well, but this is cheaper.
+ return
+
+ var/list/new_selection = block(last_selected_turf, next_selected_turf)
+ if(params["shift"]) //Right-click to remove areas from the selection
+ selected_turfs -= new_selection
+ else
+ selected_turfs |= new_selection
+
+ last_selected_image.loc = null //Remove last selected turf indicator image
+ check_selection_validity()
+ update_images()
+
+/mob/abstract/eye/blueprints/proc/check_selection_validity()
+ . = TRUE
+ errors.Cut()
+
+ if(!LAZYLEN(selected_turfs)) //Sanity check
+ errors |= "no turfs are selected"
+ return FALSE
+ if(LAZYLEN(selected_turfs) > MAX_AREA_SIZE)
+ errors |= "selection exceeds max size"
+ return FALSE
+ for(var/turf/T in selected_turfs)
+ var/turf_valid = check_turf_validity(T)
+ . = min(., turf_valid)
+ selected_turfs[T] = turf_valid
+ if(!.) return
+ . = check_contiguity()
+
+/mob/abstract/eye/blueprints/proc/check_turf_validity(var/turf/T)
+ . = TRUE
+ if(!T)
+ return FALSE
+ if(!(T.z in valid_z_levels))
+ errors |= "selection isn't marked on the blueprints"
+ . = FALSE
+ var/area/A = T.loc
+ if(!A) //Safety check
+ errors |= "selection overlaps unknown location"
+ return FALSE
+ if(!(A.area_flags & AREA_FLAG_IS_BACKGROUND)) // Cannot create new areas over old ones.
+ errors |= "selection overlaps other area"
+ . = FALSE
+ if(istype(T, (A.base_turf ? A.base_turf : /turf/space)))
+ errors |= "selection is exposed to the outside"
+ . = FALSE
+
+/mob/abstract/eye/blueprints/proc/check_contiguity()
+ var/turf/start_turf = pick(selected_turfs)
+ var/list/pending_turfs = list(start_turf)
+ var/list/checked_turfs = list()
+
+ while(pending_turfs.len)
+ if(LAZYLEN(checked_turfs) > MAX_AREA_SIZE)
+ errors |= "selection exceeds max size"
+ break
+ var/turf/T = pending_turfs[1]
+ pending_turfs -= T
+ for(var/dir in GLOB.cardinal) // Floodfill to find all turfs contiguous with the randomly chosen start_turf.
+ var/turf/NT = get_step(T, dir)
+ if(!isturf(NT) || !(NT in selected_turfs) || (NT in pending_turfs) || (NT in checked_turfs))
+ continue
+ pending_turfs += NT
+
+ checked_turfs += T
+
+ var/list/incontiguous_turfs = (selected_turfs.Copy() - checked_turfs)
+
+ if(LAZYLEN(incontiguous_turfs)) // If turfs still remain in incontiguous_turfs, there are non-contiguous turfs in the selection.
+ errors |= "selection must be contiguous"
+ return FALSE
+
+ return TRUE
+
+/mob/abstract/eye/blueprints/proc/check_modification_validity()
+ . = TRUE
+ var/area/A = get_area(src)
+ if(!(A.z in valid_z_levels))
+ to_chat(owner, SPAN_WARNING("The markings on this are entirely irrelevant to your whereabouts!"))
+ return FALSE
+ if(A in SSshuttle.shuttle_areas)
+ to_chat(owner, SPAN_WARNING("This segment of the blueprints looks far too complex. Best not touch it!"))
+ return FALSE
+ if(!A || (A.area_flags & AREA_FLAG_IS_BACKGROUND))
+ to_chat(owner, SPAN_WARNING("This area is not marked on the blueprints!"))
+ return FALSE
+
+/mob/abstract/eye/blueprints/proc/remove_selection()
+ selected_turfs.Cut()
+ last_selected_turf = null
+ update_images()
+
+/mob/abstract/eye/blueprints/proc/update_images()
+ if(!owner || !owner.client)
+ return
+
+ owner.client.images -= selection_images
+ selection_images.Cut()
+ if(LAZYLEN(selected_turfs))
+ for(var/turf/T in selected_turfs)
+ var/selection_icon_state = selected_turfs[T] ? "valid" : "invalid"
+ var/image/I = image('icons/effects/blueprints.dmi', T, selection_icon_state)
+ I.plane = HUD_PLANE
+ I.appearance_flags = NO_CLIENT_COLOR
+ selection_images += I
+
+ owner.client.images |= last_selected_image
+ owner.client.images += selection_images
+
+/mob/abstract/eye/blueprints/setLoc(T)
+ . = ..()
+ var/style = "font-family: 'Fixedsys'; -dm-text-outline: 1 black; font-size: 11px;"
+ var/area/A = get_area(src)
+ if(!A)
+ return
+ area_name_effect.maptext = "[area_prefix], [A.name]"
+
+/mob/abstract/eye/blueprints/additional_sight_flags()
+ return SEE_TURFS|BLIND
+
+/mob/abstract/eye/blueprints/apply_visual(mob/living/M)
+ M.overlay_fullscreen("blueprints", /obj/screen/fullscreen/blueprints)
+ M.client.screen += area_name_effect
+ M.add_client_color(/datum/client_color/monochrome)
+
+/mob/abstract/eye/blueprints/remove_visual(mob/living/M)
+ M.clear_fullscreen("blueprints", 0)
+ M.client.screen -= area_name_effect
+ M.remove_client_color(/datum/client_color/monochrome)
+
+//Shuttle blueprint eye
+/mob/abstract/eye/blueprints/shuttle
+ var/shuttle_name
+
+/mob/abstract/eye/blueprints/shuttle/Initialize(mapload, list/valid_zs, area_p, shuttle_name)
+ . = ..()
+ src.shuttle_name = shuttle_name
+
+/mob/abstract/eye/blueprints/shuttle/check_modification_validity()
+ . = TRUE
+ var/area/A = get_area(src)
+ if(!(A.z in valid_z_levels))
+ to_chat(owner, SPAN_WARNING("The markings on this are entirely irrelevant to your whereabouts!"))
+ return FALSE
+ var/datum/shuttle/our_shuttle = SSshuttle.shuttles[shuttle_name]
+ if(!(A in our_shuttle.shuttle_area))
+ to_chat(owner, SPAN_WARNING("That's not a part of the [our_shuttle.name]!"))
+ return FALSE
+ if(!A || (A.area_flags & AREA_FLAG_IS_BACKGROUND))
+ to_chat(owner, SPAN_WARNING("This area is not marked on the blueprints!"))
+ return FALSE
+
+/mob/abstract/eye/blueprints/shuttle/remove_area()
+ var/area/A = get_area(src)
+ if(!check_modification_validity())
+ return
+ if(A.apc)
+ to_chat(owner, SPAN_WARNING("You must remove the APC from this area before you can remove it from the blueprints!"))
+ return
+ var/datum/shuttle/our_shuttle = SSshuttle.shuttles[shuttle_name]
+ if(our_shuttle.shuttle_area.len == 1) //If it's the last shuttle area, make sure that we don't break the shuttle in question.
+ to_chat(owner, SPAN_WARNING("You cannot delete the last area in a shuttle!"))
+ return
+ to_chat(owner, SPAN_NOTICE("You scrub [A.name] off the blueprints."))
+ log_and_message_admins("deleted area [A.name] from [our_shuttle.name] via shuttle blueprints.")
+ var/background_area = /area/space
+ var/obj/effect/overmap/visitable/sector/sector = GLOB.map_sectors["[A.z]"]
+ var/obj/effect/overmap/visitable/sector/exoplanet/exoplanet = sector
+ if(istype(exoplanet))
+ background_area = exoplanet.planetary_area
+ for(var/turf/T in A.contents)
+ ChangeArea(T, background_area)
+ if(!(locate(/turf) in A))
+ qdel(A) // uh oh, is this safe?
+
+/mob/abstract/eye/blueprints/shuttle/finalize_area(area_name)
+ var/area/A = ..(area_name)
+ var/datum/shuttle/our_shuttle = SSshuttle.shuttles[shuttle_name]
+ our_shuttle.shuttle_area += A
+ SSshuttle.shuttle_areas += A
+ RegisterSignal(A, COMSIG_QDELETING, TYPE_PROC_REF(/datum/shuttle, remove_shuttle_area))
+ return A
+
+#undef MAX_AREA_SIZE
diff --git a/code/modules/mob/abstract/freelook/eye.dm b/code/modules/mob/abstract/freelook/eye.dm
index e7d4ce0e832..6b93d7b136f 100644
--- a/code/modules/mob/abstract/freelook/eye.dm
+++ b/code/modules/mob/abstract/freelook/eye.dm
@@ -24,6 +24,11 @@
var/ghostimage = null
var/datum/visualnet/visualnet
+ ///Set if the eye uses special click handling. Distinct from parent mob click handling for AI eye.
+ var/click_handler_type = /datum/click_handler/eye
+
+ ///Whether or not our eye uses normal living vision handling
+ var/living_eye = TRUE
/mob/abstract/eye/New()
ghostimage = image(src.icon,src,src.icon_state)
@@ -78,6 +83,10 @@
name = "[owner.name] ([name_suffix])"
if(owner.client)
owner.client.eye = src
+ LAZYDISTINCTADD(owner.additional_vision_handlers, src)
+ apply_visual(owner)
+ if(click_handler_type)
+ owner.PushClickHandler(click_handler_type)
setLoc(owner)
visualnet.update_eye_chunks(src, TRUE)
@@ -86,8 +95,14 @@
return
if(owner.eyeobj != src)
return
+ remove_visual(owner)
+ LAZYREMOVE(owner.additional_vision_handlers, src)
visualnet.remove_eye(src)
owner.eyeobj = null
+ if(owner.client)
+ owner.client.eye = owner
+ if(click_handler_type)
+ owner.RemoveClickHandler(click_handler_type)
owner = null
name = initial(name)
@@ -144,3 +159,28 @@
else
sprint = initial
return 1
+
+/mob/abstract/eye/ClickOn(atom/A, params)
+ if(owner)
+ return owner.ClickOn(A, params)
+ return ..()
+
+/mob/abstract/eye/proc/apply_visual(mob/M)
+ if(M != owner || !M.client)
+ return FALSE
+ return TRUE
+
+/mob/abstract/eye/proc/remove_visual(mob/M)
+ if(M != owner || !M.client)
+ return FALSE
+ return TRUE
+
+/datum/click_handler/eye/OnClick(atom/A, params)
+ var/mob/abstract/eye = user.eyeobj
+ if(!eye) //Something has broken, ensure the click handler doesn't stick around
+ user.RemoveClickHandler(src)
+ return
+ eye.ClickOn(A, params)
+
+/datum/click_handler/eye/OnDblClick(atom/A, params)
+ OnClick(A, params)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 2183b5b93c7..f58ba362bfb 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1867,6 +1867,8 @@
..()
if(update_hud)
handle_regular_hud_updates()
+ if(eyeobj)
+ eyeobj.remove_visual(src)
/mob/living/carbon/human/can_stand_overridden()
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 1fcb3dd59bb..1bab06f2b12 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -146,6 +146,7 @@
else if(viewflags)
set_sight(viewflags)
else if(eyeobj)
+ eyeobj.apply_visual(src)
if(eyeobj.owner != src)
reset_view(null)
else if(!client.adminobs)
@@ -160,7 +161,7 @@
/mob/living/proc/update_sight()
set_sight(0)
- if(stat == DEAD || eyeobj)
+ if(stat == DEAD || (eyeobj && !eyeobj.living_eye))
update_dead_sight()
else
update_living_sight()
diff --git a/code/modules/overmap/exoplanets/decor/_areas.dm b/code/modules/overmap/exoplanets/decor/_areas.dm
index c568b791b95..0520474dd93 100644
--- a/code/modules/overmap/exoplanets/decor/_areas.dm
+++ b/code/modules/overmap/exoplanets/decor/_areas.dm
@@ -2,7 +2,7 @@
name = "\improper Planetary surface"
ambience = list('sound/effects/wind/wind_2_1.ogg','sound/effects/wind/wind_2_2.ogg','sound/effects/wind/wind_3_1.ogg','sound/effects/wind/wind_4_1.ogg','sound/effects/wind/wind_4_2.ogg','sound/effects/wind/wind_5_1.ogg')
always_unpowered = 1
- area_flags = AREA_FLAG_INDESTRUCTIBLE_TURFS
+ area_flags = AREA_FLAG_INDESTRUCTIBLE_TURFS|AREA_FLAG_IS_BACKGROUND
is_outside = OUTSIDE_YES
/area/exoplanet/adhomai
diff --git a/code/modules/overmap/overmap_shuttle.dm b/code/modules/overmap/overmap_shuttle.dm
index f300afc3350..a5afbe883c2 100644
--- a/code/modules/overmap/overmap_shuttle.dm
+++ b/code/modules/overmap/overmap_shuttle.dm
@@ -10,6 +10,10 @@
/datum/shuttle/autodock/overmap/New(var/_name, var/obj/effect/shuttle_landmark/start_waypoint)
..(_name, start_waypoint)
refresh_fuel_ports_list()
+ for(var/area/A in shuttle_area) //If shuttles initialize after the blueprints, they won't set correctly so we do it here.
+ var/obj/item/blueprints/shuttle/blueprints = locate() in A
+ if(blueprints)
+ blueprints.set_valid_z_levels()
/datum/shuttle/autodock/overmap/proc/refresh_fuel_ports_list() //loop through all
fuel_ports = list()
diff --git a/code/modules/overmap/sectors.dm b/code/modules/overmap/sectors.dm
index a52c8766ba9..0f9e70d826f 100644
--- a/code/modules/overmap/sectors.dm
+++ b/code/modules/overmap/sectors.dm
@@ -207,6 +207,7 @@ var/global/area/overmap/map_overmap // Global object used to locate the overmap
return
if(comms_support)
update_away_freq(name, get_real_name())
+ SEND_SIGNAL(src, COMSIG_BASENAME_SETNAME, args)
name = get_real_name()
/obj/effect/overmap/visitable/proc/get_real_name()
diff --git a/code/modules/shuttles/shuttle.dm b/code/modules/shuttles/shuttle.dm
index 0e0c6af4b5f..04129980def 100644
--- a/code/modules/shuttles/shuttle.dm
+++ b/code/modules/shuttles/shuttle.dm
@@ -49,6 +49,7 @@
if(!istype(A))
CRASH("Shuttle \"[name]\" couldn't locate area [T].")
areas += A
+ RegisterSignal(A, COMSIG_QDELETING, PROC_REF(remove_shuttle_area))
shuttle_area = areas
if(initial_location)
@@ -311,3 +312,10 @@
/datum/shuttle/proc/on_move_interim()
return
+
+/datum/shuttle/proc/remove_shuttle_area(area/area_to_remove)
+ UnregisterSignal(area_to_remove, COMSIG_QDELETING)
+ SSshuttle.shuttle_areas -= area_to_remove
+ shuttle_area -= area_to_remove
+ if(!length(shuttle_area))
+ qdel(src)
diff --git a/html/changelogs/RustingWithYou - iseeyou.yml b/html/changelogs/RustingWithYou - iseeyou.yml
new file mode 100644
index 00000000000..640f9070b75
--- /dev/null
+++ b/html/changelogs/RustingWithYou - iseeyou.yml
@@ -0,0 +1,60 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# - (fixes bugs)
+# wip
+# - (work in progress)
+# qol
+# - (quality of life)
+# soundadd
+# - (adds a sound)
+# sounddel
+# - (removes a sound)
+# rscadd
+# - (adds a feature)
+# rscdel
+# - (removes a feature)
+# imageadd
+# - (adds an image or sprite)
+# imagedel
+# - (removes an image or sprite)
+# spellcheck
+# - (fixes spelling or grammar)
+# experiment
+# - (experimental change)
+# balance
+# - (balance changes)
+# code_imp
+# - (misc internal code change)
+# refactor
+# - (refactors code)
+# config
+# - (makes a change to the config files)
+# admin
+# - (makes changes to administrator tools)
+# server
+# - (miscellaneous changes to server)
+#################################
+
+# Your name.
+author: RustingWithYou
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
+# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - qol: "Reworks blueprints to use an eye for area modification."
+ - rscadd: "Adds outpost blueprints for creating areas on exoplanets, and shuttle blueprints for modifying shuttles."
+ - qol: "Moves eye creation to a component."
diff --git a/icons/effects/blueprints.dmi b/icons/effects/blueprints.dmi
new file mode 100644
index 0000000000000000000000000000000000000000..967aad8e08f4f96edbad58230a0419e5bd17faa9
GIT binary patch
literal 486
zcmV@P)912ZXF4VHr#g2PYl!{-@(zsdeSu)%TIfW0niH*Q}V_oj~9qT?0M
z27ubpjx2lhE7Bi!MH#`Z$Rh-Y;1C>wLvI}P6pS>%NN2YC!qN<{9
literal 0
HcmV?d00001
diff --git a/icons/obj/action_buttons/actions.dmi b/icons/obj/action_buttons/actions.dmi
index 400db51debe52607c862d9316828fb1ad5a15ca2..94314280407c8f4eb741005bb1bcdf657ecae176 100644
GIT binary patch
delta 12694
zcma)ibx>8`7w?5jccXN7cY_y@l14$KLqeoM`dso7(yerNBi$t+-6h?PbiVuj&Afl#
zyfd@s{(e^KX
z^BJ0K+tbI*ryY^a#N>Q<8kp`w0|47cnmaiFO4wZ
zIr+9b32w;;BSscM0#D0fIPK9yR|)krwW6i0+j0S5p2LOC_=uaT$Tf(~qxcq@OAU+K
z@7!=xj&HcTX~P}g)Xfdo?0;il3hWUJS=X)~3Rv^|-QN|&6(+1GonjQ^9TM!KsB+`v
zO~~Q&xt1AG#N%>mUNm#?W&&7Kh;f%XF5gApClYe->rREv288TT`{2GS(nWihavxN?
zl?22Jg4!xJl`%fJ%yyfkbR-%g&Y0~*CE6nvt9_=R*pF<@0spojK2E#)E*Y4%ULB+9
z%=aCOu;B036`?HlJE>Pk6*MjmKaUf?{?@CJh||wpO%!Z-319O0Vo49oDBG>rOmHdu
zHhXnk>dK|p!1i7lyG?KTGTQ4S1(*Asof;aLw_b&s)r`}+sY@`AgiMoUT6a;<3=w8J
zJ2GJ2{k8&^{}9_TIQrjH`V;lhR@?|rfE2cFQe2M!!S6l_f?6WOD|SP^CGAY~tC!&=
zL396W8QQq$y)vnFWbP4uGkcsk*|q^pCIztF;7)By%Zq+<@kWO~sM7C|fO*cgt;l
zM=eherVr#r>LrA)IytFo1rdW(fs=F9nQzoDX@B4S%tO}C63(Vk%pR)}LcBsm=d*I^
zA-BKR>1(GloWOQCoTtCP<*{I^q$U5ym%kAb4_nvS*-D*02cc(?@YEj+y|?a~44K3b
zP$KslJiXi8`gSoqsPrS=C62`XZ>6r{g?RERt?1q5zo%z=$+n1?xW}5a3?LkrTj&5n
ziM_PQ$dJ8Xo1V*QQbJ3t)~D^W_r;4+#|PSZ#-60uMB$XJ=d&?mRdlb!&X!q=lrTXx
zVVpX$M=p%x7<@uP7z;`?@YaR)3=WMEF5~)2d3VxY5}V!VntMgP{#=cQ(@F18|Di&W~Y**0p_Ohzq)XZWG4Or$f3;kNG->F)HDbffsUh
z0|X^(B|Dw^5^6k*%tLc`0S*qXO9h#JJ@Sn
z(rkU9A^P!R>{91+I-$fgknbBs!~t&D;#{dg{remOCyM>A11
zRhymr9Imf!^UH!P>P}x3gkV*bY
zoxVJApLq61Dj^g%n3$Sy{ZrFC>Y>jxS&(h*N6~ZH$bWBDTKizwnMbr4rV7xFqHP~8
z)inHMa}-w@pF-J=nDGz1i@UI9n$Fd^-9xZ@_6$3_H15aW5x^q)S8sDh5d4eJo{Rf2
zA*Rrd%CRorhoBGKejO4sY2*ATq|3&@cSGZoE>Ckh9y6qqlA`X%TK0o3|S3ViRPj-jt`dnMDp3jMr{`oNxmWn%fd%#>Ibzxp4#GX%{(
zL>Ho~tJ`Z1bS3a*fhJlFtr^Bl
zpJ6qn2{3K^q<0gMMf;ojG5l0lDq6xynxK6-a5SCTbhG*Wt$G{g=^qYx1sjybXFo+n
zN)ZLD>r83U(d%7M+2H|&X!J?QTjGiL`OXdB&JsERzdNOiy#s*x}%vE$@
z0;;v2$2PUCMEI4MZ+p1UTcve$_9Wie?aI1SAH*a?p~wHY!&`$HraexQ@!-9ec(VZC
zO(MbJmy22Q!HZJn9IF#K5cFiq1q5n4{SMgNjP?sF9E?A#o2JnseYvK1zpSEPA}U2y
z#9QTBg<5E27h1TBHY2WI{NQ<9Kd0D;J_xxAqOt}SB(wW`d^@?YinFRHiHdJEsX!swY7?6D4IOgwDTE7%+#Z%hdt
z%)1P1<4R!?A|oS3!RH>PAN?1ay{#-p86umui!VD|Z|NZ*fMW6c@hu$zjXR*lS1f@y
z@-lvG8-XXzo})t*x&!RFs|E=Obn{uz1%*=Lxru;9v-HZoV<3$k(tm3S3F#k~{Xn2E
z3qMptjwb(*!6%X~1KD^IVq-@(c4LgkD7~|>wtoDQ6>I{37ub)?Dx+Eos8&d6sdx!J
zry5LMs8812&*j-%Rn`M|TC$snn*m>tA9gRSl(wH5I@^a)rrz;~E+K=g?Y9SfeEGll
z{gEB4uk7Grb;o_Y9ngREz#THMN&EElw0C-{U~Ej?YQ$YrQ&aaq12N+xqED)8s5g0-
z9Z7jwq0Y)b|8jYALoW@>q;wi!7UwaYeN35A4c^`PWRKorNJs~`Ri%vd+SWRe)TVaY
z1}AQcVAA#uIXS)fEG;c5X=xD<5E90wq=eSh2~@PS{KSLEP=jR9-gtY9hc%#W@9ts(
zMB-v%F{!CxQ8<3~J0~Y6wBFK2jyR!a-WK=>(6HqE^`%j)y|_EiSZ0ayngi-zdL9%}
zLW55=wF(Fzsas%a6;#A5M@&roCaSc$dQj5dz+WnEbFTbT;k}iXvtzZRs#`F-xmRE<
zBGIr{_ZwsuR#tR$bf297qo}zP3|xE-z-H@Nn$`r
z^Rrpx;&6U-`Ip47ozNtK0&7H14wiGnE+gdopasZ#W`bUFgk9qec?l;
z?~_}OP7oU#nxYeVGhz1feD-=S|KQ
z#4vhsL-w_zN5Tm;I=bf=YAbi+p{MUuzga^g1?YzmNz0w@!Br>0OhxwAnwgo8qw#pN
zflg3brKVbn7;N|Un(!g&%#!Vf!G{k{s{w{53Acc#T
zqM{<&yfKcB56H-a#jA~qDLyQSAluR6^XHtVrjOV4p-sLgoL22Eel5qR0x_#RL^qe3uSZJ@VbpO7HID@lZzoIu`@hs
zTBsULP*~W7l9Cb$?Kr4Enwnb$n%4_3q}_r#w*vme3;O8eL>UGJ|dq?vFHJpgdth(Fjp_MOA+G`#z)wxR{gn%b6M_cG
z6DkWG*Z30H2PyHjoLMuB)^nW3qdxaF7y5~g-2{5Mw|SeZY4iTgo~u}1K>-~pfD;gK#|3JFB=wgVLlLL
z3a1L`ECRPvJbP#7GtOBA4lzTMC`~m~BW>l0HX7d{cOV3FxZ4jBw;`LEFIV(KRL}Lh
zJ{=J^B-q$F+cX6unW2%K4U(W2hT8`hCRwS0O`or1v!J5o2aKv4Vx1FTahHwU6&9X?j$R&t+Ou1{9j>-^*w5t-cC
znpNUtnbJ|kqEF&1)}IPS1k)LAX?kNzBgm)Uwp47xaPCHwPnt=fq0&bz2Jc2PMBHEu|R7l`P2bZk)5B^|KRn)@B*Tu
zaN<%^Sr2~@5QMcC6)AeWq!(*nVq;^2Kn+Wgsr+}_)$S+DueV>`k$vE?&jfVezKtH=
zoNnj>df-yVQ^15#*#{@g>=Mlvnj0>HYEi!+()Ap!`^G#|6~vZ0XYyBtl0H6i?i$&^
zkpV(5!b^&=G0qOxc2FzY#GFi)iWbChU3v6#y)!I@Lj5$B%p(oQ!|-e8`p8-H;+m7#
zxOd*$-}sal$E9^j8N}2;_Pg%zUOLk0zkj(NX@B1ID)@AJf<#u(7#I&7Ps1f?g0kTD
zcy}yQ!NH-jtoh1I(*5*rq^_~=dlv7j8Qp5{8~eR0ud1g&Gi2;!HQ&Sj?>Q3LX<`&m
zQmPL=^I!SHFOb$TNV|ha5Q2$m<9i>JcT6UDCw>KKzKNx0n4lm8q7-FWh30ZU-@2L2
zk$i*uk(-oMm#e?=Dl*in=qZ3fUM}Np=(U!hKR7_sh8M**m-fowd+H$1tyMZgL9?6+X1tH+-tG9NTxr
z=+@TNWj;)ssImcEup4aPqi~%8?(x>uGLKO@F|pZ&jvl70TDqumMUV*ZK@<6}`VPB7
zT!b2FDjie-<>uwE}6;h
zTE`llknbO(aPr=@uq_@i7PA`+ruuT4CW5Ye6&-tNfme9T_!ArxjoBYwy)C_S+*WP0
zIe($yJ-mSWNpmO-ioNl9el(uLNXK88tycWl;U{UpMf%*l558f}JedS{3hIuzuDU;r
zcm;MrgE2|XcK)Qsq3`KI2nXq5`G{z)5>R8Gtu$>T1v$};9t!r|fV>#-6PAq41T!Ug
z5Mg5rfF0Ed$2%?b!X-LSh#Z^7<4GESw$CBiq3{@?5_xuRLx3hzL^ARY_pwy|Pr#90
zZpoayIMs)H2&dD4T>UE+q}+|E$w|7)paM%}`QL|EDBthy2OJ#wZ>U~CprDK~j{mme
zi}d%Kj8yE(Id)gQBY4cHrWRFpsMqL73j(rAN|eYK-)}#Man-3uBcTY1J40LIf
ze{}uSXrA{=L4qid6O(|RhR|@tQF9)w^?qN3N-DXY|Tk;+(2-lWeMN
zHIjY5*v@bznM}f4Ir-@Uxs&JE?rVD#5UdB^+1UN(;W*D+!tD~hRdd1in+PwzgWT}1AHKon9+a&L52Z}BUF*=Ng*V>g_es(=^2MguPS-Pe=aHC}0txcVhyVPp@(
zUF(iP#MDg>!Lr5Hm%!z9u`?B~KyMBuPU$*s$NcemH(Wt+XSeI>>az0l<r2IuFZKM>$5TA!?6EXGhE?UZdtxu
zOkCHV(B8%sdkE}fRTI_XepN>YwOG1Z{a9esO-?WWB|!w5uX^z3)4y#ge3ls3r>oI6
zQw9E;BD7eTZe==>vieX^N09~!0b$d_7i()ILN&QOh=CBQt0sC(NXXmAu+Prb)}#6V
z-SlVi>H$i7n6|b!aO5`6qlcKcZ6P2a5aQ0?3V+)d$t3e1ehc}3ocd!j5}qM6Mk-)1
zw9Af(t6=y+=9K%xpZD*dUyC7hBFzu!_HDfwTH;WwcMOqqR6G2~fZjBMyzO_yLuqb@
z-2e{fy;*SI42^zAtu=#VAK{0<#W`KeHJC-2vpg_ngQJK*$gIZT8uqHb0Ez}1{=A7r
zdw|X;m3`fz!K?c|*0zPR3Ov)k?Nw5G%;Qi*w^LG*_3{M{*r#7!*lL51^!TxlWH}V8
zA<8}=8c(y2zl$yNdQm}SYX6p|Tie+Jfz-y6j05)-NjNBEArnA|LomdPmU4D+G1t#b
z@Tj6AJP`DL?Ar*1Q`Fn)PnszSI%!zaY--24Svtz>$Ppyn^mu${$7g!=vZ@#~DK>
zNxe+#`figSTj3Ck?p58pQsh^c>$^e@;6RpjS0ntUoVh!r_1
z5woBHaYNiwGz>>N3bfblv!*Qj^?ZKQVR}ARticwq#_>vO;$Cot@_>Szy(YreTTmmA
zP1(3BVDxIlA(!j79XifH!iJNWAd73t)eWrmwP1fGy>H*
zLnchg5)#9bb{;7IExItiPLSdjA`QRg^7KfuW6skjXtsI(db~A2#E_DQjIT?oRFry5
z)ETAC-V?J;vl`Ia_yG|m(;wY|PCH8zu`!YV`HToQsr&_IdPcS3n@EgMts<8>ngOwS
z!t!KYrXYqW>U)&~>0ecRmm>F{>Kg*wX=
zVEYS2*OMktW$AgknO>V3)Lp9v8)uQZo3m&sk^(d$cyO9p8di}Em4w9R%4F}m
z*$CK4b#xo!ay@BCqNn_gk}tKE*EM>c%A6M(OOoB=D!e0Bv)g~Jx6uT^C_jZ&LBf4nx+ZxMk0Si52m8?+#h3&V&TL`ryAj$gD7<=E)(
z9u>avv!T`S=5fp%Sk+YzV%foNEoEI?i40-B6Ko99z>=A8VLsb?ero+xf@1f>imqaJ4?7{}3rzp+JKCB#JQF5CLkXm1!`P4*moA8603+vF!a7p2z_pvl~uln&Sw
z+MOfhV0E3eOhMZ9`(KJ0I$E&)StaC{2xR@NrLY;@1a)$+9QMGYv7H^_a89QWLujCh
zu(4nic8d2X;A-6^gn84FE%}|`F{*CNF9qhX(*>N4+j#_90n9mPCeoVUkSLC-m`Xt0
zzm)ncS^J!o^D_DA`h(0!%=hrI1j9$4xtmpOaHG1p&s)Bz`Z}aA+!p1^Ww}eXpNT))
z9I5X=9nx&eq!Hlc4cXDY|BQ)%iX@Q}G+l8cFT9k
z?gwLO$Gxew>6#I^Sv07Z>G=;qbJB)@1S#kgdUP|5Mx8J>!_=B|KWT=}F2(-&RO@Ql
zuHrwuzeuXc**l5~Rrh%3VLIa4TJ>3sz(qMmq8ckqlu_{y>l^0v;o}+izm)G6O>YFX
zOKBk%_}mhNlXs|x9z|zWmRI%4w(-_dl{=Z&WA7mQ&rgj6?T^uZd27(WtzG~VDDz)2
zj#CC&wF-wFDI0>J<*%5rqeKuY2Mbv0p-jsI3g-Cic~_)zBL#_LPCCD%lJQU*P7Q|}
z@r98yTkZcq+4+L&k*9!s$L<8tQ`A_2LfgUjGKv_Hqs8YFJ^>1mpq#;H>m{TtOql>v
z9T}0&0v2ywe8F^TZ);DAfVSOF7Wv;h!PLaGD*5+$1S9P?C2ooqvP`tSHA~f-wXm2J
zZ34Uvj7s0_bPu%Jb2gw19m;u|HsQR6#VuvbfN+NX&Z*egLvO-!3Ss%e;QHv%lDQm;RAm7L;!
z#m*I_S)|qcDr|AV_%k1l%#)aR5=;iHaI*=RQjX$U-uCTmc5bbA&TF?ejw|?2I2wUvKHu6ym|8;dnbBFwg%i#_9nnIAqTH)h1o;^p$%?=
zx-sH`D&w&R1l3EOpc<@Pn!ma@ItF^@rVSCnieK?_Xmwf6O_g}QL(J6Bjr8~p@wf#W
zJ!=zxPGP5c3=AE^iOUo^r=usK)HgaiaTGZiV=IYwGAu!(pvqu<6uy`BZDnL
z5bhqSfkG9uxrEN=3D?~#-
zKRY7P&P>h>UN)wq`e{lvGdSAYbN!c}G_<6HgpFlZbRc8?dg0s?3=Y?TL+keyo4*k;
zX>ZZpZ^+07B8q7;x@6ofYm4(SN
zIC^*saA$9q4IV{J5uR5bn29960f^Xh`6wK(%kzW<@Q!3!D}*w^cN`|t;z&Jt+tUOZ`IEu>IWubrY>$gf6
zI4Vh};H)SG4n1X2i|QDPvz#7+;h8t|PEuMAH>En#A1!DJClO98*}wV32LBO7PIyWK
z^I(Chnq6OEd=aJKphU?+kMhDH0qsg2h|-Wq?fW2tRDIH3o#&S9R
zo4I!uj*0jw!X)#K2OL9Jtsv0@&jxKhZxZ!sT-ow%j+2Y+py{mk_KXyEgrgl7Njo$l
zDf-tAWxL|EQC=J9^Fu>HV}@b3pjdIdh^Fuq5pj|Pgc(6Ue0>cRXl)N<%T13W)eoHl
zCM7QfM~eV5JPObH$CxdthKiMr-B@>re=%}N3Jx&~9m{nmAX__SR}cNFleM+%9fx@N
zH)9ccZaBBP{!JS0i@PrZ&p{sN_~O*Rr1jyZ4IX$@wZGF;=(YCZ;V$(KHz`}iXMJ#TjWf
zaT}X%_1PWuYW1`{!}@7FWE>!9o3~$db>S^Z^}s5w9kTbM?{jw2ZI!RbAMz5kp9Tdv
z|5mWcgGj1!R#uctk=DM^z*pC;r-hFPIBTLhyINL`*T}EnMyTaKi~P54hIAW();8A1
zERTA;RlqKz@J2E6Y@uqE6*kGcR6b@9}R;;)8WFwn{olF34;$8=gL-!8@p8;r~{)p%(%!2TgDodE`*qJO!4Kx`p0@P+I7P$GDFusi-*u7;%zpG
zu7s&z6J2~<*n49WPDYZoomKD
zz3UGa{PVHGgF>&4gVPg8I+QrUQr1$C704zcrmZi6=cj^I{IsTts6n@?h(LtL%U7&4
zF7~7yGTt2+Nll~!zOJEbFVdEi4|*f6)GgHE{2~(d=gP&GR!tiMthmW7ew92NxoKkfy$mI{~)a3{6k_q5!18-#iK&HllhV7*I
zP3&ns`Z;v~FHzQL7*pAdEp5bJ1ZP@qUN{Y#ny3GSWaDO{w3E4uLL4CCkIMzFC`Hvt
zH{(VsOMwGjC+s4+6v0VS_sA~t!@JLS=ZYly9n(;;nArVfq);fp(jSpF)6bj*=%VQS
z{S0CBd4jHe17t9VZg+=lJDJQqq$sh++(Rj>#XfWj@979I$d-2%9SKfo;t1@gnY|
zCxtbW40bKAVwAyhO9v$f31#qbJLRz%!x+Pmj!pATGIc1W@wKP&s)z
z2%dU3_`rTsx=!(Yh~+da1wyLezm_}3C
zXJ_W=K8Zolaa9E9di_ve+D@n;cWD6$aNFMht*X43c@0qDF7y*L?SuV|*RpihU_=%0
zzP4NMYS+aqb}99I&JLaYNW34p8h#tDu5XKRFZM)Om_D*q_{UnZM}2HZYU=|h_on2s
zd@HO)ai^%PY6E<>!63{GpKDCV2N!eq)7gX*mm(e{`+w8Hd|cCdy6;F9u1b|yvQ3Q@
zTrvTiJt*5Zq|7=mA!D07F2*FBP|0tNU)~4Sf0>Irvh!0*2?#*UZY?fG2dz1ZoOOb6
zi|);%`_Zd~pYuc_a39hVj6!Oy#NlFCX9WU{W{5_#PD;qn5ru8;4};=Hk&}m=AWJ{I
zeskWefW!y<{ZN_HB^yr^qM-)*;ljh15FkS#@)SCod3?}!+)-^hHTDODr}<<{5H=h(
zi!Q$fy-)MUrO}}27EQ43p^xb6o=8&{fz`Y2Ythnjx~1DzyC3VvWoAa_|@9ysOTHMP^fb=b~>})zIGiFz*Ly9P`=VC>lEfFeKIKXuQAPyW$
z826N0ukU6hPkyfTy=JimP|I$YpUIb?R|DaW-2~-?@Y3>?sK4|wh2EXU7{b#4DoLD$
z&KN?uVM|g%QWD}CWpvMaX9Rb9r~;KI(!#<*0DVb$`TG@Qgjmt+Yz8UA6-f}3->M}x
zO$v2nWYy$=m_Mhvz%feU$Q1&JQuD@&WPeqav{?7S1U&j%4?N&!-catfcs#TMy|
znYv9CN&FsHzg?rL6*&!m(Q{O_m$45a&J<4i`kqm@XBPJiB2eNg1wy+5uAWcNXC68R
z6b720bEUeKOrU@a^WYLungbd=h$!v7J_nNW!$FXkltFr|Q3FL0NKjA^IW*F!61H?~
zOzCeQorsB;mp-mR0%XD&!|S!&R#U;6uo*VJqWDIhnKcnN`@Y$m&Rs?aWPRHsmi>D8
z7b57Iqm5R^2ej5-{rHuvoDlsz@afnaY1d+h|NpGV^`Md
zCgd!8$f6yG&y0k3OR2G&E2MP=?#z_3!5^C6^&t4?Cj&Gt49WjD1ua}#2JydZRPCCK
zQ4Ur+SBMkP5hv*q=yUi
z`#iDF>@L#-&JcJq4*PF^BL#jJ1ayjv^j|!80atX
zgMO2trffjaeb)K?GQO+o@D%(_?zDqo>I_puNQbc?cYOx$dp3<22vPqRyb%ukt9pYl
zf`o^}X;k6ZZSUj8u}^Q68cV|MZ}j?{!KXjQw@hz$xBVXqNT@5>2KHK8LFaevwai&=
zc4L#|LFO}#vRYU1Do>m>({2-w0In)z>Z<_0qz?KaR2hd0r!kw}&|PqGGerN1qfkBb
zt+F!4u$}zhRW(?lhd7*f=mSy!k;^T|IiT1VX^JE-`4kjdS;FM0*e&-Z$HO
z0+T-qVsWNW&pg%7Rg#Dv9q##?FPyco5a?r$fkqQL=8|}LhBDH^u@wFQ_zvz#syK9F
z*}a#C_FjQy{JCF{kam3M?|V&`G#K;1Y@&@R|2L`KD}AIfP(1*@m2+LzC^ZiM{v8Wm
ztopA~(%w!5JZ@5J8XHF^C6T}%T3E=<3lJiM>)ng@gHV?4LQT_S3t@NFZ23^!00wPKM4jKzELYS!ePsQ84UT%2D5Ljt9C}IZoJHw4Rs2w|Sbva-I
z#eTpmc&-Fj7t7!!+_<;knNcOl5x$50v*n+v-Pm7r6$_^jpc?M?mo^+9Yvg9QKiNwI
z)cJG?B`rv~HO##$cXD(PaCXvJRqIEi&aggIXbh~$i38nBpz;Am1A%)~b*HDp=YL!$
z&D4@~q0u@|t4&wT2xG!gjC%hv+DVj=$@sH>fQA7S#r$@n4m5lLG-M*ziH~q;
z^j&RytQtHR)BH{F30gAsFmWx5d{AYH1(^zx6Tt_PT7=gy19(#W5PnrlF>W$T?J()<
z>ywaS6*n^^FGgX}4A{sRO*l;qXqDj+5S{|7QSHYorA
delta 11133
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
P#7k9COQBZID*XQeQGY`Z
diff --git a/icons/obj/item/tools/blueprints.dmi b/icons/obj/item/tools/blueprints.dmi
new file mode 100644
index 0000000000000000000000000000000000000000..fdf4ba732aeaf2c8168aa7610c8514a83c46ec47
GIT binary patch
literal 516
zcmV+f0{i`mP)T4YcQ(jR7?S5zd`34Ok7tIDwFgjlsaj0k{!zwJ{j@
zH~@Eo0jvTHtPGU}z$(DN%5aTkV(|O)z
zTm(UbCT?K48(bX?w7Y>N7$ERRQrax@1%D*x-7-J