diff --git a/code/__defines/action.dm b/code/__defines/action.dm index 3f9b8a5e41..25babfe390 100644 --- a/code/__defines/action.dm +++ b/code/__defines/action.dm @@ -2,3 +2,16 @@ #define AB_CHECK_STUNNED 2 #define AB_CHECK_LYING 4 #define AB_CHECK_CONSCIOUS 8 + +///Action button triggered with right click +#define TRIGGER_SECONDARY_ACTION (1<<0) +///Action triggered to ignore any availability checks +#define TRIGGER_FORCE_AVAILABLE (1<<1) + +#define ACTION_BUTTON_DEFAULT_BACKGROUND "_use_ui_default_background" + +#define UPDATE_BUTTON_NAME (1<<0) +#define UPDATE_BUTTON_ICON (1<<1) +#define UPDATE_BUTTON_BACKGROUND (1<<2) +#define UPDATE_BUTTON_OVERLAY (1<<3) +#define UPDATE_BUTTON_STATUS (1<<4) diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index 2623cadaaf..5faf4792d7 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -191,6 +191,8 @@ #define COMSIG_ENTER_AREA "enter_area" ///from base of area/Exited(): (/area) #define COMSIG_EXIT_AREA "exit_area" +///from base of client/Click(): (atom/target, atom/location, control, params, mob/user) +#define COMSIG_CLIENT_CLICK "atom_client_click" ///from base of atom/Click(): (location, control, params, mob/user) #define COMSIG_CLICK "atom_click" ///from base of atom/ShiftClick(): (/mob) @@ -714,6 +716,16 @@ ///from base of datum/action/proc/Trigger(): (datum/action) #define COMSIG_ACTION_TRIGGER "action_trigger" #define COMPONENT_ACTION_BLOCK_TRIGGER (1<<0) +/// From /datum/action/Grant(): (mob/grant_to) +#define COMSIG_ACTION_GRANTED "action_grant" +/// From /datum/action/Remove(): (mob/removed_from) +#define COMSIG_ACTION_REMOVED "action_removed" +/// From /datum/action/Grant(): (datum/action) +#define COMSIG_MOB_GRANTED_ACTION "mob_action_grant" +/// From /datum/action/Remove(): (datum/action) +#define COMSIG_MOB_REMOVED_ACTION "mob_action_removed" +/// From /datum/action/apply_button_overlay() +#define COMSIG_ACTION_OVERLAY_APPLY "action_overlay_applied" //Xenobio hotkeys diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm index ae83c705de..3cbce52bc0 100644 --- a/code/__defines/flags.dm +++ b/code/__defines/flags.dm @@ -57,3 +57,19 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define TASK_TARGET_EXCLUSIVE (1<<1) #define TASK_USER_EXCLUSIVE (1<<2) #define TASK_ALL_EXCLUSIVE TASK_TARGET_EXCLUSIVE | TASK_USER_EXCLUSIVE + +// Update flags for [/atom/proc/update_appearance] +/// Update the atom's name +#define UPDATE_NAME (1<<0) +/// Update the atom's desc +#define UPDATE_DESC (1<<1) +/// Update the atom's icon state +#define UPDATE_ICON_STATE (1<<2) +/// Update the atom's overlays +#define UPDATE_OVERLAYS (1<<3) +/// Update the atom's greyscaling +#define UPDATE_GREYSCALE (1<<4) +/// Update the atom's smoothing. (More accurately, queue it for an update) +#define UPDATE_SMOOTHING (1<<5) +/// Update the atom's icon +#define UPDATE_ICON (UPDATE_ICON_STATE|UPDATE_OVERLAYS) diff --git a/code/_onclick/hud/_defines.dm b/code/__defines/hud.dm similarity index 94% rename from code/_onclick/hud/_defines.dm rename to code/__defines/hud.dm index e89b3beb2f..0291f2e4fc 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/__defines/hud.dm @@ -203,3 +203,14 @@ #define ui_mech_deco2_f "WEST+2:-7, SOUTH+9" #define ui_smallquad "EAST-4:22,SOUTH:5" + +//Upper left (action buttons) +#define ui_action_palette "WEST+0:23,NORTH-1:5" +#define ui_action_palette_offset(north_offset) ("WEST+0:23,NORTH-[1+north_offset]:5") + +#define ui_palette_scroll "WEST+1:8,NORTH-6:28" +#define ui_palette_scroll_offset(north_offset) ("WEST+1:8,NORTH-[6+north_offset]:28") + +#define ui_shadekin_display "EAST-1:28,CENTER-3:15" +#define ui_lleill_display "EAST-1:28,CENTER-3:15" +#define ui_xenochimera_danger_display "EAST-1:28,CENTER-3:15" diff --git a/code/_helpers/weakref.dm b/code/_helpers/weakref.dm new file mode 100644 index 0000000000..25b65e63ec --- /dev/null +++ b/code/_helpers/weakref.dm @@ -0,0 +1,2 @@ +/// Checks if potential_weakref is a weakref of thing. +#define IS_WEAKREF_OF(thing, potential_weakref) (isdatum(thing) && !isnull(potential_weakref) && thing.weak_reference == potential_weakref) diff --git a/code/_onclick/hud/_defines_vr.dm b/code/_onclick/hud/_defines_vr.dm deleted file mode 100644 index 82d0af8b02..0000000000 --- a/code/_onclick/hud/_defines_vr.dm +++ /dev/null @@ -1,3 +0,0 @@ -#define ui_shadekin_display "EAST-1:28,CENTER-3:15" -#define ui_lleill_display "EAST-1:28,CENTER-3:15" -#define ui_xenochimera_danger_display "EAST-1:28,CENTER-3:15" \ No newline at end of file diff --git a/code/_onclick/hud/action/action.dm b/code/_onclick/hud/action/action.dm index d1f4441c7d..a5a512be68 100644 --- a/code/_onclick/hud/action/action.dm +++ b/code/_onclick/hud/action/action.dm @@ -1,96 +1,130 @@ +/** + * # Action system + * + * A simple base for an modular behavior attached to atom or datum. + */ /datum/action + /// The name of the action var/name = "Generic Action" - var/desc = null - - var/atom/movable/target = null - + /// The description of what the action does, shown in button tooltips + var/desc + /// The target the action is attached to. If the target datum is deleted, the action is as well. + /// Set in New() via the proc link_to(). PLEASE set a target if you're making an action + var/datum/target + /// Where any buttons we create should be by default. Accepts screen_loc and location defines + var/default_button_position = SCRN_OBJ_IN_LIST + /// This is who currently owns the action, and most often, this is who is using the action if it is triggered + /// This can be the same as "target" but is not ALWAYS the same - this is set and unset with Grant() and Remove() + var/mob/owner + /// Flags that will determine of the owner / user of the action can... use the action var/check_flags = NONE - var/processing = FALSE - - var/obj/screen/movable/action_button/button = null - - var/button_icon = 'icons/mob/actions.dmi' - var/background_icon_state = "bg_default" - var/buttontooltipstyle = "" + /// Whether the button becomes transparent when it can't be used or just reddened var/transparent_when_unavailable = TRUE + /// List of all mobs that are viewing our action button -> A unique movable for them to view. + var/list/viewers = list() + /// If TRUE, this action button will be shown to observers / other mobs who view from this action's owner's eyes. + /// Used in [/mob/proc/show_other_mob_action_buttons] + /// (Not really, this behavior is unimplemented) + var/show_to_observers = TRUE - var/icon_icon = 'icons/mob/actions.dmi' + /// The style the button's tooltips appear to be + var/buttontooltipstyle = "" + + /// This is the file for the BACKGROUND underlay icon of the button + var/background_icon = 'icons/mob/actions/backgrounds.dmi' + /// This is the icon state state for the BACKGROUND underlay icon of the button + /// (If set to ACTION_BUTTON_DEFAULT_BACKGROUND, uses the hud's default background) + var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND + + /// This is the file for the icon that appears on the button + var/button_icon = 'icons/mob/actions.dmi' + /// This is the icon state for the icon that appears on the button var/button_icon_state = "default" - var/mob/owner + /// This is the file for any FOREGROUND overlay icons on the button (such as borders) + var/overlay_icon = 'icons/mob/actions/backgrounds.dmi' + /// This is the icon state for any FOREGROUND overlay icons on the button (such as borders) + var/overlay_icon_state /datum/action/New(Target) link_to(Target) - button = new - button.linked_action = src - button.name = name - button.actiontooltipstyle = buttontooltipstyle - if(desc) - button.desc = desc +/// Links the passed target to our action, registering any relevant signals /datum/action/proc/link_to(Target) target = Target - RegisterSignal(Target, COMSIG_ATOM_UPDATED_ICON, .proc/OnUpdatedIcon) + RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE) + + if(isatom(target)) + RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, PROC_REF(on_target_icon_update)) + + // if(istype(target, /datum/mind)) + // RegisterSignal(target, COMSIG_MIND_TRANSFERRED, .proc/on_target_mind_swapped) /datum/action/Destroy() if(owner) Remove(owner) target = null - QDEL_NULL(button) + QDEL_LIST_ASSOC_VAL(viewers) return ..() -/datum/action/proc/Grant(mob/M) - if(M) - if(owner) - if(owner == M) - return - Remove(owner) - owner = M +/// Signal proc that clears any references based on the owner or target deleting +/// If the owner's deleted, we will simply remove from them, but if the target's deleted, we will self-delete +/datum/action/proc/clear_ref(datum/ref) + SIGNAL_HANDLER + if(ref == owner) + Remove(owner) + if(ref == target) + qdel(src) - // button id generation - var/counter = 0 - var/bitfield = 0 - for(var/datum/action/A as anything in M.actions) - if(A.name == name && A.button.id) - counter += 1 - bitfield |= A.button.id - bitfield = !bitfield - var/bitflag = 1 - for(var/i in 1 to (counter + 1)) - if(bitfield & bitflag) - button.id = bitflag - break - bitflag *= 2 - - LAZYADD(M.actions, src) - if(M.client) - M.client.screen += button - button.locked = /* M.client.prefs.buttons_locked || */ button.id ? LAZYACCESS(M.client.prefs.action_button_screen_locs, "[name]_[button.id]") : FALSE //even if its not defaultly locked we should remember we locked it before - button.moved = button.id ? LAZYACCESS(M.client.prefs.action_button_screen_locs, "[name]_[button.id]") : FALSE - M.update_action_buttons(TRUE) - else +/// Grants the action to the passed mob, making it the owner +/datum/action/proc/Grant(mob/grant_to) + if(!grant_to) + Remove(owner) + return + if(owner) + if(owner == grant_to) + return Remove(owner) -/datum/action/proc/Remove(mob/M) - if(M) - if(M.client) - M.client.screen -= button - button.moved = FALSE - LAZYREMOVE(M.actions, src) - M.update_action_buttons(TRUE) - owner = null - button.moved = FALSE //so the button appears in its normal position when given to another owner. - button.locked = FALSE - button.id = null + SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, grant_to) + SEND_SIGNAL(grant_to, COMSIG_MOB_GRANTED_ACTION, src) + owner = grant_to + RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE) -/datum/action/proc/Trigger() + GiveAction(grant_to) + +/// Remove the passed mob from being owner of our action +/datum/action/proc/Remove(mob/remove_from) + SHOULD_CALL_PARENT(TRUE) + + for(var/datum/hud/hud in viewers) + if(!hud.mymob) + continue + HideFrom(hud.mymob) + LAZYREMOVE(remove_from.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared + viewers = list() + + if(owner) + SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner) + SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src) + + UnregisterSignal(owner, COMSIG_PARENT_QDELETING) + if(target == owner) + RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref)) + + owner = null + +/// Actually triggers the effects of the action. +/// Called when the on-screen button is clicked, for example. +/datum/action/proc/Trigger(trigger_flags) if(!IsAvailable()) return FALSE if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER) return FALSE return TRUE +/// Whether our action is currently available to use or not /datum/action/proc/IsAvailable() if(!owner) return FALSE @@ -108,147 +142,207 @@ return FALSE return TRUE -/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE) +/// Builds / updates all buttons we have shared or given out +/datum/action/proc/build_all_button_icons(update_flags = ALL, force) + for(var/datum/hud/hud as anything in viewers) + build_button_icon(viewers[hud], update_flags, force) + +/** + * Builds the icon of the button. + * + * Concept: + * - Underlay (Background icon) + * - Icon (button icon) + * - Maptext + * - Overlay (Background border) + * + * button - which button we are modifying the icon of + * force - whether we're forcing a full update + */ +/datum/action/proc/build_button_icon(obj/screen/movable/action_button/button, update_flags = ALL, force = FALSE) + if(!button) + return + + if(update_flags & UPDATE_BUTTON_NAME) + update_button_name(button, force) + + if(update_flags & UPDATE_BUTTON_BACKGROUND) + apply_button_background(button, force) + + if(update_flags & UPDATE_BUTTON_ICON) + apply_button_icon(button, force) + + if(update_flags & UPDATE_BUTTON_OVERLAY) + apply_button_overlay(button, force) + + if(update_flags & UPDATE_BUTTON_STATUS) + update_button_status(button, force) + +/** + * Updates the name and description of the button to match our action name and discription. + * + * current_button - what button are we editing? + * force - whether an update is forced regardless of existing status + */ +/datum/action/proc/update_button_name(obj/screen/movable/action_button/button, force = FALSE) + button.name = name + if(desc) + button.desc = desc + +/** + * Creates the background underlay for the button + * + * current_button - what button are we editing? + * force - whether an update is forced regardless of existing status + */ +/datum/action/proc/apply_button_background(obj/screen/movable/action_button/current_button, force = FALSE) + if(!background_icon || !background_icon_state || (current_button.active_underlay_icon_state == background_icon_state && !force)) + return + + // What icons we use for our background + var/list/icon_settings = list( + // The icon file + "bg_icon" = background_icon, + // The icon state, if is_action_active() returns FALSE + "bg_state" = background_icon_state, + // The icon state, if is_action_active() returns TRUE + "bg_state_active" = background_icon_state, + ) + + // If background_icon_state is ACTION_BUTTON_DEFAULT_BACKGROUND instead use our hud's action button scheme + if(background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND && owner?.hud_used) + icon_settings = owner.hud_used.get_action_buttons_icons() + + // Determine which icon to use + var/used_icon_key = is_action_active(current_button) ? "bg_state_active" : "bg_state" + + // Make the underlay + current_button.underlays.Cut() + current_button.underlays += image(icon = icon_settings["bg_icon"], icon_state = icon_settings[used_icon_key]) + current_button.active_underlay_icon_state = icon_settings[used_icon_key] + +/** + * Applies our button icon and icon state to the button + * + * current_button - what button are we editing? + * force - whether an update is forced regardless of existing status + */ +/datum/action/proc/apply_button_icon(obj/screen/movable/action_button/current_button, force = FALSE) + if(!button_icon || !button_icon_state || (current_button.icon_state == button_icon_state && !force)) + return + + current_button.icon = button_icon + current_button.icon_state = button_icon_state + +/** + * Applies any overlays to our button + * + * current_button - what button are we editing? + * force - whether an update is forced regardless of existing status + */ +/datum/action/proc/apply_button_overlay(obj/screen/movable/action_button/current_button, force = FALSE) + SEND_SIGNAL(src, COMSIG_ACTION_OVERLAY_APPLY, current_button, force) + + if(!overlay_icon || !overlay_icon_state || (current_button.active_overlay_icon_state == overlay_icon_state && !force)) + return + + current_button.cut_overlay(current_button.button_overlay) + current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = overlay_icon_state) + current_button.add_overlay(current_button.button_overlay) + current_button.active_overlay_icon_state = overlay_icon_state + +/** + * Any other miscellaneous "status" updates within the action button is handled here, + * such as redding out when unavailable or modifying maptext. + * + * current_button - what button are we editing? + * force - whether an update is forced regardless of existing status + */ +/datum/action/proc/update_button_status(obj/screen/movable/action_button/current_button, force = FALSE) + if(IsAvailable()) + current_button.color = rgb(255,255,255,255) + else + current_button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0) + +/// Gives our action to the passed viewer. +/// Puts our action in their actions list and shows them the button. +/datum/action/proc/GiveAction(mob/viewer) + var/datum/hud/our_hud = viewer.hud_used + if(viewers[our_hud]) // Already have a copy of us? go away + return + + LAZYOR(viewer.actions, src) // Move this in + ShowTo(viewer) + +/// Adds our action button to the screen of the passed viewer. +/datum/action/proc/ShowTo(mob/viewer) + var/datum/hud/our_hud = viewer.hud_used + if(!our_hud || viewers[our_hud]) // There's no point in this if you have no hud in the first place + return + + var/obj/screen/movable/action_button/button = create_button() + SetId(button, viewer) + + button.our_hud = our_hud + viewers[our_hud] = button + if(viewer.client) + viewer.client.screen += button + + button.load_position(viewer) + viewer.update_action_buttons() + +/// Removes our action from the passed viewer. +/datum/action/proc/HideFrom(mob/viewer) + var/datum/hud/our_hud = viewer.hud_used + var/obj/screen/movable/action_button/button = viewers[our_hud] + LAZYREMOVE(viewer.actions, src) if(button) - if(!status_only) - button.name = name - button.desc = desc + qdel(button) - // if(owner && owner.hud_used && background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND) - // var/list/settings = owner.hud_used.get_action_buttons_icons() - // if(button.icon != settings["bg_icon"]) - // button.icon = settings["bg_icon"] - // if(button.icon_state != settings["bg_state"]) - // button.icon_state = settings["bg_state"] - // else +/// Creates an action button movable for the passed mob, and returns it. +/datum/action/proc/create_button() + var/obj/screen/movable/action_button/button = new() + button.linked_action = src + build_button_icon(button, ALL, TRUE) + return button - if(button.icon != button_icon) - button.icon = button_icon - if(button.icon_state != background_icon_state) - button.icon_state = background_icon_state +/datum/action/proc/SetId(obj/screen/movable/action_button/our_button, mob/owner) + //button id generation + var/bitfield = 0 + for(var/datum/action/action in owner.actions) + if(action == src) // This could be us, which is dumb + continue + var/obj/screen/movable/action_button/button = action.viewers[owner.hud_used] + if(action.name == name && button.id) + bitfield |= button.id - ApplyIcon(button, force) + bitfield = ~bitfield // Flip our possible ids, so we can check if we've found a unique one + for(var/i in 0 to 23) // We get 24 possible bitflags in dm + var/bitflag = 1 << i // Shift us over one + if(bitfield & bitflag) + our_button.id = bitflag + return - if(!IsAvailable()) - button.color = transparent_when_unavailable ? rgb(128, 0, 0, 128) : rgb(128, 0, 0) - else - button.color = rgb(255, 255, 255, 255) - return TRUE - -/datum/action/proc/ApplyIcon(obj/screen/movable/action_button/current_button, force = FALSE) - if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force)) - current_button.cut_overlays(TRUE) - current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state)) - current_button.button_icon_state = button_icon_state - -// Currently never triggered -/datum/action/proc/OnUpdatedIcon() +/// Updates our buttons if our target's icon was updated +/// Still never triggered lmao +/datum/action/proc/on_target_icon_update(datum/source, updates, updated) SIGNAL_HANDLER - UpdateButtonIcon() -//Presets for item actions -/datum/action/item_action - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_LYING|AB_CHECK_CONSCIOUS - button_icon_state = null - // If you want to override the normal icon being the item - // then change this to an icon state + var/update_flag = NONE + var/forced = FALSE + if(updates & UPDATE_ICON_STATE) + update_flag |= UPDATE_BUTTON_ICON + forced = TRUE + if(updates & UPDATE_OVERLAYS) + update_flag |= UPDATE_BUTTON_OVERLAY + forced = TRUE + if(updates & (UPDATE_NAME|UPDATE_DESC)) + update_flag |= UPDATE_BUTTON_NAME + // Status is not relevant, and background is not relevant. Neither will change -/datum/action/item_action/New(Target) - . = ..() - var/obj/item/I = target - LAZYADD(I.actions, src) + // Force the update if an icon state or overlay change was done + build_all_button_icons(update_flag, forced) -/datum/action/item_action/Destroy() - var/obj/item/I = target - LAZYREMOVE(I.actions, src) - return ..() - -/datum/action/item_action/Trigger() - if(!..()) - return 0 - if(target) - var/obj/item/I = target - I.ui_action_click(owner, src.type) - return 1 - -/datum/action/item_action/ApplyIcon(obj/screen/movable/action_button/current_button, force) - if(button_icon && button_icon_state) - // If set, use the custom icon that we set instead - // of the item appearence - return ..() - else if(target && ((current_button.appearance_cache != target.appearance) || force)) - var/mutable_appearance/ma = new(target.appearance) - ma.plane = FLOAT_PLANE - ma.layer = FLOAT_LAYER - ma.pixel_x = 0 - ma.pixel_y = 0 - - current_button.cut_overlays() - current_button.add_overlay(ma) - current_button.appearance_cache = target.appearance - -/datum/action/item_action/hands_free - check_flags = AB_CHECK_CONSCIOUS - - -/datum/action/innate - check_flags = NONE - var/active = 0 - -/datum/action/innate/Trigger() - if(!..()) - return 0 - if(!active) - Activate() - else - Deactivate() - return 1 - -/datum/action/innate/proc/Activate() - return - -/datum/action/innate/proc/Deactivate() - return - -//Preset for an action with a cooldown -/datum/action/cooldown - check_flags = NONE - transparent_when_unavailable = FALSE - var/cooldown_time = 0 - var/next_use_time = 0 - -/datum/action/cooldown/New() - ..() - button.maptext = "" - button.maptext_x = 8 - button.maptext_y = 0 - button.maptext_width = 24 - button.maptext_height = 12 - -/datum/action/cooldown/IsAvailable() - return next_use_time <= world.time - -/datum/action/cooldown/proc/StartCooldown() - next_use_time = world.time + cooldown_time - button.maptext = span_maptext(span_bold("[round(cooldown_time/10, 0.1)]")) - UpdateButtonIcon() - START_PROCESSING(SSfastprocess, src) - -/datum/action/cooldown/process() - if(!owner) - button.maptext = "" - STOP_PROCESSING(SSfastprocess, src) - var/timeleft = max(next_use_time - world.time, 0) - if(timeleft == 0) - button.maptext = "" - UpdateButtonIcon() - STOP_PROCESSING(SSfastprocess, src) - else - button.maptext = span_maptext(span_bold("[round(timeleft/10, 0.1)]")) - -/datum/action/cooldown/Grant(mob/M) - ..() - if(owner) - UpdateButtonIcon() - if(next_use_time > world.time) - START_PROCESSING(SSfastprocess, src) +/// Checks if our action is actively selected. Used for selecting icons primarily. +/datum/action/proc/is_action_active(obj/screen/movable/action_button/current_button) + return FALSE diff --git a/code/_onclick/hud/action/action_group.dm b/code/_onclick/hud/action/action_group.dm new file mode 100644 index 0000000000..d0b2f8df11 --- /dev/null +++ b/code/_onclick/hud/action/action_group.dm @@ -0,0 +1,212 @@ +/datum/action_group + /// The hud we're owned by + var/datum/hud/owner + /// The actions we're managing + var/list/obj/screen/movable/action_button/actions + /// The initial vertical offset of our action buttons + var/north_offset = 0 + /// The pixel vertical offset of our action buttons + var/pixel_north_offset = 0 + /// Max amount of buttons we can have per row + /// Indexes at 1 + var/column_max = 0 + /// How far "ahead" of the first row we start. Lets us "scroll" our rows + /// Indexes at 1 + var/row_offset = 0 + /// How many rows of actions we can have at max before we just stop hiding + /// Indexes at 1 + var/max_rows = INFINITY + /// The screen location we go by + var/location + /// Our landing screen object + var/obj/screen/action_landing/landing + +/datum/action_group/New(datum/hud/owner) + ..() + actions = list() + src.owner = owner + +/datum/action_group/Destroy() + owner = null + QDEL_NULL(landing) + QDEL_LIST(actions) + return ..() + +/datum/action_group/proc/insert_action(obj/screen/action, index) + if(action in actions) + if(actions[index] == action) + return + actions -= action // Don't dupe, come on + if(!index) + index = length(actions) + 1 + index = min(length(actions) + 1, index) + actions.Insert(index, action) + refresh_actions() + +/datum/action_group/proc/remove_action(obj/screen/action) + actions -= action + if(!QDELING(src)) + refresh_actions() + +/datum/action_group/proc/refresh_actions() + // We don't use size() here because landings are not canon + var/total_rows = ROUND_UP(length(actions) / column_max) + total_rows -= max_rows // Lets get the amount of rows we're off from our max + row_offset = clamp(row_offset, 0, total_rows) // You're not allowed to offset so far that we have a row of blank space + + var/button_number = 0 + for(var/obj/screen/button as anything in actions) + var/postion = ButtonNumberToScreenCoords(button_number ) + button.screen_loc = postion + button_number++ + + if(landing) + var/postion = ButtonNumberToScreenCoords(button_number, landing = TRUE) // Need a good way to count buttons off screen, but allow this to display in the right place if it's being placed with no concern for dropdown + landing.screen_loc = postion + button_number++ + +/// Accepts a number represeting our position in the group, indexes at 0 to make the math nicer +/datum/action_group/proc/ButtonNumberToScreenCoords(number, landing = FALSE) + var/row = round(number / column_max) + row -= row_offset // If you're less then 0, you don't get to render, this lets us "scroll" rows ya feel? + if(row < 0) + return null + + // Could use >= here, but I think it's worth noting that the two start at different places, since row is based on number here + if(row > max_rows - 1) + if(!landing) // If you're not a landing, go away please. thx + return null + // We always want to render landings, even if their action button can't be displayed. + // So we set a row equal to the max amount of rows + 1. Willing to overrun that max slightly to properly display the landing spot + row = max_rows // Remembering that max_rows indexes at 1, and row indexes at 0 + + // We're going to need to set our column to match the first item in the last row, so let's set number properly now + number = row * column_max + + var/visual_row = row + north_offset + var/coord_row = visual_row ? "-[visual_row]" : "+0" + + var/visual_column = number % column_max + var/coord_col = "+[visual_column]" + var/coord_col_offset = 4 + 2 * (visual_column + 1) + return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-[pixel_north_offset]" + +/datum/action_group/proc/check_against_view() + var/owner_view = owner?.mymob?.client?.view + if(!owner_view) + return + // Unlikey as it is, we may have been changed. Want to start from our target position and fail down + column_max = initial(column_max) + // Convert our viewer's view var into a workable offset + var/list/view_size = view_to_pixels(owner_view) + + // We're primarially concerned about width here, if someone makes us 1x2000 I wish them a swift and watery death + var/furthest_screen_loc = ButtonNumberToScreenCoords(column_max - 1) + var/list/offsets = screen_loc_to_offset(furthest_screen_loc, owner_view) + if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2]) // We're all good + return + + for(column_max in column_max - 1 to 1 step -1) // Yes I could do this by unwrapping ButtonNumberToScreenCoords, but I don't feel like it + var/tested_screen_loc = ButtonNumberToScreenCoords(column_max) + offsets = screen_loc_to_offset(tested_screen_loc, owner_view) + // We've found a valid max length, pack it in + if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2]) + break + // Use our newly resized column max + refresh_actions() + +/// Returns the amount of objects we're storing at the moment +/datum/action_group/proc/size() + var/amount = length(actions) + if(landing) + amount += 1 + return amount + +/datum/action_group/proc/index_of(obj/screen/get_location) + return actions.Find(get_location) + +/// Generates a landing object that can be dropped on to join this group +/datum/action_group/proc/generate_landing() + if(landing) + return + landing = new() + landing.set_owner(src) + refresh_actions() + +/// Clears any landing objects we may currently have +/datum/action_group/proc/clear_landing() + QDEL_NULL(landing) + +/datum/action_group/proc/update_landing() + if(!landing) + return + landing.refresh_owner() + +/datum/action_group/proc/scroll(amount) + row_offset += amount + refresh_actions() + +/datum/action_group/palette + north_offset = 2 + column_max = 3 + max_rows = 3 + location = SCRN_OBJ_IN_PALETTE + +/datum/action_group/palette/insert_action(obj/screen/action, index) + . = ..() + var/obj/screen/button_palette/palette = owner.toggle_palette + palette.play_item_added() + +/datum/action_group/palette/remove_action(obj/screen/action) + . = ..() + var/obj/screen/button_palette/palette = owner.toggle_palette + palette.play_item_removed() + if(!length(actions)) + palette.set_expanded(FALSE) + +/datum/action_group/palette/refresh_actions() + var/obj/screen/button_palette/palette = owner.toggle_palette + var/obj/screen/palette_scroll/scroll_down = owner.palette_down + var/obj/screen/palette_scroll/scroll_up = owner.palette_up + if(!palette || !scroll_down || !scroll_up) + return + + var/actions_above = round((owner.listed_actions.size() - 1) / owner.listed_actions.column_max) + north_offset = initial(north_offset) + actions_above + + palette.screen_loc = ui_action_palette_offset(actions_above) + var/action_count = length(owner?.mymob?.actions) + var/our_row_count = round((length(actions) - 1) / column_max) + if(!action_count) + palette.screen_loc = null + + if(palette.expanded && action_count && our_row_count >= max_rows) + scroll_down.screen_loc = ui_palette_scroll_offset(actions_above) + scroll_up.screen_loc = ui_palette_scroll_offset(actions_above) + else + scroll_down.screen_loc = null + scroll_up.screen_loc = null + + return ..() + +/datum/action_group/palette/ButtonNumberToScreenCoords(number, landing) + var/obj/screen/button_palette/palette = owner.toggle_palette + if(palette.expanded) + return ..() + + if(!landing) + return null + + // We only render the landing in this case, so we force it to be the second item displayed (Second rather then first since it looks nicer) + // Remember the number var indexes at 0 + return ..(1 + (row_offset * column_max), landing) + + +/datum/action_group/listed + pixel_north_offset = 6 + column_max = 10 + location = SCRN_OBJ_IN_LIST + +/datum/action_group/listed/refresh_actions() + . = ..() + owner?.palette_actions.refresh_actions() // We effect them, so we gotta refresh em diff --git a/code/_onclick/hud/action/action_group_helpers.dm b/code/_onclick/hud/action/action_group_helpers.dm new file mode 100644 index 0000000000..14ca719433 --- /dev/null +++ b/code/_onclick/hud/action/action_group_helpers.dm @@ -0,0 +1,41 @@ +/// Generates visual landings for all groups that the button is not a memeber of +/datum/hud/proc/generate_landings(obj/screen/movable/action_button/button) + listed_actions.generate_landing() + palette_actions.generate_landing() + +/// Clears all currently visible landings +/datum/hud/proc/hide_landings() + listed_actions.clear_landing() + palette_actions.clear_landing() + +// Updates any existing "owned" visuals, ensures they continue to be visible +/datum/hud/proc/update_our_owner() + toggle_palette.refresh_owner() + palette_down.refresh_owner() + palette_up.refresh_owner() + listed_actions.update_landing() + palette_actions.update_landing() + +/// Ensures all of our buttons are properly within the bounds of our client's view, moves them if they're not +/datum/hud/proc/view_audit_buttons() + var/our_view = mymob?.client?.view + if(!our_view) + return + listed_actions.check_against_view() + palette_actions.check_against_view() + for(var/obj/screen/movable/action_button/floating_button as anything in floating_actions) + var/list/current_offsets = screen_loc_to_offset(floating_button.screen_loc) + // We set the view arg here, so the output will be properly hemm'd in by our new view + floating_button.screen_loc = offset_to_screen_loc(current_offsets[1], current_offsets[2], view = our_view) + +/// Generates and fills new action groups with our mob's current actions +/datum/hud/proc/build_action_groups() + listed_actions = new(src) + palette_actions = new(src) + floating_actions = list() + for(var/datum/action/action as anything in mymob.actions) + var/obj/screen/movable/action_button/button = action.viewers[src] + if(!button) + action.ShowTo(mymob) + button = action.viewers[src] + position_action(button, button.location) diff --git a/code/_onclick/hud/action/action_item_overlay.dm b/code/_onclick/hud/action/action_item_overlay.dm new file mode 100644 index 0000000000..7d1af7309e --- /dev/null +++ b/code/_onclick/hud/action/action_item_overlay.dm @@ -0,0 +1,73 @@ +/** + * Apply to an action to allow it to take an item + * and apply it as an overlay of the action button + */ +/datum/component/action_item_overlay + /// Weakref to what item the component uses to apply as an overlay. + var/datum/weakref/item_ref + /// Callback that dictates what item the component uses to apply as an overlay. + var/datum/callback/item_callback + + /// The appearance of the item we've applied + var/mutable_appearance/item_appearance + +/datum/component/action_item_overlay/Initialize(atom/movable/item, datum/callback/item_callback) + if(!istype(parent, /datum/action)) + return COMPONENT_INCOMPATIBLE + + if(!item && !item_callback) + stack_trace("[type] created without a reference item or an item callback - one or the other is required.") + return COMPONENT_INCOMPATIBLE + + src.item_ref = WEAKREF(item) + src.item_callback = item_callback + +/datum/component/action_item_overlay/Destroy(force, silent) + item_ref = null + QDEL_NULL(item_callback) + item_appearance = null + return ..() + +/datum/component/action_item_overlay/RegisterWithParent() + RegisterSignal(parent, COMSIG_ACTION_OVERLAY_APPLY, PROC_REF(on_overlays_applied)) + + var/datum/action/parent_action = parent + parent_action.build_all_button_icons(UPDATE_BUTTON_OVERLAY) + +/datum/component/action_item_overlay/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ACTION_OVERLAY_APPLY) + + // If we're being unregistered / deleted but our parent is sticking around, + // force an overlay update to get rid of our item appearance + if(!QDELING(parent)) + var/datum/action/parent_action = parent + parent_action.build_all_button_icons(UPDATE_BUTTON_OVERLAY) + +/// Signal proc for [COMSIG_ACTION_OVERLAY_APPLY], applies the item appearance if possible. +/datum/component/action_item_overlay/proc/on_overlays_applied(datum/action/source, obj/screen/movable/action_button/current_button, force) + SIGNAL_HANDLER + + // We're in the middle of being removed / deleted, remove our associated overlay + if(QDELING(src) && item_appearance) + current_button.cut_overlay(item_appearance) + item_appearance = null + return + + var/atom/movable/muse = item_callback?.Invoke() || item_ref?.resolve() + if(!istype(muse)) + return + + if(item_appearance) + // For caching purposes, we will try not to update if we don't need to + if(!force && item_appearance.icon == muse.icon && item_appearance.icon_state == muse.icon_state) + return + current_button.cut_overlay(item_appearance) + + var/mutable_appearance/muse_appearance = new(muse.appearance) + muse_appearance.plane = FLOAT_PLANE + muse_appearance.layer = FLOAT_LAYER + muse_appearance.pixel_x = 0 + muse_appearance.pixel_y = 0 + + current_button.add_overlay(muse_appearance) + item_appearance = muse_appearance diff --git a/code/_onclick/hud/action/action_screen_objects.dm b/code/_onclick/hud/action/action_screen_objects.dm index fd64a38d57..503274f86f 100644 --- a/code/_onclick/hud/action/action_screen_objects.dm +++ b/code/_onclick/hud/action/action_screen_objects.dm @@ -1,39 +1,48 @@ - /obj/screen/movable/action_button var/datum/action/linked_action + var/datum/hud/our_hud var/actiontooltipstyle = "" screen_loc = null + icon = null // we don't use the base icon at all, just underlays and overlays - var/button_icon_state - var/appearance_cache + /// The icon state of our active overlay, used to prevent re-applying identical overlays + var/active_overlay_icon_state + /// The icon state of our active underlay, used to prevent re-applying identical underlays + var/active_underlay_icon_state + /// The overlay we have overtop our button + var/mutable_appearance/button_overlay + /// Where we are currently placed on the hud. SCRN_OBJ_DEFAULT asks the linked action what it thinks + var/location = SCRN_OBJ_DEFAULT + /// A unique bitflag, combined with the name of our linked action this lets us persistently remember any user changes to our position var/id + /// A weakref of the last thing we hovered over + /// God I hate how dragging works + var/datum/weakref/last_hovored_ref + +/obj/screen/movable/action_button/Destroy() + if(our_hud) + var/mob/viewer = our_hud.mymob + our_hud.hide_action(src) + viewer?.client?.screen -= src + linked_action.viewers -= our_hud + viewer.update_action_buttons() + our_hud = null + linked_action = null + return ..() /obj/screen/movable/action_button/proc/can_use(mob/user) - if(linked_action) - return linked_action.owner == user - else if(isobserver(user)) - // var/mob/dead/observer/O = user - // return !O.observetarget - return TRUE - else - return TRUE + // if(isobserver(user)) + // var/mob/dead/observer/dead_mob = user + // if(dead_mob.observetarget) // Observers can only click on action buttons if they're not observing something + // return FALSE -/obj/screen/movable/action_button/MouseDrop(over_object) - if(!can_use(usr)) - return - if((istype(over_object, /obj/screen/movable/action_button) && !istype(over_object, /obj/screen/movable/action_button/hide_toggle))) - if(locked) - to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first.")) - return - var/obj/screen/movable/action_button/B = over_object - var/list/actions = usr.actions - actions.Swap(actions.Find(src.linked_action), actions.Find(B.linked_action)) - moved = FALSE - B.moved = FALSE - usr.update_action_buttons() - else - return ..() + if(linked_action) + if(linked_action.viewers[user.hud_used]) + return TRUE + return FALSE + + return TRUE /obj/screen/movable/action_button/Click(location,control,params) if(!can_use(usr)) @@ -41,108 +50,40 @@ var/list/modifiers = params2list(params) if(LAZYACCESS(modifiers, SHIFT_CLICK)) - if(locked) - to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first.")) - return TRUE - moved = FALSE - usr.update_action_buttons() - return TRUE - if(LAZYACCESS(modifiers, CTRL_CLICK)) - locked = !locked - to_chat(usr, span_notice("Action button \"[name]\" [locked ? "" : "un"]locked.")) - if(id && usr?.client?.prefs) // try to (un)remember position - LAZYSET(usr.client.prefs.action_button_screen_locs, "[name]_[id]", locked ? moved : null) + var/datum/hud/our_hud = usr.hud_used + our_hud.position_action(src, SCRN_OBJ_DEFAULT) return TRUE if(!usr.checkClickCooldown()) return usr.setClickCooldown(1) - linked_action.Trigger() + var/trigger_flags + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + trigger_flags |= TRIGGER_SECONDARY_ACTION + linked_action.Trigger(trigger_flags = trigger_flags) return TRUE -//Hide/Show Action Buttons ... Button -/obj/screen/movable/action_button/hide_toggle - name = "Hide Buttons" - desc = "Shift-click any button to reset its position, and Control-click it to lock it in place. Alt-click this button reset all buttons to their default positions." - icon = 'icons/mob/actions.dmi' - icon_state = "bg_default" - var/hidden = FALSE - -// /obj/screen/movable/action_button/hide_toggle/Initialize() -// . = ..() -// var/static/list/icon_cache = list() - -// var/cache_key = "[hide_icon][hide_state]" -// hide_appearance = icon_cache[cache_key] -// if(!hide_appearance) -// hide_appearance = icon_cache[cache_key] = mutable_appearance(hide_icon, hide_state) -// cache_key = "[hide_icon][show_state]" -// show_appearance = icon_cache[cache_key] -// if(!show_appearance) -// show_appearance = icon_cache[cache_key] = mutable_appearance(hide_icon, show_state) - -/obj/screen/movable/action_button/hide_toggle/Click(location, control, params) +// Entered and Exited won't fire while you're dragging something, because you're still "holding" it +// Very much byond logic, but I want nice behavior, so we fake it with drag +/obj/screen/movable/action_button/MouseDrag(atom/over_object, src_location, over_location, src_control, over_control, params) + . = ..() if(!can_use(usr)) - return FALSE + return + if(IS_WEAKREF_OF(over_object, last_hovored_ref)) + return - var/list/modifiers = params2list(params) - if(LAZYACCESS(modifiers, SHIFT_CLICK)) - if(locked) - to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first.")) - return TRUE - moved = FALSE - usr.update_action_buttons(TRUE) - return TRUE - if(LAZYACCESS(modifiers, CTRL_CLICK)) - locked = !locked - to_chat(usr, span_notice("Action button \"[name]\" [locked ? "" : "un"]locked.")) - if(id && usr?.client?.prefs) // try to (un)remember position - LAZYSET(usr.client.prefs.action_button_screen_locs, "[name]_[id]", locked ? moved : null) - return TRUE - if(LAZYACCESS(modifiers, ALT_CLICK)) - AltClick(usr) - return TRUE + var/atom/old_object + if(last_hovored_ref) + old_object = last_hovored_ref.resolve() + else // If there is no current ref, we assume it was us. We also treat this as our "first go" location. + old_object = src + var/datum/hud/our_hud = usr.hud_used + our_hud?.generate_landings(src) - usr.hud_used.action_buttons_hidden = !usr.hud_used.action_buttons_hidden + if(old_object) + old_object.MouseExited(over_location, over_control, params) - hidden = usr.hud_used.action_buttons_hidden - if(hidden) - name = "Show Buttons" - else - name = "Hide Buttons" - update_icon() - usr.update_action_buttons() - -/obj/screen/movable/action_button/hide_toggle/AltClick(mob/user) - for(var/datum/action/A as anything in user.actions) - var/obj/screen/movable/action_button/B = A.button - B.moved = FALSE - if(B.id && usr?.client?.prefs) - LAZYSET(usr.client.prefs.action_button_screen_locs, "[name]_[B.id]", null) - // B.locked = usr.client.prefs.buttons_locked - // locked = usr.client.prefs.buttons_locked - if(id && usr?.client?.prefs) - LAZYSET(usr.client.prefs.action_button_screen_locs, "[name]_[id]", null) - moved = FALSE - user.update_action_buttons(TRUE) - to_chat(user, span_notice("Action button positions have been reset.")) - -/obj/screen/movable/action_button/hide_toggle/proc/InitialiseIcon(mob/user) - // var/settings = owner_hud.get_action_buttons_icons() - // icon = settings["bg_icon"] - // icon_state = settings["bg_state"] - // hide_icon = settings["toggle_icon"] - // hide_state = settings["toggle_hide"] - // show_state = settings["toggle_show"] - if(isalien(user)) - icon_state = "bg_alien" - else - icon_state = "bg_default" - update_icon() - -/obj/screen/movable/action_button/hide_toggle/update_icon() - cut_overlays() - add_overlay(mutable_appearance(icon, hidden ? "show" : "hide")) - // add_overlay(mutable_appearance(hide_icon, hidden ? show_state : hide_state)) + last_hovored_ref = WEAKREF(over_object) + over_object?.MouseEntered(over_location, over_control, params) /obj/screen/movable/action_button/MouseEntered(location, control, params) . = ..() @@ -151,89 +92,391 @@ /obj/screen/movable/action_button/MouseExited(location, control, params) closeToolTip(usr) + return ..() -// /datum/hud/proc/get_action_buttons_icons() -// . = list() -// .["bg_icon"] = ui_style_icon -// .["bg_state"] = "template" +/obj/screen/movable/action_button/MouseDrop(over_object) + last_hovored_ref = null + if(!can_use(usr)) + return + var/datum/hud/our_hud = usr.hud_used + if(over_object == src) + our_hud.hide_landings() + return + if(istype(over_object, /obj/screen/action_landing)) + var/obj/screen/action_landing/reserve = over_object + reserve.hit_by(src) + our_hud.hide_landings() + save_position() + return -// //TODO : Make these fit theme -// .["toggle_icon"] = 'icons/mob/actions.dmi' -// .["toggle_hide"] = "hide" -// .["toggle_show"] = "show" + our_hud.hide_landings() + if(istype(over_object, /obj/screen/button_palette) || istype(over_object, /obj/screen/palette_scroll)) + our_hud.position_action(src, SCRN_OBJ_IN_PALETTE) + save_position() + return + if(istype(over_object, /obj/screen/movable/action_button)) + var/obj/screen/movable/action_button/button = over_object + our_hud.position_action_relative(src, button) + save_position() + return + . = ..() + our_hud.position_action(src, screen_loc) + save_position() -//used to update the buttons icon. -/mob/proc/update_action_buttons_icon(status_only = FALSE) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon(status_only) +/obj/screen/movable/action_button/proc/save_position() + var/mob/user = our_hud.mymob + if(!user?.client) + return + var/position_info = "" + switch(location) + if(SCRN_OBJ_FLOATING) + position_info = screen_loc + if(SCRN_OBJ_IN_LIST) + position_info = SCRN_OBJ_IN_LIST + if(SCRN_OBJ_IN_PALETTE) + position_info = SCRN_OBJ_IN_PALETTE -//This is the proc used to update all the action buttons. -/mob/proc/update_action_buttons(reload_screen) + LAZYSET(user.client.prefs.action_button_screen_locs, "[name]_[id]", position_info) + +/obj/screen/movable/action_button/proc/load_position() + var/mob/user = our_hud.mymob + if(!user) + return + var/position_info = LAZYACCESS(user.client?.prefs?.action_button_screen_locs, "[name]_[id]") || SCRN_OBJ_DEFAULT + user.hud_used.position_action(src, position_info) + +/obj/screen/movable/action_button/proc/dump_save() + var/mob/user = our_hud.mymob + if(!user?.client) + return + LAZYREMOVE(user.client.prefs.action_button_screen_locs, "[name]_[id]") + +/** + * This is a silly proc used in hud code code to determine what icon and icon state we should be using + * for hud elements (such as action buttons) that don't have their own icon and icon state set. + * + * It returns a list, which is pretty much just a struct of info + */ +/datum/hud/proc/get_action_buttons_icons() + . = list() + .["bg_icon"] = 'icons/mob/actions/backgrounds.dmi' // ui_style // TODO: Implement hud-specific icon stuff + .["bg_state"] = "bg_default" + .["bg_state_active"] = "bg_default_on" + + +/** + * Updates all action buttons this mob has. + * + * Arguments: + * * update_flags - Which flags of the action should we update + * * force - Force buttons update even if the given button icon state has not changed + */ +/mob/proc/update_mob_action_buttons(update_flags = ALL, force = FALSE) + for(var/datum/action/current_action as anything in actions) + current_action.build_all_button_icons(update_flags, force) + +/** + * This proc handles adding all of the mob's actions to their screen + * + * If you just need to update existing buttons, use [/mob/proc/update_mob_action_buttons]! + * + * Arguments: + * * update_flags - reload_screen - bool, if TRUE, this proc will add the button to the screen of the passed mob as well + */ +/mob/proc/update_action_buttons(reload_screen = FALSE) if(!hud_used || !client) return if(!hud_used.hud_shown) //Hud toggled to minimal return - var/button_number = 0 - - if(hud_used.action_buttons_hidden) - for(var/datum/action/A in actions) - A.button.screen_loc = null - if(reload_screen) - client.screen += A.button - else - for(var/datum/action/A in actions) - button_number++ - A.UpdateButtonIcon() - var/obj/screen/movable/action_button/B = A.button - if(!B.moved) - B.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number) - else - B.screen_loc = B.moved - if(reload_screen) - client.screen += B - - if(!button_number) - hud_used.hide_actions_toggle?.screen_loc = null - return - - // not exactly sure how this happens but it does - if(hud_used.hide_actions_toggle) - if(!hud_used.hide_actions_toggle.moved) - hud_used.hide_actions_toggle.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number+1) - else - hud_used.hide_actions_toggle.screen_loc = hud_used.hide_actions_toggle.moved + for(var/datum/action/action as anything in actions) + var/obj/screen/movable/action_button/button = action.viewers[hud_used] + action.build_all_button_icons() if(reload_screen) - client.screen += hud_used.hide_actions_toggle + client.screen += button -#define AB_WEST_OFFSET 4 -#define AB_NORTH_OFFSET 26 -#define AB_MAX_COLUMNS 10 + if(reload_screen) + hud_used.update_our_owner() + // This holds the logic for the palette buttons + hud_used?.palette_actions?.refresh_actions() -/datum/hud/proc/ButtonNumberToScreenCoords(var/number) // TODO : Make this zero-indexed for readabilty - var/row = round((number-1)/AB_MAX_COLUMNS) - var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1 +/** + * Show (most) of the another mob's action buttons to this mob + * + * Used for observers viewing another mob's screen + */ +/mob/proc/show_other_mob_action_buttons(mob/take_from) + if(!hud_used || !client) + return - var/coord_col = "+[col-1]" - var/coord_col_offset = AB_WEST_OFFSET+2*col + for(var/datum/action/action as anything in take_from.actions) + if(!action.show_to_observers) + continue + action.GiveAction(src) + RegisterSignal(take_from, COMSIG_MOB_GRANTED_ACTION, PROC_REF(on_observing_action_granted)) + RegisterSignal(take_from, COMSIG_MOB_REMOVED_ACTION, PROC_REF(on_observing_action_removed)) - var/coord_row = "[-1 - row]" - var/coord_row_offset = AB_NORTH_OFFSET +/** + * Hide another mob's action buttons from this mob + * + * Used for observers viewing another mob's screen + */ +/mob/proc/hide_other_mob_action_buttons(mob/take_from) + for(var/datum/action/action as anything in take_from.actions) + action.HideFrom(src) + UnregisterSignal(take_from, list(COMSIG_MOB_GRANTED_ACTION, COMSIG_MOB_REMOVED_ACTION)) - return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:[coord_row_offset]" +/// Signal proc for [COMSIG_MOB_GRANTED_ACTION] - If we're viewing another mob's action buttons, +/// we need to update with any newly added buttons granted to the mob. +/mob/proc/on_observing_action_granted(mob/living/source, datum/action/action) + SIGNAL_HANDLER -/datum/hud/proc/SetButtonCoords(var/obj/screen/button,var/number) - var/row = round((number-1)/AB_MAX_COLUMNS) - var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1 - var/x_offset = 32*(col-1) + AB_WEST_OFFSET + 2*col - var/y_offset = -32*(row+1) + AB_NORTH_OFFSET + if(!action.show_to_observers) + return + action.GiveAction(src) - var/matrix/M = matrix() - M.Translate(x_offset,y_offset) - button.transform = M +/// Signal proc for [COMSIG_MOB_REMOVED_ACTION] - If we're viewing another mob's action buttons, +/// we need to update with any removed buttons from the mob. +/mob/proc/on_observing_action_removed(mob/living/source, datum/action/action) + SIGNAL_HANDLER -#undef AB_WEST_OFFSET -#undef AB_NORTH_OFFSET -#undef AB_MAX_COLUMNS + action.HideFrom(src) + +// Button Palette +// A new way to interact with actions + +/obj/screen/button_palette + desc = "Drag buttons to move them
Shift-click any button to reset it
Alt-click this to reset all buttons" + icon = 'icons/hud/64x16_actions.dmi' + icon_state = "screen_gen_palette" + screen_loc = ui_action_palette + var/datum/hud/our_hud + var/expanded = FALSE + /// Id of any currently running timers that set our color matrix + var/color_timer_id + +/obj/screen/button_palette/Destroy() + if(our_hud) + our_hud.mymob?.client?.screen -= src + our_hud.toggle_palette = null + our_hud = null + return ..() + +/obj/screen/button_palette/Initialize(mapload) + . = ..() + // update_appearance() + update_name() + +/obj/screen/button_palette/proc/set_hud(datum/hud/our_hud) + src.our_hud = our_hud + refresh_owner() + +// /obj/screen/button_palette/update_name(updates) +/obj/screen/button_palette/proc/update_name() + // . = ..() + if(expanded) + name = "Hide Buttons" + else + name = "Show Buttons" + +/obj/screen/button_palette/proc/refresh_owner() + var/mob/viewer = our_hud.mymob + if(viewer.client) + viewer.client.screen |= src + + // var/list/settings = our_hud.get_action_buttons_icons() + // var/ui_icon = "[settings["bg_icon"]]" + // var/list/ui_segments = splittext(ui_icon, ".") + // var/list/ui_paths = splittext(ui_segments[1], "/") + // var/ui_name = ui_paths[length(ui_paths)] + + // icon_state = "[ui_name]_palette" + +/obj/screen/button_palette/MouseEntered(location, control, params) + . = ..() + if(QDELETED(src)) + return + show_tooltip(params) + +/obj/screen/button_palette/MouseExited() + closeToolTip(usr) + return ..() + +/obj/screen/button_palette/proc/show_tooltip(params) + openToolTip(usr, src, params, title = name, content = desc) + +GLOBAL_LIST_INIT(palette_added_matrix, list(0.4,0.5,0.2,0, 0,1.4,0,0, 0,0.4,0.6,0, 0,0,0,1, 0,0,0,0)) +GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6,0, 0,0,0,1, 0,0,0,0)) + +/obj/screen/button_palette/proc/play_item_added() + color_for_now(GLOB.palette_added_matrix) + +/obj/screen/button_palette/proc/play_item_removed() + color_for_now(GLOB.palette_removed_matrix) + +/obj/screen/button_palette/proc/color_for_now(list/color) + if(color_timer_id) + return + add_atom_colour(color, TEMPORARY_COLOUR_PRIORITY) //We unfortunately cannot animate matrix colors. Curse you lummy it would be ~~non~~trivial to interpolate between the two valuessssssssss + color_timer_id = addtimer(CALLBACK(src, PROC_REF(remove_color), color), 2 SECONDS) + +/obj/screen/button_palette/proc/remove_color(list/to_remove) + color_timer_id = null + remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, to_remove) + +/obj/screen/button_palette/proc/can_use(mob/user) + if(isobserver(user)) + // var/mob/dead/observer/O = user + // return !O.observetarget + return TRUE + return TRUE + +/obj/screen/button_palette/Click(location, control, params) + if(!can_use(usr)) + return + + var/list/modifiers = params2list(params) + + if(LAZYACCESS(modifiers, ALT_CLICK)) + for(var/datum/action/action as anything in usr.actions) // Reset action positions to default + for(var/datum/hud/hud as anything in action.viewers) + var/obj/screen/movable/action_button/button = action.viewers[hud] + hud.position_action(button, SCRN_OBJ_DEFAULT) + to_chat(usr, span_notice("Action button positions have been reset.")) + return TRUE + + set_expanded(!expanded) + +/obj/screen/button_palette/proc/clicked_while_open(datum/source, atom/target, atom/location, control, params, mob/user) + if(istype(target, /obj/screen/movable/action_button) || istype(target, /obj/screen/palette_scroll) || target == src) // If you're clicking on an action button, or us, you can live + return + set_expanded(FALSE) + if(source) + UnregisterSignal(source, COMSIG_CLIENT_CLICK) + +/obj/screen/button_palette/proc/set_expanded(new_expanded) + var/datum/action_group/our_group = our_hud.palette_actions + if(!length(our_group.actions)) //Looks dumb, trust me lad + new_expanded = FALSE + if(expanded == new_expanded) + return + + expanded = new_expanded + our_group.refresh_actions() + // update_appearance() + update_name() + + if(!usr.client) + return + + if(expanded) + RegisterSignal(usr.client, COMSIG_CLIENT_CLICK, PROC_REF(clicked_while_open)) + else + UnregisterSignal(usr.client, COMSIG_CLIENT_CLICK) + + closeToolTip(usr) //Our tooltips are now invalid, can't seem to update them in one frame, so here, just close them + +/obj/screen/palette_scroll + icon = 'icons/hud/screen_gen.dmi' + screen_loc = ui_palette_scroll + /// How should we move the palette's actions? + /// Positive scrolls down the list, negative scrolls back + var/scroll_direction = 0 + var/datum/hud/our_hud + +/obj/screen/palette_scroll/proc/can_use(mob/user) + if(isobserver(user)) + // var/mob/dead/observer/O = user + // return !O.observetarget + return TRUE + return TRUE + +/obj/screen/palette_scroll/proc/set_hud(datum/hud/our_hud) + src.our_hud = our_hud + refresh_owner() + +/obj/screen/palette_scroll/proc/refresh_owner() + var/mob/viewer = our_hud.mymob + if(viewer.client) + viewer.client.screen |= src + + // var/list/settings = our_hud.get_action_buttons_icons() + // icon = settings["bg_icon"] + +/obj/screen/palette_scroll/Click(location, control, params) + if(!can_use(usr)) + return + our_hud.palette_actions.scroll(scroll_direction) + +/obj/screen/palette_scroll/MouseEntered(location, control, params) + . = ..() + if(QDELETED(src)) + return + openToolTip(usr, src, params, title = name, content = desc) + +/obj/screen/palette_scroll/MouseExited() + closeToolTip(usr) + return ..() + +/obj/screen/palette_scroll/down + name = "Scroll Down" + desc = "Click on this to scroll the actions above down" + icon_state = "scroll_down" + scroll_direction = 1 + +/obj/screen/palette_scroll/down/Destroy() + if(our_hud) + our_hud.mymob?.client?.screen -= src + our_hud.palette_down = null + our_hud = null + return ..() + +/obj/screen/palette_scroll/up + name = "Scroll Up" + desc = "Click on this to scroll the actions above up" + icon_state = "scroll_up" + scroll_direction = -1 + +/obj/screen/palette_scroll/up/Destroy() + if(our_hud) + our_hud.mymob?.client?.screen -= src + our_hud.palette_up = null + our_hud = null + return ..() + +/// Exists so you have a place to put your buttons when you move them around +/obj/screen/action_landing + name = "Button Space" + desc = "Drag and drop a button into this spot
to add it to the group" + icon = 'icons/hud/screen_gen.dmi' + icon_state = "reserved" + // We want our whole 32x32 space to be clickable, so dropping's forgiving + mouse_opacity = MOUSE_OPACITY_OPAQUE + var/datum/action_group/owner + +/obj/screen/action_landing/Destroy() + if(owner) + owner.landing = null + owner?.owner?.mymob?.client?.screen -= src + owner.refresh_actions() + owner = null + return ..() + +/obj/screen/action_landing/proc/set_owner(datum/action_group/owner) + src.owner = owner + refresh_owner() + +/obj/screen/action_landing/proc/refresh_owner() + var/datum/hud/our_hud = owner.owner + var/mob/viewer = our_hud.mymob + if(viewer.client) + viewer.client.screen |= src + + // var/list/settings = our_hud.get_action_buttons_icons() + // icon = settings["bg_icon"] + +/// Reacts to having a button dropped on it +/obj/screen/action_landing/proc/hit_by(obj/screen/movable/action_button/button) + var/datum/hud/our_hud = owner.owner + our_hud.position_action(button, owner.location) diff --git a/code/_onclick/hud/action/innate_action.dm b/code/_onclick/hud/action/innate_action.dm new file mode 100644 index 0000000000..cdfb169ddb --- /dev/null +++ b/code/_onclick/hud/action/innate_action.dm @@ -0,0 +1,20 @@ +//Preset for general and toggled actions +/datum/action/innate + check_flags = NONE + /// Whether we're active or not, if we're a innate - toggle action. + var/active = 0 + +/datum/action/innate/Trigger(trigger_flags) + if(!..()) + return 0 + if(!active) + Activate() + else + Deactivate() + return 1 + +/datum/action/innate/proc/Activate() + return + +/datum/action/innate/proc/Deactivate() + return diff --git a/code/_onclick/hud/action/item_action.dm b/code/_onclick/hud/action/item_action.dm new file mode 100644 index 0000000000..4644efbf83 --- /dev/null +++ b/code/_onclick/hud/action/item_action.dm @@ -0,0 +1,37 @@ + +//Presets for item actions +/datum/action/item_action + check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_LYING|AB_CHECK_CONSCIOUS + // If you want to override the normal icon being the item + // then change this to an icon state + button_icon_state = null + +/datum/action/item_action/New(Target) + . = ..() + + // If our button state is null, use the target's icon instead + if(target && isnull(button_icon_state)) + AddComponent(/datum/component/action_item_overlay, target) + +/datum/action/item_action/vv_edit_var(var_name, var_value) + . = ..() + if(!. || !target) + return + + if(var_name == NAMEOF(src, button_icon_state)) + // If someone vv's our icon either add or remove the component + if(isnull(var_name)) + AddComponent(/datum/component/action_item_overlay, target) + else + qdel(GetComponent(/datum/component/action_item_overlay)) + +/datum/action/item_action/Trigger(trigger_flags) + if(!..()) + return 0 + if(target) + var/obj/item/item_target = target + item_target.ui_action_click(owner, src.type) + return 1 + +/datum/action/item_action/hands_free + check_flags = AB_CHECK_CONSCIOUS diff --git a/code/_onclick/hud/action/positioning.dm b/code/_onclick/hud/action/positioning.dm new file mode 100644 index 0000000000..1ed18d87cd --- /dev/null +++ b/code/_onclick/hud/action/positioning.dm @@ -0,0 +1,49 @@ +/datum/hud/proc/position_action(obj/screen/movable/action_button/button, position) + if(button.location != SCRN_OBJ_DEFAULT) + hide_action(button) + switch(position) + if(SCRN_OBJ_DEFAULT) // Reset to the default + button.dump_save() // Nuke any existing saves + position_action(button, button.linked_action.default_button_position) + return + if(SCRN_OBJ_IN_LIST) + listed_actions.insert_action(button) + if(SCRN_OBJ_IN_PALETTE) + palette_actions.insert_action(button) + else // If we don't have it as a define, this is a screen_loc, and we should be floating + floating_actions += button + button.screen_loc = position + position = SCRN_OBJ_FLOATING + + button.location = position + +/datum/hud/proc/position_action_relative(obj/screen/movable/action_button/button, obj/screen/movable/action_button/relative_to) + if(button.location != SCRN_OBJ_DEFAULT) + hide_action(button) + switch(relative_to.location) + if(SCRN_OBJ_IN_LIST) + listed_actions.insert_action(button, listed_actions.index_of(relative_to)) + if(SCRN_OBJ_IN_PALETTE) + palette_actions.insert_action(button, palette_actions.index_of(relative_to)) + if(SCRN_OBJ_FLOATING) // If we don't have it as a define, this is a screen_loc, and we should be floating + floating_actions += button + var/client/our_client = mymob.client + if(!our_client) + position_action(button, button.linked_action.default_button_position) + return + button.screen_loc = get_valid_screen_location(relative_to.screen_loc, world.icon_size, our_client.view) // Asks for a location adjacent to our button that won't overflow the map + + button.location = relative_to.location + +/// Removes the passed in action from its current position on the screen +/datum/hud/proc/hide_action(obj/screen/movable/action_button/button) + switch(button.location) + if(SCRN_OBJ_DEFAULT) // Invalid + CRASH("We just tried to hide an action buttion that somehow has the default position as its location, you done fucked up") + if(SCRN_OBJ_FLOATING) + floating_actions -= button + if(SCRN_OBJ_IN_LIST) + listed_actions.remove_action(button) + if(SCRN_OBJ_IN_PALETTE) + palette_actions.remove_action(button) + button.screen_loc = null diff --git a/code/_onclick/hud/action/types/item.dm b/code/_onclick/hud/action/types/item.dm index df2d770aec..6adec05d61 100644 --- a/code/_onclick/hud/action/types/item.dm +++ b/code/_onclick/hud/action/types/item.dm @@ -4,7 +4,6 @@ /datum/action/item_action/activate/New(Target, name) . = ..() src.name = name - button.name = name // Specific names /datum/action/item_action/toggle_grippers diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index c2b54a001b..774acba147 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -186,8 +186,15 @@ var/list/global_huds = list( var/list/miniobjs var/list/obj/screen/hotkeybuttons - var/obj/screen/movable/action_button/hide_toggle/hide_actions_toggle - var/action_buttons_hidden = 0 + var/obj/screen/button_palette/toggle_palette + var/obj/screen/palette_scroll/down/palette_down + var/obj/screen/palette_scroll/up/palette_up + + var/datum/action_group/palette/palette_actions + var/datum/action_group/listed/listed_actions + var/list/floating_actions + + var/list/slot_info var/icon/ui_style @@ -205,9 +212,19 @@ var/list/global_huds = list( ..() /datum/hud/Destroy() - . = ..() + if(mymob.hud_used == src) + mymob.hud_used = null + QDEL_NULL_LIST(minihuds) - QDEL_NULL(hide_actions_toggle) + + // Actions + QDEL_NULL(toggle_palette) + QDEL_NULL(palette_down) + QDEL_NULL(palette_up) + QDEL_NULL(palette_actions) + QDEL_NULL(listed_actions) + QDEL_LIST(floating_actions) + grab_intent = null hurt_intent = null disarm_intent = null @@ -232,6 +249,8 @@ var/list/global_huds = list( ammo_hud_list = null mymob = null + return ..() + /datum/hud/proc/hidden_inventory_update() if(!mymob) return if(ishuman(mymob)) @@ -323,10 +342,15 @@ var/list/global_huds = list( return 0 mymob.create_mob_hud(src) - hide_actions_toggle = new() - hide_actions_toggle.InitialiseIcon(mymob) - // if(mymob.client) - // hide_actions_toggle.locked = mymob.client.prefs.buttons_locked + + // Past this point, mymob.hud_used is set + + toggle_palette = new() + toggle_palette.set_hud(src) + palette_down = new() + palette_down.set_hud(src) + palette_up = new() + palette_up.set_hud(src) persistant_inventory_update() mymob.reload_fullscreen() // Reload any fullscreen overlays this mob has. @@ -340,6 +364,11 @@ var/list/global_huds = list( HUD.ui_style = ui_style2icon(client?.prefs?.UI_style) HUD.ui_color = client?.prefs?.UI_style_color HUD.ui_alpha = client?.prefs?.UI_style_alpha + set_hud_used(HUD) + +/mob/proc/set_hud_used(datum/hud/new_hud) + hud_used = new_hud + new_hud.build_action_groups() /datum/hud/proc/apply_minihud(var/datum/mini_hud/MH) if(MH in minihuds) @@ -409,7 +438,7 @@ var/list/global_huds = list( hud_used?.action_intent.screen_loc = ui_acti //Restore intent selection to the original position client.screen += zone_sel //This one is a special snowflake - client.screen += hud_used.hide_actions_toggle + client.screen += hud_used.toggle_palette hud_used.hidden_inventory_update() hud_used.persistant_inventory_update() diff --git a/code/_onclick/hud/movable_screen_objects.dm b/code/_onclick/hud/movable_screen_objects.dm index daa82352e4..6179fd09c6 100644 --- a/code/_onclick/hud/movable_screen_objects.dm +++ b/code/_onclick/hud/movable_screen_objects.dm @@ -11,6 +11,7 @@ /obj/screen/movable mouse_drag_pointer = 'icons/effects/mouse_pointers/screen_drag.dmi' var/snap2grid = FALSE + // TODO: Check if these can safely be deleted var/moved = FALSE var/locked = FALSE var/x_off = -16 diff --git a/code/game/gamemodes/technomancer/devices/shield_armor.dm b/code/game/gamemodes/technomancer/devices/shield_armor.dm index c991f6e164..20c42faa30 100644 --- a/code/game/gamemodes/technomancer/devices/shield_armor.dm +++ b/code/game/gamemodes/technomancer/devices/shield_armor.dm @@ -78,7 +78,7 @@ to_chat(user, span_notice("You [active ? "" : "de"]activate \the [src].")) update_icon() user.update_inv_wear_suit() - user.update_action_buttons_icon() + user.update_mob_action_buttons() /obj/item/clothing/suit/armor/shield/update_icon() icon_state = "shield_armor_[active]" diff --git a/code/game/gamemodes/technomancer/devices/tesla_armor.dm b/code/game/gamemodes/technomancer/devices/tesla_armor.dm index 01a65b5a66..c6e1ae3cc4 100644 --- a/code/game/gamemodes/technomancer/devices/tesla_armor.dm +++ b/code/game/gamemodes/technomancer/devices/tesla_armor.dm @@ -57,7 +57,7 @@ to_chat(user, span_notice("You [active ? "" : "de"]activate \the [src].")) update_icon() user.update_inv_wear_suit() - user.update_action_buttons_icon() + user.update_mob_action_buttons() /obj/item/clothing/suit/armor/tesla/update_icon() if(active && ready) @@ -72,7 +72,7 @@ if(ishuman(loc)) var/mob/living/carbon/human/H = loc H.update_inv_wear_suit(0) - H.update_action_buttons_icon() + H.update_mob_action_buttons() ..() /obj/item/clothing/suit/armor/tesla/proc/shoot_lightning(mob/target, power) diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm index 0ce8eaf590..d3d5b1bd72 100644 --- a/code/game/mecha/mecha_actions.dm +++ b/code/game/mecha/mecha_actions.dm @@ -63,7 +63,9 @@ /datum/action/innate/mecha check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUNNED | AB_CHECK_CONSCIOUS + background_icon = 'icons/effects/actions_mecha.dmi' button_icon = 'icons/effects/actions_mecha.dmi' + overlay_icon = 'icons/effects/actions_mecha.dmi' var/obj/mecha/chassis /datum/action/innate/mecha/Destroy() @@ -82,7 +84,7 @@ /datum/action/innate/mecha/mech_toggle_lights/Activate() button_icon_state = "mech_lights_[chassis.lights ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.lights() @@ -93,7 +95,7 @@ /datum/action/innate/mecha/mech_toggle_internals/Activate() button_icon_state = "mech_internals_[chassis.use_internal_tank ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.internal_tank() @@ -122,7 +124,7 @@ /datum/action/innate/mecha/strafe/Activate() button_icon_state = "mech_strafe_[chassis.strafing ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.strafing() @@ -133,7 +135,7 @@ /datum/action/innate/mecha/mech_defence_mode/Activate() button_icon_state = "mech_defense_mode_[chassis.defence_mode ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.defence_mode() @@ -144,7 +146,7 @@ /datum/action/innate/mecha/mech_overload_mode/Activate() button_icon_state = "mech_overload_[chassis.overload ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.overload() @@ -155,7 +157,7 @@ /datum/action/innate/mecha/mech_smoke/Activate() //button_icon_state = "mech_smoke_[chassis.smoke ? "off" : "on"]" - //UpdateButtonIcon() //Dual colors notneeded ATM + //build_all_button_icons() //Dual colors notneeded ATM chassis.smoke() @@ -166,7 +168,7 @@ /datum/action/innate/mecha/mech_zoom/Activate() button_icon_state = "mech_zoom_[chassis.zoom ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.zoom() @@ -177,7 +179,7 @@ /datum/action/innate/mecha/mech_toggle_thrusters/Activate() button_icon_state = "mech_thrusters_[chassis.thrusters ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.thrusters() @@ -202,7 +204,7 @@ chassis.occupant_message("You select [chassis.selected]") send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list()) button_icon_state = "mech_cycle_equip_on" - UpdateButtonIcon() + build_all_button_icons() return var/number = 0 for(var/A in available_equipment) @@ -217,7 +219,7 @@ chassis.occupant_message("You switch to [chassis.selected]") button_icon_state = "mech_cycle_equip_on" send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list()) - UpdateButtonIcon() + build_all_button_icons() return @@ -232,7 +234,7 @@ button_icon_state = "mech_damtype_[chassis.damtype]" playsound(src, 'sound/mecha/mechmove01.ogg', 50, 1) - UpdateButtonIcon() + build_all_button_icons() chassis.query_damtype() @@ -243,7 +245,7 @@ /datum/action/innate/mecha/mech_toggle_phasing/Activate() button_icon_state = "mech_phasing_[chassis.phasing ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.phasing() @@ -254,7 +256,7 @@ /datum/action/innate/mecha/mech_toggle_cloaking/Activate() button_icon_state = "mech_phasing_[chassis.cloaked ? "off" : "on"]" - UpdateButtonIcon() + build_all_button_icons() chassis.toggle_cloaking() diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index a4629ace92..10aeda384b 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -114,9 +114,9 @@ /obj/item/Initialize(mapload) //CHOMPedit I stg I'm going to overwrite these many uncommented edits. . = ..() for(var/path in actions_types) - new path(src) - if(islist(origin_tech)) - origin_tech = typelist(NAMEOF(src, origin_tech), origin_tech) + add_item_action(path) + /* if(islist(origin_tech)) // CHOMPAdded section, removed because it has a major flaw. If an item has a different origin list than the other items of the same type, they coulf all get that modified list + origin_tech = typelist(NAMEOF(src, origin_tech), origin_tech) */ if(embed_chance < 0) if(sharp) embed_chance = max(5, round(force/w_class)) @@ -137,10 +137,55 @@ m.update_inv_r_hand() m.update_inv_l_hand() src.loc = null - for(var/X in actions) - qdel(X) + + // Handle celaning up our actions list + for(var/datum/action/action as anything in actions) + remove_item_action(action) + return ..() + +/// Called when an action associated with our item is deleted +/obj/item/proc/on_action_deleted(datum/source) + SIGNAL_HANDLER + + if(!(source in actions)) + CRASH("An action ([source.type]) was deleted that was associated with an item ([src]), but was not found in the item's actions list.") + + LAZYREMOVE(actions, source) + +/// Adds an item action to our list of item actions. +/// Item actions are actions linked to our item, that are granted to mobs who equip us. +/// This also ensures that the actions are properly tracked in the actions list and removed if they're deleted. +/// Can be be passed a typepath of an action or an instance of an action. +/obj/item/proc/add_item_action(action_or_action_type) + var/datum/action/action + if(ispath(action_or_action_type, /datum/action)) + action = new action_or_action_type(src) + else if(istype(action_or_action_type, /datum/action)) + action = action_or_action_type + else + CRASH("item add_item_action got a type or instance of something that wasn't an action.") + + LAZYADD(actions, action) + RegisterSignal(action, COMSIG_PARENT_QDELETING, PROC_REF(on_action_deleted)) + if(ismob(loc)) + // We're being held or are equipped by someone while adding an action? + // Then they should also probably be granted the action, given it's in a correct slot + var/mob/holder = loc + give_item_action(action, holder, holder.get_inventory_slot(src)) + + return action + +/// Removes an instance of an action from our list of item actions. +/obj/item/proc/remove_item_action(datum/action/action) + if(!action) + return + + UnregisterSignal(action, COMSIG_PARENT_QDELETING) + LAZYREMOVE(actions, action) + qdel(action) + // Check if target is reasonable for us to operate on. /obj/item/proc/check_allowed_items(atom/target, not_inside, target_self) if(((src in target) && !target_self) || ((!istype(target.loc, /turf)) && (!istype(target, /turf)) && (not_inside))) @@ -338,8 +383,9 @@ if(zoom) zoom() //binoculars, scope, etc appearance_flags &= ~NO_CLIENT_COLOR - for(var/datum/action/A as anything in actions) - A.Remove(user) + // Remove any item actions we temporary gave out. + for(var/datum/action/action_item_has as anything in actions) + action_item_has.Remove(user) // called just as an item is picked up (loc is not yet changed) /obj/item/proc/pickup(mob/user) @@ -365,10 +411,9 @@ // for items that can be placed in multiple slots // note this isn't called during the initial dressing of a player /obj/item/proc/equipped(var/mob/user, var/slot) - for(var/X in actions) - var/datum/action/A = X - if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot. - A.Grant(user) + // Give out actions our item has to people who equip it. + for(var/datum/action/action as anything in actions) + give_item_action(action, user, slot) hud_layerise() user.position_hud_item(src,slot) if(user.client) user.client.screen |= src @@ -383,6 +428,18 @@ playsound(src, pickup_sound, 20, preference = /datum/preference/toggle/pickup_sounds) return +/// Gives one of our item actions to a mob, when equipped to a certain slot +/obj/item/proc/give_item_action(datum/action/action, mob/to_who, slot) + // Some items only give their actions buttons when in a specific slot. + if(!item_action_slot_check(slot, to_who)) + // There is a chance we still have our item action currently, + // and are moving it from a "valid slot" to an "invalid slot". + // So call Remove() here regardless, even if excessive. + action.Remove(to_who) + return + + action.Grant(to_who) + //sometimes we only want to grant the item's action if it's equipped in a specific slot. /obj/item/proc/item_action_slot_check(slot, mob/user) if(slot == SLOT_BACK || slot == LEGS) //these aren't true slots, so avoid granting actions there diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index f78e3e9966..bd04398f2e 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -102,7 +102,7 @@ STOP_PROCESSING(SSobj, src) playsound(src, 'sound/weapons/empty.ogg', 15, 1, -3) // VOREStation Edit update_brightness() - user.update_action_buttons_icon() + user.update_mob_action_buttons() return 1 /obj/item/flashlight/emp_act(severity) diff --git a/code/game/objects/items/weapons/shields_vr.dm b/code/game/objects/items/weapons/shields_vr.dm index f1eb816f67..1de6070647 100644 --- a/code/game/objects/items/weapons/shields_vr.dm +++ b/code/game/objects/items/weapons/shields_vr.dm @@ -64,7 +64,7 @@ set_light(0) light_applied = 0 update_icon(user) - user.update_action_buttons_icon() + user.update_mob_action_buttons() playsound(src, 'sound/weapons/empty.ogg', 15, 1, -3) /obj/item/shield/riot/explorer/update_icon() diff --git a/code/game/objects/items/weapons/tanks/jetpack.dm b/code/game/objects/items/weapons/tanks/jetpack.dm index 42b67842e4..7660cb8e67 100644 --- a/code/game/objects/items/weapons/tanks/jetpack.dm +++ b/code/game/objects/items/weapons/tanks/jetpack.dm @@ -55,7 +55,7 @@ if (ismob(usr)) var/mob/M = usr M.update_inv_back() - M.update_action_buttons_icon() + M.update_mob_action_buttons() to_chat(usr, "You toggle the thrusters [on? "on":"off"].") diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index acf5fe0dc3..133fea38fd 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -52,8 +52,7 @@ return if(istype(src, /mob/living/carbon/human)) var/mob/living/carbon/human/H = src - H.hud_used = new /datum/hud(H) - H.create_mob_hud(H.hud_used) + new /datum/hud(H) /datum/ai_holder var/mob/living/holder = null // The mob this datum is going to control. diff --git a/code/modules/catalogue/cataloguer.dm b/code/modules/catalogue/cataloguer.dm index 766fd2e939..2ea1045dca 100644 --- a/code/modules/catalogue/cataloguer.dm +++ b/code/modules/catalogue/cataloguer.dm @@ -368,7 +368,7 @@ GLOBAL_LIST_EMPTY(all_cataloguers) if (ismob(usr)) var/mob/M = usr - M.update_action_buttons_icon() + M.update_mob_action_buttons() /obj/item/cataloguer/compact/afterattack(atom/target, mob/user, proximity_flag) if(!deployed) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index f9bb791283..72c6f5a171 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -732,3 +732,9 @@ if("Set-Tab") stat_tab = payload["tab"] SSstatpanels.immediate_send_stat_data(src) + + +// Mouse stuff +/client/Click(atom/object, atom/location, control, params) + SEND_SIGNAL(src, COMSIG_CLIENT_CLICK, object, location, control, params, usr) + . = ..() diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index dd6ce9c347..c9388ddea2 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -485,8 +485,7 @@ update_light() update_icon(user) - spawn(10) // FIXME: Remove when SSoverlays stops queueing overlay changes - user.update_action_buttons_icon() + user.update_mob_action_buttons() /obj/item/clothing/head/attack_ai(var/mob/user) if(!mob_wear_hat(user)) diff --git a/code/modules/clothing/glasses/glasses.dm b/code/modules/clothing/glasses/glasses.dm index 5ed049694c..572f45c0b5 100644 --- a/code/modules/clothing/glasses/glasses.dm +++ b/code/modules/clothing/glasses/glasses.dm @@ -71,7 +71,7 @@ BLIND // can't see anything tint = initial(tint) enables_planes = away_planes away_planes = null - user.update_action_buttons_icon() + user.update_mob_action_buttons() user.recalculate_vis() /obj/item/clothing/glasses/attack_self(mob/user) @@ -408,7 +408,7 @@ BLIND // can't see anything tint = TINT_NONE to_chat(usr, "You push \the [src] up out of your face.") update_clothing_icon() - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() /obj/item/clothing/glasses/welding/superior name = "superior welding goggles" @@ -503,7 +503,7 @@ BLIND // can't see anything user << activation_sound user.recalculate_vis() user.update_inv_glasses() - user.update_action_buttons_icon() + user.update_mob_action_buttons() /obj/item/clothing/glasses/sunglasses/sechud/aviator/update_icon() if(on) @@ -623,4 +623,4 @@ BLIND // can't see anything icon_state = "[initial(icon_state)]up" to_chat(usr, "You push \the [src] up from in front of your eyes.") update_clothing_icon() - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() diff --git a/code/modules/clothing/glasses/glasses_vr.dm b/code/modules/clothing/glasses/glasses_vr.dm index 58d7c4ce3e..bfebf06912 100644 --- a/code/modules/clothing/glasses/glasses_vr.dm +++ b/code/modules/clothing/glasses/glasses_vr.dm @@ -106,7 +106,7 @@ if(src && choice && !user.incapacitated() && in_range(user,src)) icon_state = options[choice] user.update_inv_glasses() - user.update_action_buttons_icon() + user.update_mob_action_buttons() to_chat(user, span_notice("Your [src] now displays [choice] .")) return 1 diff --git a/code/modules/clothing/glasses/hud_vr.dm b/code/modules/clothing/glasses/hud_vr.dm index 2bad04d4bc..93a9708d43 100644 --- a/code/modules/clothing/glasses/hud_vr.dm +++ b/code/modules/clothing/glasses/hud_vr.dm @@ -141,7 +141,7 @@ away_planes = null to_chat(usr, span_notice("You enabled the Augmented Reality HUD of your [src.name].")) ar_toggled = !ar_toggled - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() usr.recalculate_vis() @@ -249,7 +249,7 @@ item_state = initial(item_state) usr.update_inv_glasses() to_chat(usr, "You activate the retinal projector on the [src].") - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() /obj/item/clothing/glasses/omnihud/all name = "\improper AR-B glasses" diff --git a/code/modules/clothing/head/helmet_vr.dm b/code/modules/clothing/head/helmet_vr.dm index b02024594c..7b7890b423 100644 --- a/code/modules/clothing/head/helmet_vr.dm +++ b/code/modules/clothing/head/helmet_vr.dm @@ -83,7 +83,7 @@ if (ismob(src.loc)) //should allow masks to update when it is opened/closed var/mob/M = src.loc M.update_inv_wear_mask() - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() // Costume Versions Here /obj/item/clothing/head/helmet/combat/crusader_costume @@ -140,4 +140,4 @@ if (ismob(src.loc)) //should allow masks to update when it is opened/closed var/mob/M = src.loc M.update_inv_wear_mask() - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index 1b4afb4d09..3f179d3e36 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -65,7 +65,7 @@ if (ismob(src.loc)) //should allow masks to update when it is opened/closed var/mob/M = src.loc M.update_inv_wear_mask() - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() /obj/item/clothing/head/welding/demon name = "demonic welding helmet" diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index c821aeeca2..be5dad9f66 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -373,7 +373,7 @@ if(src && choice && !user.incapacitated() && in_range(user,src)) icon_state = options[choice] user.update_inv_wear_mask() - user.update_action_buttons_icon() + user.update_mob_action_buttons() to_chat(user, span_notice("Your paper mask now is now [choice].")) return 1 @@ -408,7 +408,7 @@ if(src && choice && !user.incapacitated() && in_range(user,src)) icon_state = options[choice] user.update_inv_wear_mask() - user.update_action_buttons_icon() + user.update_mob_action_buttons() to_chat(user, span_notice("Your [src] now displays a [choice] emotion.")) return 1 diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index f7729dd89d..1df7f1c759 100644 --- a/code/modules/clothing/shoes/magboots.dm +++ b/code/modules/clothing/shoes/magboots.dm @@ -42,7 +42,7 @@ playsound(src, 'sound/effects/magnetclamp.ogg', 20) to_chat(user, "You enable the mag-pulse traction system.") user.update_inv_shoes() //so our mob-overlays update - user.update_action_buttons_icon() + user.update_mob_action_buttons() /obj/item/clothing/shoes/magboots/mob_can_equip(mob/user, slot, disable_warning = FALSE) var/mob/living/carbon/human/H = user @@ -114,7 +114,7 @@ magpulse = 1 canremove = FALSE //kinda hard to take off magclaws when you are gripping them tightly. to_chat(user, "You dig your claws deeply into the flooring, bracing yourself.") - user.update_action_buttons_icon() + user.update_mob_action_buttons() //In case they somehow come off while enabled. /obj/item/clothing/shoes/magboots/vox/dropped(mob/user as mob) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 56e464bcf5..66e4e29dd7 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -465,7 +465,7 @@ throw_alert("handcuffed", /obj/screen/alert/restrained/handcuffed, new_master = handcuffed) else clear_alert("handcuffed") - update_action_buttons_icon() //some of our action buttons might be unusable when we're handcuffed. + update_mob_action_buttons() //some of our action buttons might be unusable when we're handcuffed. update_inv_handcuffed() // Clears blood overlays diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index d6a81795eb..417d3d35d6 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -1,6 +1,6 @@ /mob/living/death(gibbed) clear_fullscreens() - update_action_buttons_icon() + update_mob_action_buttons() if(ai_holder) ai_holder.go_sleep() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 566cc0113e..82810d0da5 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1041,7 +1041,7 @@ if(lying != lying_prev) lying_prev = lying update_transform() - update_action_buttons_icon(status_only = TRUE) + update_mob_action_buttons() //VOREStation Add if(lying && LAZYLEN(buckled_mobs)) for(var/mob/living/L as anything in buckled_mobs) diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 7f72aa87e3..1d5c8a1d49 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -38,8 +38,9 @@ client.images = null //remove the images such as AIs being unable to see runes client.screen = list() //remove hud items just in case - if(hud_used) qdel(hud_used) //remove the hud objects - hud_used = new /datum/hud(src) + if(hud_used) + qdel(hud_used) //remove the hud objects + new /datum/hud(src) if(client.prefs && client.prefs.client_fps) client.fps = client.prefs.client_fps diff --git a/code/modules/projectiles/guns/projectile/shotgun_vr.dm b/code/modules/projectiles/guns/projectile/shotgun_vr.dm index 8ee2e7d049..f8cdfc8d3b 100644 --- a/code/modules/projectiles/guns/projectile/shotgun_vr.dm +++ b/code/modules/projectiles/guns/projectile/shotgun_vr.dm @@ -69,7 +69,7 @@ H.update_inv_r_hand() playsound(src, 'sound/weapons/targeton.ogg', 50, 1) - user.update_action_buttons_icon() + user.update_mob_action_buttons() /obj/item/gun/projectile/shotgun/compact/verb/verb_toggle_stock(mob/user as mob) set category = "Object" diff --git a/code/modules/vore/fluffstuff/custom_clothes_vr.dm b/code/modules/vore/fluffstuff/custom_clothes_vr.dm index 5c90cefa94..3bd2856644 100644 --- a/code/modules/vore/fluffstuff/custom_clothes_vr.dm +++ b/code/modules/vore/fluffstuff/custom_clothes_vr.dm @@ -1131,7 +1131,7 @@ No. With a teleporter? Just *no*. - Hawk, YW if (ismob(loc)) //should allow masks to update when it is opened/closed var/mob/M = loc M.update_inv_wear_mask() - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() //Vorrarkul: Theodora Lindt /obj/item/clothing/suit/chococoat @@ -2601,7 +2601,7 @@ Departamental Swimsuits, for general use if (ismob(loc)) //should allow masks to update when it is opened/closed var/mob/M = loc M.update_inv_wear_mask() - usr.update_action_buttons_icon() + usr.update_mob_action_buttons() /obj/item/clothing/suit/storage/toggle/labcoat/fluff/zeracloak name = "Grand Purple Cloak" diff --git a/icons/hud/64x16_actions.dmi b/icons/hud/64x16_actions.dmi new file mode 100644 index 0000000000..812d888846 Binary files /dev/null and b/icons/hud/64x16_actions.dmi differ diff --git a/icons/hud/screen_gen.dmi b/icons/hud/screen_gen.dmi new file mode 100644 index 0000000000..86383abbef Binary files /dev/null and b/icons/hud/screen_gen.dmi differ diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi new file mode 100644 index 0000000000..c8f8b723f9 Binary files /dev/null and b/icons/mob/actions/backgrounds.dmi differ diff --git a/vorestation.dme b/vorestation.dme index df662e3d2f..0328b575ab 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -70,6 +70,7 @@ #include "code\__defines\holder.dm" #include "code\__defines\holomap.dm" #include "code\__defines\hoses.dm" +#include "code\__defines\hud.dm" #include "code\__defines\implant.dm" #include "code\__defines\input.dm" #include "code\__defines\instruments.dm" @@ -236,6 +237,7 @@ #include "code\_helpers\visual_filters.dm" #include "code\_helpers\widelists_ch.dm" #include "code\_helpers\graphs\astar_ch.dm" +#include "code\_helpers\weakref.dm" #include "code\_helpers\icons\flatten.dm" #include "code\_helpers\logging\debug.dm" #include "code\_helpers\logging\ui.dm" @@ -256,8 +258,6 @@ #include "code\_onclick\other_mobs.dm" #include "code\_onclick\rig.dm" #include "code\_onclick\telekinesis.dm" -#include "code\_onclick\hud\_defines.dm" -#include "code\_onclick\hud\_defines_vr.dm" #include "code\_onclick\hud\ability_screen_objects.dm" #include "code\_onclick\hud\ai.dm" #include "code\_onclick\hud\alert.dm" @@ -287,7 +287,13 @@ #include "code\_onclick\hud\soulcatcher_guest.dm" #include "code\_onclick\hud\spell_screen_objects.dm" #include "code\_onclick\hud\action\action.dm" +#include "code\_onclick\hud\action\action_group.dm" +#include "code\_onclick\hud\action\action_group_helpers.dm" +#include "code\_onclick\hud\action\action_item_overlay.dm" #include "code\_onclick\hud\action\action_screen_objects.dm" +#include "code\_onclick\hud\action\innate_action.dm" +#include "code\_onclick\hud\action\item_action.dm" +#include "code\_onclick\hud\action\positioning.dm" #include "code\_onclick\hud\action\types\item.dm" #include "code\ATMOSPHERICS\_atmos_setup.dm" #include "code\ATMOSPHERICS\_atmospherics_helpers.dm"