[MIRROR] The final action buttons PR (#9324)

Co-authored-by: ShadowLarkens <shadowlarkens@gmail.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2024-10-26 15:00:36 -07:00
committed by GitHub
parent 196770e16f
commit 09f82b6fff
45 changed files with 1373 additions and 454 deletions

View File

@@ -1,205 +0,0 @@
/*
These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var.
The short version:
Everything is encoded as strings because apparently that's how Byond rolls.
"1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid.
"1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid.
Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy.
The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15".
Therefore, the top right corner (except during admin shenanigans) is at "15,15"
*/
#define ui_entire_screen "WEST,SOUTH to EAST,NORTH"
//Lower left, persistant menu
#define ui_inventory "WEST:6,SOUTH:5"
//Lower center, persistant menu
#define ui_sstore1 "WEST+2:10,SOUTH:5"
#define ui_id "WEST+3:12,SOUTH:5"
#define ui_belt "WEST+4:14,SOUTH:5"
#define ui_back "CENTER-2:14,SOUTH:5"
#define ui_rhand "CENTER-1:16,SOUTH:5"
#define ui_lhand "CENTER:16,SOUTH:5"
#define ui_equip "CENTER-1:16,SOUTH+1:5"
#define ui_swaphand1 "CENTER-1:16,SOUTH+1:5"
#define ui_swaphand2 "CENTER:16,SOUTH+1:5"
#define ui_storage1 "CENTER+1:16,SOUTH:5"
#define ui_storage2 "CENTER+2:16,SOUTH:5"
#define ui_alien_head "CENTER-3:12,SOUTH:5" //aliens
#define ui_alien_oclothing "CENTER-2:14,SOUTH:5"//aliens
#define ui_inv1 "CENTER-1,SOUTH:5" //borgs
#define ui_inv2 "CENTER,SOUTH:5" //borgs
#define ui_inv3 "CENTER+1,SOUTH:5" //borgs
#define ui_borg_store "CENTER+2,SOUTH:5" //borgs
#define ui_borg_inventory "CENTER-2,SOUTH:5"//borgs
#define ui_monkey_mask "WEST+4:14,SOUTH:5" //monkey
#define ui_monkey_back "WEST+5:14,SOUTH:5" //monkey
#define ui_construct_health "EAST:00,CENTER:15" //same height as humans, hugging the right border
#define ui_construct_purge "EAST:00,CENTER-1:15"
#define ui_construct_fire "EAST-1:16,CENTER+1:13" //above health, slightly to the left
#define ui_construct_pull "EAST-1:28,SOUTH+1:10" //above the zone_sel icon
#define ui_pai_comms "EAST-1:28,SOUTH+1:5"
//Lower right, persistant menu
#define ui_dropbutton "EAST-4:22,SOUTH:5"
#define ui_drop_throw "EAST-1:28,SOUTH+1:7"
#define ui_pull_resist "EAST-2:26,SOUTH+1:7"
#define ui_acti "EAST-2:26,SOUTH:5"
#define ui_movi "EAST-3:24,SOUTH:5"
#define ui_zonesel "EAST-1:28,SOUTH:5"
#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12)
#define ui_borg_pull "EAST-4:24,SOUTH+1:7" //borgs
#define ui_borg_radio "EAST-2:26,SOUTH+1:7" //borgs
#define ui_borg_panel "EAST-1:28,SOUTH+1:7" //borgs
#define ui_borg_module "EAST-3:24,SOUTH+1:5"//borgs
#define ui_vtec_control "EAST-3:24,SOUTH:5"//borgs
#define ui_ai_core "SOUTH:6,WEST:16"
#define ui_ai_camera_list "SOUTH:6,WEST+1:16"
#define ui_ai_track_with_camera "SOUTH:6,WEST+2:16"
#define ui_ai_camera_light "SOUTH:6,WEST+3:16"
#define ui_ai_crew_monitor "SOUTH:6,WEST+4:16"
#define ui_ai_crew_manifest "SOUTH:6,WEST+5:16"
#define ui_ai_alerts "SOUTH:6,WEST+6:16"
#define ui_ai_announcement "SOUTH:6,WEST+7:16"
#define ui_ai_shuttle "SOUTH:6,WEST+8:16"
#define ui_ai_state_laws "SOUTH:6,WEST+9:16"
#define ui_ai_pda_send "SOUTH:6,WEST+10:16"
#define ui_ai_pda_log "SOUTH:6,WEST+11:16"
#define ui_ai_take_picture "SOUTH:6,WEST+12:16"
#define ui_ai_view_images "SOUTH:6,WEST+13:16"
#define ui_ai_multicam "SOUTH+1:6,WEST+11:16"
#define ui_ai_add_multicam "SOUTH+1:6,WEST+12:16"
#define ui_ai_updown "SOUTH+1:6,WEST+13:16"
//Upper-middle right (alerts)
#define ui_alert1 "EAST-1:28,CENTER+5:27"
#define ui_alert2 "EAST-1:28,CENTER+4:25"
#define ui_alert3 "EAST-1:28,CENTER+3:23"
#define ui_alert4 "EAST-1:28,CENTER+2:21"
#define ui_alert5 "EAST-1:28,CENTER+1:19"
//Gun buttons
#define ui_gun1 "EAST-2:26,SOUTH+2:7"
#define ui_gun2 "EAST-1:28, SOUTH+3:7"
#define ui_gun3 "EAST-2:26,SOUTH+3:7"
#define ui_gun_select "EAST-1:28,SOUTH+2:7"
#define ui_gun4 "EAST-3:24,SOUTH+2:7"
//Upper-middle right (damage indicators)
#define ui_toxin "EAST-1:28,NORTH-2:27"
#define ui_fire "EAST-1:28,NORTH-3:25"
#define ui_oxygen "EAST-1:28,NORTH-4:23"
#define ui_pressure "EAST-1:28,NORTH-5:21"
#define ui_alien_toxin "EAST-1:28,NORTH-2:25"
#define ui_alien_fire "EAST-1:28,NORTH-3:25"
#define ui_alien_oxygen "EAST-1:28,NORTH-4:25"
// Goes above HUD, mid-right
#define ui_ammo_hud1 "EAST-1:28,CENTER+1:25"
#define ui_ammo_hud2 "EAST-1:28,CENTER+2:27"
#define ui_ammo_hud3 "EAST-1:28,CENTER+3:29"
#define ui_ammo_hud4 "EAST-1:28,CENTER+4:31"
//Middle right (status indicators)
#define ui_temp "EAST-1:28,CENTER-2:13"
#define ui_health "EAST-1:28,CENTER-1:15"
#define ui_internal "EAST-1:28,CENTER:17"
#define ui_under_health "EAST-1:28,CENTER-2:13"
//borgs
#define ui_borg_health "EAST-1:28,CENTER-2:13" //borgs have the health display where humans have the pressure damage indicator.
#define ui_borg_under_health "EAST-1:31,CENTER-3:13"
#define ui_alien_health "EAST-1:28,CENTER-2:13" //aliens have the health display where humans have the pressure damage indicator.
#define ui_ling_chemical_display "EAST-1:28,CENTER-3:15"
#define ui_wiz_energy_display "EAST-1:28,CENTER-3:15"
//#define ui_wiz_instability_display "EAST-2:28,CENTER-3:15"
#define ui_wiz_instability_display "EAST-1:28,NORTH-2:27"
//Pop-up inventory
#define ui_shoes "WEST+1:8,SOUTH:5"
#define ui_iclothing "WEST:6,SOUTH+1:7"
#define ui_oclothing "WEST+1:8,SOUTH+1:7"
#define ui_gloves "WEST+2:10,SOUTH+1:7"
#define ui_glasses "WEST:6,SOUTH+2:9"
#define ui_mask "WEST+1:8,SOUTH+2:9"
#define ui_l_ear "WEST+2:10,SOUTH+2:9"
#define ui_r_ear "WEST+2:10,SOUTH+3:11"
#define ui_head "WEST+1:8,SOUTH+3:11"
//Intent small buttons
#define ui_help_small "EAST-3:8,SOUTH:1"
#define ui_disarm_small "EAST-3:15,SOUTH:18"
#define ui_grab_small "EAST-3:32,SOUTH:18"
#define ui_harm_small "EAST-3:39,SOUTH:1"
//#define ui_swapbutton "6:-16,1:5" //Unused
//#define ui_headset "SOUTH,8"
#define ui_hand "CENTER-1:14,SOUTH:5"
#define ui_hstore1 "CENTER-2,CENTER-2"
//#define ui_resist "EAST+1,SOUTH-1"
#define ui_sleep "EAST+1, NORTH-13"
#define ui_rest "EAST+1, NORTH-14"
#define ui_iarrowleft "SOUTH-1,EAST-4"
#define ui_iarrowright "SOUTH-1,EAST-2"
#define ui_spell_master "EAST-2:16,NORTH-1:26"
#define ui_genetic_master "EAST-1:16,NORTH-3:16"
// Ghost ones
// CHOMPedit
#define ui_ghost_returntomenu "SOUTH:6,CENTER-4:24"
// CHOMPedit
#define ui_ghost_vr "SOUTH: 6,CENTER-3:24"
#define ui_ghost_jumptomob "SOUTH:6,CENTER-2:24"
#define ui_ghost_orbit "SOUTH:6,CENTER-1:24"
#define ui_ghost_reenter_corpse "SOUTH:6,CENTER:24"
#define ui_ghost_teleport "SOUTH:6,CENTER+1:24"
#define ui_ghost_pai "SOUTH: 6,CENTER+2:24"
#define ui_ghost_updown "SOUTH: 6,CENTER+3:24"
// NIF Soulcatcher guest ones
#define ui_nifsc_reenter "SOUTH:6,CENTER-3:24"
#define ui_nifsc_arproj "SOUTH:6,CENTER-2:24"
#define ui_nifsc_jumptoowner "SOUTH:6,CENTER-1:24"
#define ui_nifsc_nme "SOUTH:6,CENTER:24"
#define ui_nifsc_nsay "SOUTH:6,CENTER+1:24"
// Rig panel
#define ui_rig_deco1 "WEST:-7, SOUTH+5"
#define ui_rig_deco2 "WEST:-7, SOUTH+6"
#define ui_rig_pwr "WEST+1:-7, SOUTH+6"
#define ui_rig_health "WEST+1:-7, SOUTH+6"
#define ui_rig_air "WEST+1:-7, SOUTH+5"
#define ui_rig_airtoggle "WEST+1:-7, SOUTH+5"
#define ui_rig_deco1_f "WEST+2:-7, SOUTH+5"
#define ui_rig_deco2_f "WEST+2:-7, SOUTH+6"
// Mech panel
#define ui_mech_deco1 "WEST:-7, SOUTH+8"
#define ui_mech_deco2 "WEST:-7, SOUTH+9"
#define ui_mech_pwr "WEST+1:-7, SOUTH+9"
#define ui_mech_health "WEST+1:-7, SOUTH+9"
#define ui_mech_air "WEST+1:-7, SOUTH+8"
#define ui_mech_airtoggle "WEST+1:-7, SOUTH+8"
#define ui_mech_deco1_f "WEST+2:-7, SOUTH+8"
#define ui_mech_deco2_f "WEST+2:-7, SOUTH+9"
#define ui_smallquad "EAST-4:22,SOUTH:5"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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 = "<b>Drag</b> buttons to move them<br><b>Shift-click</b> any button to reset it<br><b>Alt-click</b> 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 = "<b>Click</b> 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 = "<b>Click</b> 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 = "<b>Drag and drop</b> a button into this spot<br>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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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