Skip to content

Commit

Permalink
[MIRROR] Updates to orbit ui [READY] (#2517)
Browse files Browse the repository at this point in the history
* Updates to orbit ui [READY] (#83186)

## About The Pull Request
Pretty big orbit ui refactor.
Check changelog for full list of features.

<details>
<summary>screenshots</summary>

In game
![Screenshot 2024-05-15
133935](https://github.com/tgstation/tgstation/assets/42397676/60c2fc95-9fc5-4417-8477-d6fdae589100)

Sort by department
![Screenshot 2024-05-15
133951](https://github.com/tgstation/tgstation/assets/42397676/96114884-3c10-4b03-a042-c19b25485bf3)

Did you know ninjas had a hud icon that hasn't worked for four years?

![image](https://github.com/tgstation/tgstation/assets/42397676/74f1414e-df57-4586-8cfd-0a154b560b83)

Criticals

![image](https://github.com/tgstation/tgstation/assets/42397676/b6ed9b94-bab3-4878-9a18-345efad9b92d)

Orbit blade


https://github.com/tgstation/tgstation/assets/42397676/99681548-bfb3-4895-9c95-3b650df71107

</details>

## Why It's Good For The Game
Some QoL for the orbit menu, giving more info on where action is and
isn't.
Removes more of the uselocalstate hook which is deprecated anyways
## Changelog
:cl:
fix: Fixed an issue preventing space ninjas from having a hud icon
add: ORBIT UI CHANGES:
add: AFK players are greyed out.
add: Living NPCs now display health.
add: Icons displayed are now based on hud icons, which includes icons
for player-visible antagonists
add: You can now sort by job department (click health icon)
add: Round ending "critical" items will be listed at the top.
add: Click the settings button to expand for more info
add: Your current orbit target is highlighted.
/:cl:

* Updates to orbit ui [READY]

---------

Co-authored-by: Jeremiah <[email protected]>
Co-authored-by: NovaBot13 <[email protected]>
  • Loading branch information
3 people authored and StealsThePRs committed May 16, 2024
1 parent a0aa7b3 commit c41c9e9
Show file tree
Hide file tree
Showing 17 changed files with 1,112 additions and 405 deletions.
4 changes: 4 additions & 0 deletions code/datums/components/orbiter.dm
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@
orbiter_mob.updating_glide_size = TRUE
orbiter_mob.glide_size = 8

if(isobserver(orbiter))
var/mob/dead/observer/ghostie = orbiter
ghostie.orbiting_ref = null

REMOVE_TRAIT(orbiter, TRAIT_NO_FLOATING_ANIM, ORBITING_TRAIT)

if(!refreshing && !length(orbiter_list) && !QDELING(src))
Expand Down
2 changes: 1 addition & 1 deletion code/modules/antagonists/space_ninja/space_ninja.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "\improper Space Ninja"
antagpanel_category = ANTAG_GROUP_NINJAS
job_rank = ROLE_NINJA
antag_hud_name = "space_ninja"
antag_hud_name = "ninja"
hijack_speed = 1
show_name_in_check_antagonists = TRUE
show_to_ghosts = TRUE
Expand Down
10 changes: 10 additions & 0 deletions code/modules/mob/dead/observer/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
var/datum/spawners_menu/spawners_menu
var/datum/minigames_menu/minigames_menu

/// The POI we're orbiting (orbit menu)
var/orbiting_ref

/mob/dead/observer/Initialize(mapload)
set_invisibility(GLOB.observer_default_invisibility)

Expand Down Expand Up @@ -1091,3 +1094,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(!prefs || (client?.combo_hud_enabled && prefs.toggles & COMBOHUD_LIGHTING))
return ..()
return GLOB.ghost_lighting_options[prefs.read_preference(/datum/preference/choiced/ghost_lighting)]


/// Called when we exit the orbiting state
/mob/dead/observer/proc/on_deorbit(datum/source)
SIGNAL_HANDLER

orbiting_ref = null
209 changes: 160 additions & 49 deletions code/modules/mob/dead/observer/orbit.dm
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,33 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
var/mob/dead/observer/user = usr
user.ManualFollow(poi)
user.reset_perspective(null)
user.orbiting_ref = ref
if (auto_observe)
user.do_observe(poi)
return TRUE
if ("refresh")
update_static_data(usr, ui)
ui.send_full_update()
return TRUE

return FALSE


/datum/orbit_menu/ui_data(mob/user)
var/list/data = list()

if(isobserver(user))
data["orbiting"] = get_currently_orbiting(user)

return data


/datum/orbit_menu/ui_static_data(mob/user)
var/list/new_mob_pois = SSpoints_of_interest.get_mob_pois(CALLBACK(src, PROC_REF(validate_mob_poi)), append_dead_role = FALSE)
var/list/new_other_pois = SSpoints_of_interest.get_other_pois()

var/list/alive = list()
var/list/antagonists = list()
var/list/critical = list()
var/list/deadchat_controlled = list()
var/list/dead = list()
var/list/ghosts = list()
Expand All @@ -57,14 +71,10 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)

for(var/name in new_mob_pois)
var/list/serialized = list()

var/mob/mob_poi = new_mob_pois[name]

var/poi_ref = REF(mob_poi)

var/number_of_orbiters = length(mob_poi.get_all_orbiters())

serialized["ref"] = poi_ref
serialized["ref"] = REF(mob_poi)
serialized["full_name"] = name
if(number_of_orbiters)
serialized["orbiters"] = number_of_orbiters
Expand All @@ -81,33 +91,26 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
continue

if(isnull(mob_poi.mind))
if(isliving(mob_poi))
var/mob/living/npc = mob_poi
serialized["health"] = FLOOR((npc.health / npc.maxHealth * 100), 1)

npcs += list(serialized)
continue

var/datum/mind/mind = mob_poi.mind
var/was_antagonist = FALSE

serialized["client"] = !!mob_poi.client
serialized["name"] = mob_poi.real_name

if(isliving(mob_poi)) // handles edge cases like blob
var/mob/living/player = mob_poi
serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1)
if(issilicon(player))
serialized["job"] = player.job
else
var/obj/item/card/id/id_card = player.get_idcard(hand_first = FALSE)
serialized["job"] = id_card?.get_trim_assignment()

for(var/datum/antagonist/antag_datum as anything in mind.antag_datums)
if (antag_datum.show_to_ghosts)
was_antagonist = TRUE
serialized["antag"] = antag_datum.name
serialized["antag_group"] = antag_datum.antagpanel_category
antagonists += list(serialized)
break

if(!was_antagonist)
alive += list(serialized)
if(isliving(mob_poi))
serialized += get_living_data(mob_poi)

var/list/antag_data = get_antag_data(mob_poi.mind)
if(length(antag_data))
serialized += antag_data
antagonists += list(serialized)
continue

alive += list(serialized)

for(var/name in new_other_pois)
var/atom/atom_poi = new_other_pois[name]
Expand All @@ -122,43 +125,150 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
))
continue

misc += list(list(
"ref" = REF(atom_poi),
"full_name" = name,
))
var/list/other_data = get_misc_data(atom_poi)
var/misc_data = list(other_data[1])

// Display the supermatter crystal integrity
if(istype(atom_poi, /obj/machinery/power/supermatter_crystal))
var/obj/machinery/power/supermatter_crystal/crystal = atom_poi
misc[length(misc)]["extra"] = "Integrity: [round(crystal.get_integrity_percent())]%"
continue
// Display the nuke timer
if(istype(atom_poi, /obj/machinery/nuclearbomb))
var/obj/machinery/nuclearbomb/bomb = atom_poi
if(bomb.timing)
misc[length(misc)]["extra"] = "Timer: [bomb.countdown?.displayed_text]s"
continue
// Display the holder if its a nuke disk
if(istype(atom_poi, /obj/item/disk/nuclear))
var/obj/item/disk/nuclear/disk = atom_poi
var/mob/holder = disk.pulledby || get(disk, /mob)
misc[length(misc)]["extra"] = "Location: [holder?.real_name || "Unsecured"]"
continue
misc += misc_data

if(other_data[2]) // Critical = TRUE
critical += misc_data

return list(
"alive" = alive,
"antagonists" = antagonists,
"critical" = critical,
"deadchat_controlled" = deadchat_controlled,
"dead" = dead,
"ghosts" = ghosts,
"misc" = misc,
"npcs" = npcs,
)


/// Shows the UI to the specified user.
/datum/orbit_menu/proc/show(mob/user)
ui_interact(user)


/// Helper function to get threat type, group, overrides for job and icon
/datum/orbit_menu/proc/get_antag_data(datum/mind/poi_mind) as /list
var/list/serialized = list()

for(var/datum/antagonist/antag as anything in poi_mind.antag_datums)
if(!antag.show_to_ghosts)
continue

serialized["antag"] = antag.name
serialized["antag_group"] = antag.antagpanel_category
serialized["job"] = antag.name
serialized["icon"] = antag.antag_hud_name

return serialized


/// Helper to get the current thing we're orbiting (if any)
/datum/orbit_menu/proc/get_currently_orbiting(mob/dead/observer/user)
if(isnull(user.orbiting_ref))
return

var/atom/poi = SSpoints_of_interest.get_poi_atom_by_ref(user.orbiting_ref)
if(isnull(poi))
user.orbiting_ref = null
return

if((ismob(poi) && !SSpoints_of_interest.is_valid_poi(poi, CALLBACK(src, PROC_REF(validate_mob_poi)))) \
|| !SSpoints_of_interest.is_valid_poi(poi)
)
user.orbiting_ref = null
return

var/list/serialized = list()

if(!ismob(poi))
var/list/misc_info = get_misc_data(poi)
serialized += misc_info[1]
return serialized

var/mob/mob_poi = poi
serialized["full_name"] = mob_poi.name
serialized["ref"] = REF(poi)

if(mob_poi.mind)
serialized["client"] = !!mob_poi.client
serialized["name"] = mob_poi.real_name

if(isliving(mob_poi))
serialized += get_living_data(mob_poi)

return serialized


/// Helper function to get job / icon / health data for a living mob
/datum/orbit_menu/proc/get_living_data(mob/living/player) as /list
var/list/serialized = list()

serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1)
if(issilicon(player))
serialized["job"] = player.job
serialized["icon"] = "borg"
else
var/obj/item/card/id/id_card = player.get_idcard(hand_first = FALSE)
serialized["job"] = id_card?.get_trim_assignment()
serialized["icon"] = id_card?.get_trim_sechud_icon_state()

return serialized


/// Gets a list: Misc data and whether it's critical. Handles all snowflakey type cases
/datum/orbit_menu/proc/get_misc_data(atom/movable/atom_poi) as /list
var/list/misc = list()
var/critical = FALSE

misc["ref"] = REF(atom_poi)
misc["full_name"] = atom_poi.name

// Display the supermatter crystal integrity
if(istype(atom_poi, /obj/machinery/power/supermatter_crystal))
var/obj/machinery/power/supermatter_crystal/crystal = atom_poi
var/integrity = round(crystal.get_integrity_percent())
misc["extra"] = "Integrity: [integrity]%"

if(integrity < 10)
critical = TRUE

return list(misc, critical)

// Display the nuke timer
if(istype(atom_poi, /obj/machinery/nuclearbomb))
var/obj/machinery/nuclearbomb/bomb = atom_poi

if(bomb.timing)
misc["extra"] = "Timer: [bomb.countdown?.displayed_text]s"
critical = TRUE

return list(misc, critical)

// Display the holder if its a nuke disk
if(istype(atom_poi, /obj/item/disk/nuclear))
var/obj/item/disk/nuclear/disk = atom_poi
var/mob/holder = disk.pulledby || get(disk, /mob)
misc["extra"] = "Location: [holder?.real_name || "Unsecured"]"

return list(misc, critical)

// Display singuloths if they exist
if(istype(atom_poi, /obj/singularity))
var/obj/singularity/singulo = atom_poi
misc["extra"] = "Energy: [round(singulo.energy)]"

if(singulo.current_size > 2)
critical = TRUE

return list(misc, critical)

return list(misc, critical)


/**
* Helper POI validation function passed as a callback to various SSpoints_of_interest procs.
*
Expand All @@ -181,3 +291,4 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
return FALSE

return potential_poi.validate()

57 changes: 57 additions & 0 deletions tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { DmIcon, Icon } from '../../components';
import { JOB2ICON } from '../common/JobToIcon';
import { Antagonist, Observable } from './types';

type Props = {
item: Observable | Antagonist;
};

type IconSettings = {
dmi: string;
transform: string;
};

const normalIcon: IconSettings = {
dmi: 'icons/mob/huds/hud.dmi',
transform: 'scale(2.3) translateX(8px) translateY(1px)',
};

const antagIcon: IconSettings = {
dmi: 'icons/mob/huds/antag_hud.dmi',
transform: 'scale(1.8) translateX(-16px) translateY(7px)',
};

export function JobIcon(props: Props) {
const { item } = props;

let iconSettings: IconSettings;
if ('antag' in item) {
iconSettings = antagIcon;
} else {
iconSettings = normalIcon;
}

// We don't need to cast here but typescript isn't smart enough to know that
const { icon = '', job = '' } = item;

return (
<div className="JobIcon">
{icon === 'borg' ? (
<Icon color="lightblue" name={JOB2ICON[job]} mr={0.5} />
) : (
<div
style={{
height: '17px',
width: '18px',
}}
>
<DmIcon
icon={iconSettings.dmi}
icon_state={icon}
style={{ transform: iconSettings.transform }}
/>
</div>
)}
</div>
);
}
Loading

0 comments on commit c41c9e9

Please sign in to comment.