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
This commit is contained in:
RustingWithYou
2024-04-19 21:56:26 +00:00
committed by GitHub
parent ab23fbdb70
commit 6957bc34f1
29 changed files with 810 additions and 302 deletions
+12 -40
View File
@@ -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"
@@ -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()
. = ..()
@@ -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 = "<span style=\"[style]\">[area_prefix], [A.name]</span>"
/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
+40
View File
@@ -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)
@@ -1867,6 +1867,8 @@
..()
if(update_hud)
handle_regular_hud_updates()
if(eyeobj)
eyeobj.remove_visual(src)
/mob/living/carbon/human/can_stand_overridden()
+2 -1
View File
@@ -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()
@@ -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
+4
View File
@@ -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()
+1
View File
@@ -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()
+8
View File
@@ -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)