Skip to content
Open
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
4 changes: 4 additions & 0 deletions code/__defines/dcs/signals.dm
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@
#define COMSIG_ITEM_EQUIPPED "item_equip"
///from base of obj/item/dropped(): (mob/user)
#define COMSIG_ITEM_DROPPED "item_drop"
/// A mob has just equipped an item. Called on [/mob] from base of [/obj/item/equipped()]: (/obj/item/equipped_item, slot)
#define COMSIG_MOB_EQUIPPED_ITEM "mob_equipped_item"
/// A mob has just unequipped an item.
#define COMSIG_MOB_UNEQUIPPED_ITEM "mob_unequipped_item"
///from base of obj/item/pickup(): (/mob/taker)
#define COMSIG_ITEM_PICKUP "item_pickup"
///from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone)
Expand Down
51 changes: 51 additions & 0 deletions code/_helpers/_lists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,57 @@
UNTYPED_LIST_ADD(keys, key)
return keys

<<<<<<< HEAD

Check failure on line 984 in code/_helpers/_lists.dm

View workflow job for this annotation

GitHub Actions / DreamChecker

got '<<', expected one of: ';', '//!', '/*!', '/'
=======
///compare two lists, returns TRUE if they are the same
/proc/compare_list(list/l,list/d)
if(!islist(l) || !islist(d))
return FALSE

if(l.len != d.len)
return FALSE

for(var/i in 1 to l.len)
if(l[i] != d[i])
return FALSE

return TRUE

//TG sort_list
///uses sort_list() but uses the var's name specifically. This should probably be using mergeAtom() instead
/proc/sort_names(list/list_to_sort, order=1)
return sortTim(list_to_sort.Copy(), order >= 0 ? GLOBAL_PROC_REF(cmp_name_asc) : GLOBAL_PROC_REF(cmp_name_dsc))

/// Compares 2 lists, returns TRUE if they are the same
/proc/deep_compare_list(list/list_1, list/list_2)
if(list_1 == list_2)
return TRUE

if(!islist(list_1) || !islist(list_2))
return FALSE

if(list_1.len != list_2.len)
return FALSE

for(var/i in 1 to list_1.len)
var/key_1 = list_1[i]
var/key_2 = list_2[i]
if (islist(key_1) && islist(key_2))
if(!deep_compare_list(key_1, key_2))
return FALSE
else if(key_1 != key_2)
return FALSE
if(istext(key_1) || islist(key_1) || ispath(key_1) || isdatum(key_1) || key_1 == world)
var/value_1 = list_1[key_1]
var/value_2 = list_2[key_1]
if (islist(value_1) && islist(value_2))
if(!deep_compare_list(value_1, value_2))
return FALSE
else if(value_1 != value_2)
return FALSE
return TRUE

>>>>>>> fa2aaa5293 ([MIRROR] Facing component (#11763))
//CHOMPAdd start
/proc/pick_weight(list/list_to_pick)
var/total = 0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/**
* 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 seperate listener.
* 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).
Expand All @@ -24,8 +26,8 @@
return COMPONENT_INCOMPATIBLE
src.connections = connections
src.range = range
set_tracked(tracked)
src.works_in_containers = works_in_containers
set_tracked(tracked)

/datum/component/connect_range/Destroy()
set_tracked(null)
Expand All @@ -41,15 +43,15 @@
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)
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)
unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
UnregisterSignal(tracked, list(
COMSIG_MOVABLE_MOVED,
COMSIG_PARENT_QDELETING,
Expand All @@ -66,28 +68,37 @@
SIGNAL_HANDLER
qdel(src)

/datum/component/connect_range/proc/update_signals(atom/target, atom/old_loc, forced = FALSE)
/datum/component/connect_range/proc/update_signals(atom/target, atom/old_loc)
var/turf/current_turf = get_turf(target)
var/on_same_turf = current_turf == get_turf(old_loc) //Only register/unregister turf signals if it's moved to a new turf.
unregister_signals(old_loc, on_same_turf)

if(isnull(current_turf))
unregister_signals(old_loc, turfs)
turfs = list()
return

if(ismovable(target.loc))
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))

if(on_same_turf && !forced)
return
for(var/turf/target_turf in RANGE_TURFS(range, current_turf))
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, on_same_turf = FALSE)
/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
Expand All @@ -96,10 +107,9 @@
for(var/atom/movable/target as anything in (get_nested_locs(location) + location))
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)

if(on_same_turf)
if(!length(remove_from))
return
var/turf/previous_turf = get_turf(location)
for(var/turf/target_turf in RANGE_TURFS(range, previous_turf))
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)
Expand Down
140 changes: 140 additions & 0 deletions code/datums/components/reactive_icon_update.dm
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions code/game/objects/items.dm
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@
else if(slot == slot_l_hand || slot == slot_r_hand)
if(!muffled_by_belly(user))
playsound(src, pickup_sound, 20, preference = /datum/preference/toggle/pickup_sounds)
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot)
SEND_SIGNAL(user, COMSIG_MOB_EQUIPPED_ITEM, src, slot)
var/mob/living/M = loc
if(!istype(M))
return
M.update_held_icons()

/// Gives one of our item actions to a mob, when equipped to a certain slot
/obj/item/proc/give_item_action(datum/action/action, mob/to_who, slot)
Expand Down
2 changes: 2 additions & 0 deletions code/modules/mob/inventory.dm
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ var/list/slot_equipment_priority = list( \
else
I.dropInto(drop_location())
I.dropped(src)
//SEND_SIGNAL(item_dropping, COMSIG_ITEM_POST_UNEQUIP, O, target)
SEND_SIGNAL(src, COMSIG_MOB_UNEQUIPPED_ITEM, O, target)
return TRUE

//Returns the item equipped to the specified slot, if any.
Expand Down
2 changes: 0 additions & 2 deletions code/modules/mob/living/carbon/human/human_attackhand.dm
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,6 @@
else
visible_message(span_danger("[M] has pushed [src]!"))
break_all_grabs(M)
for(var/obj/item/I in holding)
drop_from_inventory(I)
else
visible_message(span_warning("[M] attempted to push [src]!"))
return
Expand Down
Binary file added icons/obj/directional_test.dmi
Binary file not shown.
Loading
Loading