Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ports extensions from Nebula #9059

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
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
Atermonera marked this conversation as resolved.
Show resolved Hide resolved
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
Atermonera marked this conversation as resolved.
Show resolved Hide resolved
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