Facing component (#18574)

* save this because im afraid

* Directions

* Triggering list

* don't

* Update reactive_icon_update.dm

* Update vorestation.dme

* Update reactive_icon_update.dm

* Update reactive_icon_update.dm

* this can be an obj as well!
This commit is contained in:
Cameron Lennox
2025-10-01 23:14:54 +02:00
committed by GitHub
parent f07ebdf376
commit f71f0bc673
9 changed files with 286 additions and 3 deletions
+117
View File
@@ -0,0 +1,117 @@
/**
* This component behaves similar to connect_loc_behalf but for all turfs in range, hooking into a signal on each of them.
* Just like connect_loc_behalf, It can react to that signal on behalf of a separate listener.
* Good for components, though it carries some overhead. Can't be an element as that may lead to bugs.
*/
/datum/component/connect_range
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// An assoc list of signal -> procpath to register to the loc this object is on.
var/list/connections
/// The turfs currently connected to this component
var/list/turfs = list()
/**
* The atom the component is tracking. The component will delete itself if the tracked is deleted.
* Signals will also be updated whenever it moves (if it's a movable).
*/
var/atom/tracked
/// The component will hook into signals only on turfs not farther from tracked than this.
var/range
/// Whether the component works when the movable isn't directly located on a turf.
var/works_in_containers
/datum/component/connect_range/Initialize(atom/tracked, list/connections, range, works_in_containers = TRUE)
if(!isatom(tracked) || isarea(tracked) || range < 0)
return COMPONENT_INCOMPATIBLE
src.connections = connections
src.range = range
src.works_in_containers = works_in_containers
set_tracked(tracked)
/datum/component/connect_range/Destroy()
set_tracked(null)
return ..()
/datum/component/connect_range/InheritComponent(datum/component/component, original, atom/tracked, list/connections, range, works_in_containers)
// Not equivalent. Checks if they are not the same list via shallow comparison.
if(!compare_list(src.connections, connections))
stack_trace("connect_range component attached to [parent] tried to inherit another connect_range component with different connections")
return
if(src.tracked != tracked)
set_tracked(tracked)
if(src.range == range && src.works_in_containers == works_in_containers)
return
//Unregister the signals with the old settings.
unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
src.range = range
src.works_in_containers = works_in_containers
//Re-register the signals with the new settings.
update_signals(src.tracked)
/datum/component/connect_range/proc/set_tracked(atom/new_tracked)
if(tracked) //Unregister the signals from the old tracked and its surroundings
unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
UnregisterSignal(tracked, list(
COMSIG_MOVABLE_MOVED,
COMSIG_QDELETING,
))
tracked = new_tracked
if(!tracked)
return
//Register signals on the new tracked atom and its surroundings.
RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
RegisterSignal(tracked, COMSIG_QDELETING, PROC_REF(handle_tracked_qdel))
update_signals(tracked)
/datum/component/connect_range/proc/handle_tracked_qdel()
SIGNAL_HANDLER
qdel(src)
/datum/component/connect_range/proc/update_signals(atom/target, atom/old_loc)
var/turf/current_turf = get_turf(target)
if(isnull(current_turf))
unregister_signals(old_loc, turfs)
turfs = list()
return
var/loc_is_movable = ismovable(target.loc)
if(loc_is_movable)
if(!works_in_containers)
unregister_signals(old_loc, turfs)
turfs = list()
return
//Only register/unregister turf signals if it's moved to a new turf.
if(current_turf == get_turf(old_loc))
unregister_signals(old_loc, null)
return
var/list/old_turfs = turfs
turfs = RANGE_TURFS(range, current_turf)
unregister_signals(old_loc, old_turfs - turfs)
if(loc_is_movable)
//Keep track of possible movement of all movables the target is in.
for(var/atom/movable/container as anything in get_nested_locs(target))
RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
for(var/turf/target_turf as anything in turfs - old_turfs)
for(var/signal in connections)
parent.RegisterSignal(target_turf, signal, connections[signal])
/datum/component/connect_range/proc/unregister_signals(atom/location, list/remove_from)
//The location is null or is a container and the component shouldn't have register signals on it
if(isnull(location) || (!works_in_containers && !isturf(location)))
return
if(ismovable(location))
for(var/atom/movable/target as anything in (get_nested_locs(location) + location))
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
if(!length(remove_from))
return
for(var/turf/target_turf as anything in remove_from)
parent.UnregisterSignal(target_turf, connections)
/datum/component/connect_range/proc/on_moved(atom/movable/movable, atom/old_loc)
SIGNAL_HANDLER
update_signals(movable, old_loc)
@@ -0,0 +1,140 @@
///Component that updates the icon_state of an item when something approaches.
///NOTE: This uses the initial icon of the object, meaning it will not work properly with items that change their icon_state for other reasons.
/datum/component/reactive_icon_update
dupe_mode = COMPONENT_DUPE_UNIQUE
///What we want to append to our icon_state when our conditions are filled
var/icon_prefix
///List of which directions we want to be valid. Can be NORTH/SOUTH/EAST/WEST along with NORTHEAST/SOUTHEAST/SOUTHWEST/NORTHWEST
var/list/directions
///Range that we want it to look out for.
var/range
///What type of mobs trigger the icon change.
var/list/triggering_mobs = list(/mob/living)
/datum/component/reactive_icon_update/Initialize(icon_prefix, list/directions, range, triggering_mobs)
if(!isobj(parent) || !isnum(range) || (!directions || !LAZYLEN(directions)) || (triggering_mobs && !LAZYLEN(triggering_mobs)))
return COMPONENT_INCOMPATIBLE
src.icon_prefix = icon_prefix
src.directions = directions
src.range = range
if(triggering_mobs)
src.triggering_mobs = triggering_mobs
var/static/list/connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(update_proximity_icon),
)
AddComponent(/datum/component/connect_range, parent, connections, range)
/datum/component/reactive_icon_update/UnregisterFromParent()
directions.Cut()
triggering_mobs.Cut()
/datum/component/reactive_icon_update/proc/update_proximity_icon(atom/current_loc, atom/movable/AM, atom/old_loc)
SIGNAL_HANDLER
SHOULD_NOT_OVERRIDE(TRUE)
var/obj/our_item = parent
if(!ismob(AM) || !mob_check(AM))
return
var/mob/M = AM
if(M == our_item.loc) //Ignore the mob wearing us
return
///What direction the mob is relative to us.
var/mob_direction_x
var/mob_direction_y
var/actual_direction
///First, we get the X and Y coordinates
var/x
var/y
if(our_item.x && our_item.y)
x = our_item.x
y = our_item.y
if(our_item.x == 0 || our_item.y == 0) //We're inside of something! Clothing is a great example of this.
if(our_item.loc)
x = our_item.loc.x
y = our_item.loc.y
if(x == 0 || y == 0) //We're two layers deep, just give up.
return
if(M.x > x)
mob_direction_x = EAST
else if(M.x < x)
mob_direction_x = WEST
else
mob_direction_x = null //Same X as us
if(M.y > y)
mob_direction_y = NORTH
else if(M.y < y)
mob_direction_y = SOUTH
else
mob_direction_y = null //Same Y as us
///Then we combine them to get our actual direction, not just cardinals.
if(mob_direction_x == EAST && mob_direction_y == SOUTH) //Diagonals first
actual_direction = SOUTHEAST
else if(mob_direction_x == EAST && mob_direction_y == NORTH)
actual_direction = NORTHEAST
else if(mob_direction_x == WEST && mob_direction_y == SOUTH)
actual_direction = SOUTHWEST
else if(mob_direction_x == WEST && mob_direction_y == NORTH)
actual_direction = NORTHWEST
else if(mob_direction_x) //Only horizontal
actual_direction = mob_direction_x
else if(mob_direction_y) //Only vertical
actual_direction = mob_direction_y
else //Exactly ontop of us, so we don't care about direction.
return
///We then make sure the actual_direction is in our directions list.
if(actual_direction && !(actual_direction in directions)) //Not a valid direction. Convert to N/E/S/W
//East and west take priority because those are generally the most visually striking.
if((WEST in directions) && (actual_direction == (NORTHWEST || SOUTHWEST)))
actual_direction = WEST
else if((EAST in directions) && (actual_direction == (NORTHEAST || SOUTHEAST)))
actual_direction = EAST
else if((SOUTH in directions) && (actual_direction == (SOUTHWEST || SOUTHEAST)))
actual_direction = SOUTH
else if((NORTH in directions) && (actual_direction == (NORTHWEST || NORTHEAST)))
actual_direction = NORTH
else
return
var/directional_name
switch(actual_direction)
if(NORTH)
directional_name = "north"
if(EAST)
directional_name = "east"
if(SOUTH)
directional_name = "south"
if(WEST)
directional_name = "west"
if(NORTHEAST)
directional_name = "northeast"
if(SOUTHEAST)
directional_name = "southeast"
if(SOUTHWEST)
directional_name = "southwest"
if(NORTHWEST)
directional_name = "northwest"
//We then update our icon state. For example:
//We have an item that has the icon_state of "cloak" and the prefix of "_direction" and we're facing NORTH
//The icon_state will be changed to cloak_direction_north
our_item.icon_state = initial(our_item.icon_state) + icon_prefix + "_" + directional_name
//Example item for testing directions.
/obj/item/tool/screwdriver/test_driver
icon = 'icons/obj/directional_test.dmi'
/obj/item/tool/screwdriver/test_driver/Initialize(mapload)
..()
icon_state = "screwdriver"
AddComponent(/datum/component/reactive_icon_update, directions = list(NORTH, EAST, SOUTH, WEST, SOUTHWEST, SOUTHEAST, NORTHEAST, NORTHWEST), range = 3)
/datum/component/reactive_icon_update/proc/mob_check(mob/triggering_mob)
SHOULD_NOT_OVERRIDE(TRUE)
if(is_type_in_list(triggering_mob, triggering_mobs))
return TRUE
return FALSE