Files
Aurora.3/code/_onclick/hud/screen_objects.dm
Matt Atlas cca0dfe7b6 Ports /tg/ and CM planemasters to replace our renderers. (#21458)
Part one of our nefarious plans to destroy lag forever, and also
accomplish some cool shit.

This replaces our renderer system with plane masters, this will also be
used to turn skyboxes into backdrops and thus totally remove skybox
updating lag. Additionally, this will let us manipulate entire planes
very easily to do all sorts of zany shit. All credit goes to the
original coders, this is some seriously cool stuff.

Also fixes some bizarre smoothing behaviour, reduces updateoverlays
calls since now not every single structure in the game tries smoothing
with nothing.

<img width="1349" height="1349" alt="image"
src="https://github.com/user-attachments/assets/9b7cecd7-3c47-448b-9dd8-9b904640bf82"
/>
<img width="1349" height="1349" alt="image"
src="https://github.com/user-attachments/assets/647d75a2-1bff-45ca-ab92-0aea10631afd"
/>

---------

Co-authored-by: Matt Atlas <liermattia@gmail.com>
2025-10-16 11:39:15 +00:00

592 lines
16 KiB
Plaintext

/**
Screen objects
Todo: improve/re-implement
Screen objects are only used for the hud and should not appear anywhere "in-game".
They are used with the client/screen list and the screen_loc var.
For more information, see the byond documentation on the screen_loc and screen vars.
*/
/atom/movable/screen
name = ""
icon = 'icons/mob/screen/generic.dmi'
plane = HUD_PLANE
layer = HUD_BASE_LAYER
/// A reference to the object in the slot. Grabs or items, generally.
var/obj/master = null
/// A reference to the owner HUD, if any.
var/datum/hud/hud = null
appearance_flags = NO_CLIENT_COLOR
/atom/movable/screen/Initialize(mapload, ...)
. = ..()
//This is done with signals because the screen code sucks, blame the ancient developers
if(hud)
RegisterSignal(hud, COMSIG_QDELETING, PROC_REF(handle_hud_destruction))
/atom/movable/screen/Destroy(force = FALSE)
master = null
screen_loc = null
hud = null
. = ..()
/// Screen elements are always on top of the players screen and don't move so yes they are adjacent
/atom/movable/screen/Adjacent(atom/neighbor, atom/target, atom/movable/mover)
return TRUE
/**
* Handles the deletion of the HUD this screen is associated to
*/
/atom/movable/screen/proc/handle_hud_destruction()
SIGNAL_HANDLER
UnregisterSignal(hud, COMSIG_QDELETING)
qdel(src)
/atom/movable/screen/text
icon = null
icon_state = null
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
screen_loc = "CENTER-7,CENTER-7"
maptext_height = 480
maptext_width = 480
/atom/movable/screen/inventory
/// The identifier for the slot. It has nothing to do with ID cards.
var/slot_id
/// Required for inventory/screen overlays.
var/list/object_overlays = list()
var/color_changed = FALSE
/atom/movable/screen/inventory/MouseEntered()
..()
add_overlays()
/atom/movable/screen/inventory/MouseExited()
..()
CutOverlays(object_overlays)
object_overlays.Cut()
/atom/movable/screen/inventory/proc/add_overlays()
var/mob/user = hud.mymob
if(hud && user && slot_id)
var/obj/item/holding = user.get_active_hand()
if(!holding || user.get_equipped_item(slot_id))
return
var/image/item_overlay = image(holding)
item_overlay.alpha = 92
if(!holding.mob_can_equip(user, slot_id, disable_warning = TRUE))
item_overlay.color = "#ff0000"
else
item_overlay.color = "#00ff00"
object_overlays += item_overlay
AddOverlays(object_overlays)
/atom/movable/screen/inventory/proc/set_color_for(var/set_color, var/set_time)
if(color_changed)
return
var/old_color = color
color = set_color
color_changed = TRUE
addtimer(CALLBACK(src, PROC_REF(set_color_to), old_color), set_time)
/atom/movable/screen/inventory/proc/set_color_to(var/set_color)
color = set_color
color_changed = FALSE
/atom/movable/screen/close
name = "close"
/atom/movable/screen/close/Click()
if(master)
if(istype(master, /obj/item/storage))
var/obj/item/storage/S = master
S.close(usr)
return 1
/atom/movable/screen/item_action
var/obj/item/owner
/atom/movable/screen/item_action/Destroy()
owner = null
. = ..()
/atom/movable/screen/item_action/Click()
if(!usr || !owner)
return 1
if(!usr.canClick())
return
if(usr.stat || usr.restrained() || usr.stunned || usr.lying)
return 1
if(!(owner in usr))
return 1
owner.ui_action_click()
return 1
/atom/movable/screen/grab
name = "grab"
/atom/movable/screen/grab/Click()
var/obj/item/grab/G = master
G.s_click(src)
return 1
/atom/movable/screen/grab/attack_hand()
return
/atom/movable/screen/grab/attackby()
return
/atom/movable/screen/storage
name = "storage"
screen_loc = "7,7 to 10,8"
plane = HUD_PLANE
layer = HUD_BASE_LAYER
/atom/movable/screen/storage/Click()
if(!usr.canClick())
return TRUE
if(usr.stat || usr.paralysis || usr.stunned || usr.weakened)
return TRUE
if(master)
var/obj/item/I = usr.get_active_hand()
if(I)
usr.ClickOn(master)
return TRUE
/atom/movable/screen/storage/background
name = "background storage"
layer = HUD_BELOW_ITEM_LAYER
/atom/movable/screen/storage/background/Initialize(mapload, var/obj/set_master, var/set_icon_state)
. = ..()
master = set_master
if(master)
name = master.name
icon_state = set_icon_state
/atom/movable/screen/storage/background/Click()
if(usr.incapacitated())
return TRUE
if(master)
usr.ClickOn(master)
return TRUE
/atom/movable/screen/zone_sel
name = "damage zone"
icon_state = "zone_sel"
screen_loc = ui_zonesel
var/selecting = BP_CHEST
var/static/list/hover_overlays_cache = list()
var/hovering_choice
var/mutable_appearance/selecting_appearance
/atom/movable/screen/zone_sel/Click(location, control,params)
if(isobserver(usr))
return
var/list/PL = params2list(params)
var/icon_x = text2num(PL["icon-x"])
var/icon_y = text2num(PL["icon-y"])
var/choice = get_zone_at(icon_x, icon_y)
if(!choice)
return 1
return set_selected_zone(choice, usr)
/atom/movable/screen/zone_sel/MouseEntered(location, control, params)
MouseMove(location, control, params)
/atom/movable/screen/zone_sel/MouseMove(location, control, params)
if(isobserver(usr))
return
var/list/PL = params2list(params)
var/icon_x = text2num(PL["icon-x"])
var/icon_y = text2num(PL["icon-y"])
var/choice = get_zone_at(icon_x, icon_y)
if(hovering_choice == choice)
return
remove_vis_contents(hover_overlays_cache[hovering_choice])
hovering_choice = choice
var/obj/effect/overlay/zone_sel/overlay_object = hover_overlays_cache[choice]
if(!overlay_object)
overlay_object = new
overlay_object.icon_state = "[choice]"
hover_overlays_cache[choice] = overlay_object
add_vis_contents(overlay_object)
/obj/effect/overlay/zone_sel
icon = 'icons/mob/zone_sel.dmi'
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
alpha = 128
anchored = TRUE
plane = HUD_ITEM_LAYER
/atom/movable/screen/zone_sel/MouseExited(location, control, params)
if(!isobserver(usr) && hovering_choice)
remove_vis_contents(hover_overlays_cache[hovering_choice])
hovering_choice = null
/atom/movable/screen/zone_sel/proc/get_zone_at(icon_x, icon_y)
switch(icon_y)
if(1 to 3) //Feet
switch(icon_x)
if(10 to 15)
return BP_R_FOOT
if(17 to 22)
return BP_L_FOOT
if(4 to 9) //Legs
switch(icon_x)
if(10 to 15)
return BP_R_LEG
if(17 to 22)
return BP_L_LEG
if(10 to 13) //Hands and groin
switch(icon_x)
if(8 to 11)
return BP_R_HAND
if(12 to 20)
return BP_GROIN
if(21 to 24)
return BP_L_HAND
if(14 to 22) //Chest and arms to shoulders
switch(icon_x)
if(8 to 11)
return BP_R_ARM
if(12 to 20)
return BP_CHEST
if(21 to 24)
return BP_L_ARM
if(23 to 30) //Head, but we need to check for eye or mouth
if(icon_x in 12 to 20)
switch(icon_y)
if(23 to 24)
if(icon_x in 15 to 17)
return BP_MOUTH
if(26) //Eyeline, eyes are on 15 and 17
if(icon_x in 14 to 18)
return BP_EYES
if(25 to 27)
if(icon_x in 15 to 17)
return BP_EYES
return BP_HEAD
/atom/movable/screen/zone_sel/proc/set_selected_zone(choice, mob/user)
if(isobserver(user))
return
if(choice != selecting)
selecting = choice
update_icon()
SEND_SIGNAL(user, COMSIG_MOB_ZONE_SEL_CHANGE, user)
/atom/movable/screen/zone_sel/update_icon()
ClearOverlays()
selecting_appearance = mutable_appearance('icons/mob/zone_sel.dmi', "[selecting]")
AddOverlays(selecting_appearance)
/atom/movable/screen/Click(location, control, params)
if(!usr)
return TRUE
var/list/modifiers = params2list(params)
var/client/viewer_client = usr?.client
switch(name)
if("toggle")
if(usr.hud_used.inventory_shown)
usr.hud_used.inventory_shown = 0
usr.client.screen -= usr.hud_used.other
else
usr.hud_used.inventory_shown = 1
usr.client.screen += usr.hud_used.other
usr.hud_used.hidden_inventory_update()
if("equip")
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
H.quick_equip()
if("resist")
if(isliving(usr))
var/mob/living/L = usr
L.resist()
if("act_intent")
usr.a_intent_change("right")
if(I_HELP)
usr.set_intent(I_HELP)
usr.hud_used.action_intent.icon_state = "intent_help"
if(I_HURT)
usr.set_intent(I_HURT)
usr.hud_used.action_intent.icon_state = "intent_harm"
if(I_GRAB)
usr.set_intent(I_GRAB)
usr.hud_used.action_intent.icon_state = "intent_grab"
if(I_DISARM)
usr.set_intent(I_DISARM)
usr.hud_used.action_intent.icon_state = "intent_disarm"
if("pull")
usr.stop_pulling()
if("throw")
if(!usr.stat && isturf(usr.loc) && !usr.restrained())
usr.toggle_throw_mode()
if("drop")
if(usr.client)
usr.client.drop_item()
if("up hint")
if(modifiers["shift"])
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
if(H.last_special + 50 > world.time)
to_chat(usr, "You cannot do that again so quickly.")
return
H.last_special = world.time
to_chat(usr, SPAN_NOTICE("You take look around to see if there are any holes in the roof around."))
for(var/turf/T in view(viewer_client.view, usr))
if(T.density)
continue
var/turf/above_turf = GET_TURF_ABOVE(T)
if(!isopenturf(above_turf))
continue
var/image/point_up = image(icon = 'icons/mob/screen/generic.dmi', icon_state = "arrow_up", loc = T)
point_up.plane = GAME_PLANE
point_up.layer = POINTER_LAYER
viewer_client?.images += point_up
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(remove_image_from_client), point_up, viewer_client), 3 SECONDS)
return
if(!isliving(usr))
return
var/mob/living/user = usr
var/turf/T1 = get_turf(user)
var/turf/T = GET_TURF_ABOVE(T1)
if (!T)
to_chat(user, SPAN_NOTICE("There is nothing above you!"))
else if (isopenturf(T))
user.look_up_open_space(T1)
else
to_chat(user, SPAN_NOTICE("You see a ceiling staring back at you."))
if("module")
if(isrobot(usr))
var/mob/living/silicon/robot/R = usr
if(modifiers["shift"])
if(R.module)
to_chat(R, SPAN_NOTICE("You currently have the [R.module.name] active."))
else
to_chat(R, SPAN_WARNING("You don't have a module active currently."))
return
R.pick_module()
if("Return-to-core")
if (istype(usr, /mob/living/silicon/robot/shell))
usr.body_return()
return
if("health")
if(isrobot(usr))
if(modifiers["shift"])
var/mob/living/silicon/robot/R = usr
R.self_diagnosis_verb()
if("inventory")
if(isrobot(usr))
var/mob/living/silicon/robot/R = usr
if(R.module)
R.hud_used.toggle_show_robot_modules()
return 1
else
to_chat(R, "You haven't selected a module yet.")
if("radio")
if(isrobot(usr))
var/mob/living/silicon/robot/R = usr
if(modifiers["shift"])
if(!R.radio.radio_desc)
R.radio.setupRadioDescription()
to_chat(R, SPAN_NOTICE("You analyze your integrated radio:"))
to_chat(R, R.radio.radio_desc)
return
R.radio_menu()
if("panel")
if(isrobot(usr))
var/mob/living/silicon/robot/R = usr
R.installed_modules()
if("store")
if(isrobot(usr))
var/mob/living/silicon/robot/R = usr
if(!R.module)
to_chat(R, SPAN_WARNING("You haven't selected a module yet."))
return
if(modifiers["alt"])
R.uneq_all()
return
R.uneq_active()
else
return 0
return 1
/atom/movable/screen/inventory/Click()
// At this point in client Click() code we have passed the 1/10 sec check and little else
// We don't even know if it's a middle click
if(!usr.canClick())
return TRUE
if(use_check_and_message(usr, USE_ALLOW_NON_ADJACENT|USE_ALLOW_NON_ADV_TOOL_USR)) //You're always adjacent to your inventory in practice.
return TRUE
switch(name)
if("right hand")
if(iscarbon(usr))
var/mob/living/carbon/C = usr
C.activate_hand("r")
if("left hand")
if(iscarbon(usr))
var/mob/living/carbon/C = usr
C.activate_hand("l")
if("swap")
usr.swap_hand()
if("hand")
usr.swap_hand()
else
if(usr.attack_ui(slot_id))
usr.update_inv_l_hand(0)
usr.update_inv_r_hand(0)
return 1
/atom/movable/screen/movement_intent
name = "mov_intent"
screen_loc = ui_movi
/// This updates the run/walk button on the hud.
/atom/movable/screen/movement_intent/proc/update_move_icon(var/mob/living/user)
if(!user.client)
return
if(user.max_stamina == -1 || user.stamina == user.max_stamina)
if(user.stamina_bar)
user.stamina_bar.end_progress()
QDEL_NULL(user.stamina_bar) //Because otherwise they stack weirdly when calculating the progress bar offsets
else
if(!user.stamina_bar)
user.stamina_bar = new(user, user.max_stamina, src)
user.stamina_bar.goal = user.max_stamina
user.stamina_bar.update(user.stamina)
if(user.m_intent == M_RUN)
icon_state = "running"
else if(user.m_intent == M_LAY)
icon_state = "lying"
else
icon_state = "walking"
#define BLACKLIST_SPECIES_RUNNING list(SPECIES_DIONA, SPECIES_DIONA_COEUS)
/atom/movable/screen/movement_intent/Click(location, control, params)
if(!usr)
return 1
var/list/modifiers = params2list(params)
if(iscarbon(usr))
var/mob/living/carbon/C = usr
if (modifiers["alt"])
C.set_walk_speed()
return
if(C.legcuffed)
to_chat(C, SPAN_NOTICE("You are legcuffed! You cannot run until you get [C.legcuffed] removed!"))
C.m_intent = M_WALK //Just incase
C.hud_used.move_intent.icon_state = "walking"
return 1
switch(usr.m_intent)
if(M_RUN)
usr.m_intent = M_WALK
if(M_WALK)
if(!(usr.get_species() in BLACKLIST_SPECIES_RUNNING))
usr.m_intent = M_RUN
if(M_LAY)
// No funny "haha i get the bonuses then stand up"
var/obj/item/gun/gun_in_hand = C.get_type_in_hands(/obj/item/gun)
if(gun_in_hand?.wielded)
to_chat(C, SPAN_WARNING("You cannot wield and stand up!"))
return
if(C.lying_is_intentional)
usr.m_intent = M_WALK
if(modifiers["button"] == "middle" && !C.lying) // See /mob/proc/update_canmove() for more logic on the lying FSM
// You want this bonus weapon or not? Wield it when you are lying, not before!
var/obj/item/gun/gun_in_hand = C.get_type_in_hands(/obj/item/gun)
if(gun_in_hand?.wielded)
to_chat(C, SPAN_WARNING("You cannot wield and lie down!"))
return
C.m_intent = M_LAY
// this works in conjunction with M_LAY to make the mob stand up or lie down instantly
C.update_canmove()
C.update_icon()
else if(istype(usr, /mob/living/simple_animal/hostile/morph))
var/mob/living/simple_animal/hostile/morph/M = usr
switch(usr.m_intent)
if(M_RUN)
usr.m_intent = M_WALK
if(M_WALK)
usr.m_intent = M_RUN
M.update_speed()
update_move_icon(usr)
#undef BLACKLIST_SPECIES_RUNNING
/// Hand slots are special to handle the handcuffs overlay
/atom/movable/screen/inventory/hand
var/image/handcuff_overlay
var/image/disabled_hand_overlay
var/image/removed_hand_overlay
/atom/movable/screen/inventory/hand/update_icon()
..()
if(!hud)
return
if(!handcuff_overlay)
var/state = (hud.l_hand_hud_object == src) ? "l_hand_hud_handcuffs" : "r_hand_hud_handcuffs"
handcuff_overlay = image("icon"='icons/mob/screen_gen.dmi', "icon_state" = state)
if(!disabled_hand_overlay)
var/state = (hud.l_hand_hud_object == src) ? "l_hand_disabled" : "r_hand_disabled"
disabled_hand_overlay = image("icon" = 'icons/mob/screen_gen.dmi', "icon_state" = state)
if(!removed_hand_overlay)
var/state = (hud.l_hand_hud_object == src) ? "l_hand_removed" : "r_hand_removed"
removed_hand_overlay = image("icon" = 'icons/mob/screen_gen.dmi', "icon_state" = state)
ClearOverlays()
if(hud.mymob && ishuman(hud.mymob))
var/mob/living/carbon/human/H = hud.mymob
var/obj/item/organ/external/O
if(hud.l_hand_hud_object == src)
O = H.organs_by_name[BP_L_HAND]
else
O = H.organs_by_name[BP_R_HAND]
if(!O || O.is_stump())
AddOverlays(removed_hand_overlay)
else if(O && (!O.is_usable() || O.is_malfunctioning()))
AddOverlays(disabled_hand_overlay)
if(H.handcuffed)
AddOverlays(handcuff_overlay)
/atom/movable/screen/inventory/back
name = "back"