Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions code/_helpers/unsorted.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1595,3 +1595,7 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
/proc/CallAsync(datum/source, proctype, list/arguments)
set waitfor = FALSE
return call(source, proctype)(arglist(arguments))

// call to generate a stack trace and print to runtime logs
/proc/get_stack_trace(msg, file, line)
CRASH("%% [file],[line] %% [msg]")
2 changes: 2 additions & 0 deletions code/_macros.dm
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@

//check if all bitflags specified are present
#define CHECK_MULTIPLE_BITFIELDS(flagvar, flags) ((flagvar & (flags)) == flags)

#define PRINT_STACK_TRACE(X) get_stack_trace(X, __FILE__, __LINE__)
3 changes: 3 additions & 0 deletions code/datums/extensions/_defines.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define EXTENSION_FLAG_NONE 0
#define EXTENSION_FLAG_IMMEDIATE 1 // Instantly instantiates, instead of doing it lazily.
//#define EXTENSION_FLAG_MULTIPLE_INSTANCES 2 // Allows multiple instances per base type. To be implemented
25 changes: 25 additions & 0 deletions code/datums/extensions/event_registration.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// For registering for events to be called when certain conditions are met.

/datum/extension/event_registration
base_type = /datum/extension/event_registration
expected_type = /datum
flags = EXTENSION_FLAG_IMMEDIATE
var/decl/observ/event
var/datum/target
var/callproc

/datum/extension/event_registration/New(datum/holder, decl/observ/event, datum/target, callproc)
..()
event.register(target, src, .proc/trigger)
events_repository.register(/decl/observ/destroyed, target, src, .proc/qdel_self)
src.event = event
src.target = target
src.callproc = callproc

/datum/extension/event_registration/Destroy()
events_repository.unregister(/decl/observ/destroyed, target, src)
event.unregister(target, src)
. = ..()

/datum/extension/event_registration/proc/trigger()
call(holder, callproc)(arglist(args))
77 changes: 77 additions & 0 deletions code/datums/extensions/extensions.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/datum/extension
var/base_type
var/datum/holder = null // The holder
var/expected_type = /datum
var/flags = EXTENSION_FLAG_NONE

/datum/extension/New(var/datum/holder)
if(!istype(holder, expected_type))
CRASH("Invalid holder type. Expected [expected_type], was [holder.type]")
src.holder = holder

/datum/extension/proc/post_construction()

/datum/extension/Destroy()
holder = null
. = ..()

/datum
var/list/datum/extension/extensions

//Variadic - Additional positional arguments can be given. Named arguments might not work so well
/proc/set_extension(var/datum/source, var/datum/extension/extension_type)
var/datum/extension/extension_base_type = initial(extension_type.base_type)
if(!ispath(extension_base_type, /datum/extension))
CRASH("Invalid base type: Expected /datum/extension, was [log_info_line(extension_base_type)]")
if(!ispath(extension_type, extension_base_type))
CRASH("Invalid extension type: Expected [extension_base_type], was [log_info_line(extension_type)]")
if(!source.extensions)
source.extensions = list()
var/datum/extension/existing_extension = source.extensions[extension_base_type]
if(istype(existing_extension))
qdel(existing_extension)

if(initial(extension_base_type.flags) & EXTENSION_FLAG_IMMEDIATE)
var/datum/extension/created = construct_extension_instance(extension_type, source, args.Copy(3))
source.extensions[extension_base_type] = created
created.post_construction()
return created

var/list/extension_data = list(extension_type, source)
if(args.len > 2)
extension_data += args.Copy(3)
source.extensions[extension_base_type] = extension_data

/proc/get_or_create_extension(var/datum/source, var/datum/extension/extension_type)
var/base_type = initial(extension_type.base_type)
if(!has_extension(source, base_type))
set_extension(arglist(args))
return get_extension(source, base_type)

/proc/get_extension(var/datum/source, var/base_type)
if(!source.extensions)
return
. = source.extensions[base_type]
if(!.)
return
if(islist(.)) //a list, so it's expecting to be lazy-loaded
var/list/extension_data = .
var/datum/extension/created = construct_extension_instance(extension_data[1], extension_data[2], extension_data.Copy(3))
source.extensions[base_type] = created
created.post_construction()
return created

//Fast way to check if it has an extension, also doesn't trigger instantiation of lazy loaded extensions
/proc/has_extension(var/datum/source, var/base_type)
return !!(source.extensions && source.extensions[base_type])

/proc/construct_extension_instance(var/extension_type, var/datum/source, var/list/arguments)
arguments = list(source) + arguments
return new extension_type(arglist(arguments))

/proc/remove_extension(var/datum/source, var/base_type)
if(!source.extensions || !source.extensions[base_type])
return
if(!islist(source.extensions[base_type]))
qdel(source.extensions[base_type])
LAZYREMOVE(source.extensions, base_type)
107 changes: 107 additions & 0 deletions code/datums/extensions/eye/_eye.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#define HOLDER_TARGET 0
#define EYE_TARGET 1
#define EXTENSION_TARGET 2

/datum/extension/eye
base_type = /datum/extension/eye
var/mob/observer/eye/extension_eye
var/eye_type = /mob/observer/eye
var/mob/current_looker

// Actions used to pass commands from the eye to the holder. Must be subtype of /datum/action/eye.
var/action_type
var/list/actions = list()

/datum/extension/eye/Destroy()
unlook()
QDEL_NULL_LIST(actions)
. = ..()

// Create the eye object and give control to the given mob.
/datum/extension/eye/proc/look(var/mob/new_looker, var/list/eye_args)
if(new_looker.eyeobj || current_looker)
return FALSE

LAZYINSERT(eye_args, get_turf(new_looker), 1) // Make sure that a loc is provided to the eye.

if(!extension_eye)
extension_eye = new eye_type(arglist(eye_args))

current_looker = new_looker

extension_eye.possess(current_looker)

if(action_type)
for(var/atype in subtypesof(action_type))
var/datum/action/eye/action = new atype(src) // Eye actions determine their target based off their own target_type var.
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.
events_repository.register(/decl/observ/moved, holder, src, /datum/extension/eye/proc/unlook)
events_repository.register(/decl/observ/moved, current_looker, src, /datum/extension/eye/proc/unlook)

events_repository.register(/decl/observ/destroyed, current_looker, src, /datum/extension/eye/proc/unlook)
events_repository.register(/decl/observ/destroyed, extension_eye, src, /datum/extension/eye/proc/unlook)

if(isliving(current_looker))
events_repository.register(/decl/observ/stat_set, current_looker, src, /datum/extension/eye/proc/unlook)
events_repository.register(/decl/observ/logged_out, current_looker, src, /datum/extension/eye/proc/unlook)

return TRUE

/datum/extension/eye/proc/unlook()

events_repository.unregister(/decl/observ/moved, holder, src, /datum/extension/eye/proc/unlook)
events_repository.unregister(/decl/observ/moved, current_looker, src, /datum/extension/eye/proc/unlook)

events_repository.unregister(/decl/observ/destroyed, current_looker, src, /datum/extension/eye/proc/unlook)
events_repository.unregister(/decl/observ/destroyed, extension_eye, src, /datum/extension/eye/proc/unlook)

if(isliving(current_looker))
events_repository.unregister(/decl/observ/stat_set, current_looker, src, /datum/extension/eye/proc/unlook)
events_repository.unregister(/decl/observ/logged_out, current_looker, src, /datum/extension/eye/proc/unlook)

QDEL_NULL(extension_eye)

if(current_looker)
for(var/datum/action/A in actions)
A.Remove(current_looker)

current_looker = null

/datum/extension/eye/proc/get_eye_turf()
return get_turf(extension_eye)

/datum/action/eye
action_type = AB_GENERIC
check_flags = AB_CHECK_STUNNED|AB_CHECK_LYING
var/eye_type = /mob/observer/eye/
var/target_type = HOLDER_TARGET // The relevant owner of the proc to be called by the action.

/datum/action/eye/New(var/datum/extension/eye/eye_extension)
switch(target_type)
if(HOLDER_TARGET)
return ..(eye_extension.holder)
if(EYE_TARGET)
return ..(eye_extension.extension_eye)
if(EXTENSION_TARGET)
return ..(eye_extension)
else
CRASH("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

// 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 = EXTENSION_TARGET
32 changes: 32 additions & 0 deletions code/datums/extensions/eye/blueprints.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/datum/extension/eye/blueprints
expected_type = /obj/item/blueprints
eye_type = /mob/observer/eye/blueprints

action_type = /datum/action/eye/blueprints

/datum/action/eye/blueprints
eye_type = /mob/observer/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
12 changes: 12 additions & 0 deletions code/datums/extensions/eye/freelook.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// For mobs connecting to the station's cameranet without needing AI procs.
/datum/extension/eye/cameranet
eye_type = /mob/observer/eye/freelook/cameranet

// For mobs connecting to other visualnets. Pass visualnet as eye_args in look().
/datum/extension/eye/freelook
eye_type = /mob/observer/eye/freelook

/datum/extension/eye/freelook/proc/set_visualnet(var/datum/visualnet/net)
if(extension_eye)
var/mob/observer/eye/freelook/fl = extension_eye
if(istype(fl)) fl.visualnet = net
26 changes: 26 additions & 0 deletions code/datums/extensions/eye/landing.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/datum/extension/eye/landing
expected_type = /obj/machinery/computer/shuttle_control/explore
eye_type = /mob/observer/eye/landing

action_type = /datum/action/eye/landing

/datum/extension/eye/landing/get_eye_turf()
var/turf/eye_turf = ..()
var/mob/observer/eye/landing/landing_eye = extension_eye

return locate(eye_turf.x + landing_eye.x_offset, eye_turf.y + landing_eye.y_offset, eye_turf.z)

/datum/action/eye/landing
eye_type = /mob/observer/eye/landing

/datum/action/eye/landing/finish_landing
name = "Set landing location"
procname = "finish_landing"
button_icon_state = "shuttle_land"
target_type = HOLDER_TARGET

/datum/action/eye/landing/toggle_offsetting
name = "Offset landing location"
procname = "toggle_offsetting"
button_icon_state = "shuttle_offset"
target_type = EYE_TARGET
36 changes: 36 additions & 0 deletions code/datums/extensions/interactive.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//Extensions that can be interacted with via Topic
/datum/extension/interactive
base_type = /datum/extension/interactive
var/list/host_predicates
var/list/user_predicates

/datum/extension/interactive/New(var/datum/holder, var/host_predicates = list(), var/user_predicates = list())
..()

src.host_predicates = host_predicates ? host_predicates : list()
src.user_predicates = user_predicates ? user_predicates : list()

/datum/extension/interactive/Destroy()
host_predicates.Cut()
user_predicates.Cut()
return ..()

/datum/extension/interactive/proc/extension_status(var/mob/user)
if(!holder || !user)
return STATUS_CLOSE
if(!all_predicates_true(list(holder), host_predicates))
return STATUS_CLOSE
if(!all_predicates_true(list(user), user_predicates))
return STATUS_CLOSE
if(holder.CanUseTopic(user, global.default_topic_state) != STATUS_INTERACTIVE)
return STATUS_CLOSE

return STATUS_INTERACTIVE

/datum/extension/interactive/proc/extension_act(var/href, var/list/href_list, var/mob/user)
return extension_status(user) == STATUS_CLOSE

/datum/extension/interactive/Topic(var/href, var/list/href_list)
if(..())
return TRUE
return extension_act(href, href_list, usr)
Loading