diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index e176abc86c..ae817fc0a4 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -530,6 +530,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) diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm index 0688d3c390..eff79bc5be 100644 --- a/code/_helpers/_lists.dm +++ b/code/_helpers/_lists.dm @@ -995,6 +995,20 @@ GLOBAL_LIST_EMPTY(json_cache) UNTYPED_LIST_ADD(keys, key) return keys +///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) @@ -1063,18 +1077,4 @@ GLOBAL_LIST_EMPTY(json_cache) return_list += bit return return_list - -///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 //CHOMPAdd end diff --git a/code/datums/components/connect_range_ch.dm b/code/datums/components/connect_range.dm similarity index 80% rename from code/datums/components/connect_range_ch.dm rename to code/datums/components/connect_range.dm index 95cb560c86..e001261ab1 100644 --- a/code/datums/components/connect_range_ch.dm +++ b/code/datums/components/connect_range.dm @@ -1,6 +1,6 @@ /** * 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 @@ -8,6 +8,8 @@ /// 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). @@ -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) @@ -41,7 +43,7 @@ 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. @@ -49,7 +51,7 @@ /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_QDELETING, @@ -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 @@ -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) diff --git a/code/datums/components/reactive_icon_update.dm b/code/datums/components/reactive_icon_update.dm new file mode 100644 index 0000000000..4e3c26e861 --- /dev/null +++ b/code/datums/components/reactive_icon_update.dm @@ -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 diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index a885c9697b..2ebd8fed32 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -477,6 +477,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) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index f9ec94fb2c..7f1883975f 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -240,6 +240,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. diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index 8b6fe389fd..2f57d1c8b3 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -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 diff --git a/icons/obj/directional_test.dmi b/icons/obj/directional_test.dmi new file mode 100644 index 0000000000..01ffd98cb6 Binary files /dev/null and b/icons/obj/directional_test.dmi differ diff --git a/vorestation.dme b/vorestation.dme index a4d4576b93..3214be9345 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -594,16 +594,17 @@ #include "code\datums\components\connect_containers_ch.dm" #include "code\datums\components\connect_loc_behalf.dm" #include "code\datums\components\connect_mob_behalf.dm" -#include "code\datums\components\connect_range_ch.dm" +#include "code\datums\components\connect_range.dm" #include "code\datums\components\holographic_nature.dm" #include "code\datums\components\infective.dm" #include "code\datums\components\orbiter.dm" #include "code\datums\components\overlay_lighting.dm" +#include "code\datums\components\reactive_icon_update.dm" #include "code\datums\components\recursive_move.dm" #include "code\datums\components\resize_guard.dm" #include "code\datums\components\swarm.dm" -#include "code\datums\components\turfslip.dm" #include "code\datums\components\tethered_item.dm" +#include "code\datums\components\turfslip.dm" #include "code\datums\components\animations\dizzy.dm" #include "code\datums\components\animations\jittery.dm" #include "code\datums\components\antags\antag.dm"