Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Turns the Track Mob list the AI has access to into a TGUI interface version #2415

Merged
merged 4 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions code/_onclick/hud/ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
if(..())
return
var/mob/living/silicon/ai/AI = usr
var/target_name = input(AI, "Choose who you want to track", "Tracking") as null|anything in AI.trackable_mobs()
AI.ai_camera_track(target_name)
GLOB.tracking_menu.show(AI, AI) //NSV13 - Better Tracking Menu

/atom/movable/screen/ai/camera_light
name = "Toggle Camera Light"
Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/dead/observer/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/area/A = V
if(!(A.area_flags & HIDDEN_AREA))
filtered += A
var/area/thearea = input("Area to jump to", "BOOYEA") as null|anything in filtered
var/area/thearea = tgui_input_list(usr, "Area to jump to", "BOOYEA", filtered) // NSV13 - TGUI input list

if(!thearea)
return
Expand Down
1 change: 1 addition & 0 deletions nsv13.dme
Original file line number Diff line number Diff line change
Expand Up @@ -3894,6 +3894,7 @@
#include "nsv13\code\modules\mob\living\carbon\human\species_types\syndicate_knpc.dm"
#include "nsv13\code\modules\mob\living\silicon\ai\custom_holoform.dm"
#include "nsv13\code\modules\mob\living\silicon\ai\robot_control.dm"
#include "nsv13\code\modules\mob\living\silicon\ai\track.dm"
#include "nsv13\code\modules\mob\living\simple_animal\bot\catmed.dm"
#include "nsv13\code\modules\mob\living\simple_animal\bot\hugbot.dm"
#include "nsv13\code\modules\mob\living\simple_animal\bot\secbot.dm"
Expand Down
83 changes: 83 additions & 0 deletions nsv13/code/modules/mob/living/silicon/ai/track.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
GLOBAL_DATUM_INIT(tracking_menu, /datum/track_menu, new)

/datum/track_menu
/// List of user -> UI source
var/list/ui_sources = list()

var/static/list/mob_allowed_typecache

/datum/track_menu/ui_state(mob/user)
return GLOB.default_state

/datum/track_menu/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Tracking")
ui.open()
ui.set_autoupdate(TRUE)

/datum/track_menu/proc/show(mob/user, source)
ui_sources[WEAKREF(user)] = source
ui_interact(user)

/datum/track_menu/ui_host(mob/user)
return ui_sources[WEAKREF(user)]

/datum/track_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if (..())
return

switch(action)
if("track")
var/ref = params["name"]
var/mob/living/silicon/ai/AI = usr
for(var/target in AI.trackable_mobs())
if(target == ref)
AI.ai_camera_track(target)
return TRUE
if("refresh")
update_static_data(usr, ui)
return TRUE

/datum/track_menu/ui_static_data(mob/user)
var/list/carbon = list()
var/list/simple_mob = list()

var/mob/living/silicon/ai/AI = user
var/list/pois = AI.trackable_mobs()
for(var/name in pois)
var/list/serialized = list()
serialized["name"] = name

var/poi = pois[name]

serialized["ref"] = REF(poi)

var/datum/weakref/target = poi
var/mob/mob_poi = target?.resolve()
if(istype(mob_poi))
if(mob_poi.mind == null)
simple_mob += list(serialized)
else if(istype(mob_poi, /mob/living/carbon/human))
var/mob/living/carbon/human/player = mob_poi
var/nanite_sensors = HAS_TRAIT(player, TRAIT_NANITE_SENSORS)
var/obj/item/clothing/under/uniform = player.w_uniform
if(nanite_sensors || uniform.sensor_mode >= SENSOR_VITALS)
serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1)

var/obj/item/card/id/identification_card = mob_poi.get_idcard()
if(identification_card)
serialized["job"] = identification_card.assignment
serialized["role_icon"] = "hud[ckey(identification_card.GetJobIcon())]"

carbon += list(serialized)

return list(
"carbon" = carbon,
"simple_mob" = simple_mob,
)

/datum/track_menu/ui_assets()
. = ..() || list()
. += get_asset_datum(/datum/asset/simple/orbit)
. += get_asset_datum(/datum/asset/spritesheet/job_icons)
89 changes: 50 additions & 39 deletions tgui/packages/common/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const toKeyedArray = (obj, keyProp = 'key') => {
}))(obj);
};

/**
/** NSV13 - TGUI Core/Style Updates Minor Upstream Port - Start
* Iterates over elements of collection, returning an array of all elements
* iteratee returns truthy for. The predicate is invoked with three
* arguments: (value, index|key, collection).
Expand All @@ -72,22 +72,25 @@ export const toKeyedArray = (obj, keyProp = 'key') => {
*
* @returns {any[]}
*/
export const filter = iterateeFn => collection => {
if (collection === null && collection === undefined) {
return collection;
}
if (Array.isArray(collection)) {
const result = [];
for (let i = 0; i < collection.length; i++) {
const item = collection[i];
if (iterateeFn(item, i, collection)) {
result.push(item);
export const filter
= <T>(iterateeFn: (input: T, index: number, collection: T[]) => boolean) =>
(collection: T[]): T[] => {
if (collection === null || collection === undefined) {
return collection;
}
}
return result;
}
throw new Error(`filter() can't iterate on type ${typeof collection}`);
};
if (Array.isArray(collection)) {
const result: T[] = [];
for (let i = 0; i < collection.length; i++) {
const item = collection[i];
if (iterateeFn(item, i, collection)) {
result.push(item);
}
}
return result;
}
throw new Error(`filter() can't iterate on type ${typeof collection}`);
};
// NSV13 - TGUI Core/Style Updates Minor Upstream Port - Stop

/**
* Creates an array of values by running each element in collection
Expand Down Expand Up @@ -140,36 +143,44 @@ const COMPARATOR = (objA, objB) => {
return 0;
};

/**
/** NSV13 - TGUI Core/Style Updates Minor Upstream Port - Start
* Creates an array of elements, sorted in ascending order by the results
* of running each element in a collection thru each iteratee.
*
* Iteratees are called with one argument (value).
*
* @returns {any[]}
*/
export const sortBy = (...iterateeFns) => array => {
if (!Array.isArray(array)) {
return array;
}
let length = array.length;
// Iterate over the array to collect criteria to sort it by
let mappedArray = [];
for (let i = 0; i < length; i++) {
const value = array[i];
mappedArray.push({
criteria: iterateeFns.map(fn => fn(value)),
value,
});
}
// Sort criteria using the base comparator
mappedArray.sort(COMPARATOR);
// Unwrap values
while (length--) {
mappedArray[length] = mappedArray[length].value;
}
return mappedArray;
};
export const sortBy
= <T>(...iterateeFns: ((input: T) => unknown)[]) =>
(array: T[]): T[] => {
if (!Array.isArray(array)) {
return array;
}
let length = array.length;
// Iterate over the array to collect criteria to sort it by
let mappedArray: {
criteria: unknown[];
value: T;
}[] = [];
for (let i = 0; i < length; i++) {
const value = array[i];
mappedArray.push({
criteria: iterateeFns.map((fn) => fn(value)),
value,
});
}
// Sort criteria using the base comparator
mappedArray.sort(COMPARATOR);

// Unwrap values
const values: T[] = [];
while (length--) {
values[length] = mappedArray[length].value;
}
return values;
};
// NSV13 - TGUI Core/Style Updates Minor Upstream Port - Stop

/**
*
Expand Down
4 changes: 4 additions & 0 deletions tgui/packages/tgui/interfaces/Tracking/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum HEALTH {
Good = 69,
Average = 19,
}
65 changes: 65 additions & 0 deletions tgui/packages/tgui/interfaces/Tracking/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { filter } from '../../../common/collections';
import { flow } from '../../../common/fp';
import { HEALTH } from './constants';
import type { Trackable } from './types';

/** Returns the display color for certain health percentages */
const getHealthColor = (health: number) => {
switch (true) {
case health > HEALTH.Good:
return 'good';
case health > HEALTH.Average:
return 'average';
default:
return 'bad';
}
};

export const getMostRelevant = (
searchQuery: string,
trackables: Trackable[][]
) => {
const mostRelevant: Trackable = flow([
// Filters out anything that doesn't match search
filter<Trackable>((trackable) =>
isJobOrNameMatch(trackable, searchQuery)
),
// Makes a single Trackables list for an easy search
])(trackables.flat())[0];

return mostRelevant;
};

/**
* ### getDisplayColor
* Displays color for buttons based on the health count. Toggleable.
* @param {Trackable} item - The point of interest.
* @param {string} color - OPTIONAL: The color to default to.
*/
export const getDisplayColor = (
item: Trackable,
color?: string
) => {
const { health } = item;
if (typeof health !== 'number') {
return color ? 'good' : 'grey';
}
return getHealthColor(health);
};

/** Checks if a full name or job title matches the search. */
export const isJobOrNameMatch = (
trackable: Trackable,
searchQuery: string
) => {
if (!searchQuery) {
return true;
}
const { name, job } = trackable;

return (
name?.toLowerCase().includes(searchQuery?.toLowerCase())
|| job?.toLowerCase().includes(searchQuery?.toLowerCase())
|| false
);
};
Loading
Loading