diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index 7e1dc3c3640..1c7b1ba7679 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -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) diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm index e8b0a6584f4..72200d058cf 100644 --- a/code/_helpers/_lists.dm +++ b/code/_helpers/_lists.dm @@ -981,6 +981,57 @@ var/global/list/json_cache = list() UNTYPED_LIST_ADD(keys, key) return keys +<<<<<<< HEAD +======= +///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 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 c87d203a2d6..d2e45923d0b 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_PARENT_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 00000000000..4e3c26e8614 --- /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 ceb297cb408..633f677390c 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -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) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 956022867f0..17b385dd98d 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -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. diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index 2b01cc7204b..08da39ef7c3 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 00000000000..01ffd98cb62 Binary files /dev/null and b/icons/obj/directional_test.dmi differ diff --git a/vorestation.dme b/vorestation.dme index 9eb9c19ab4f..89bcf086211 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -524,14 +524,72 @@ #include "code\datums\components\connect_containers_ch.dm" #include "code\datums\components\connect_loc_behalf.dm" #include "code\datums\components\connect_mob_behalf.dm" +<<<<<<< HEAD #include "code\datums\components\connect_range_ch.dm" #include "code\datums\components\material_container.dm" +======= +#include "code\datums\components\connect_range.dm" +#include "code\datums\components\holographic_nature.dm" +#include "code\datums\components\infective.dm" +>>>>>>> fa2aaa5293 ([MIRROR] Facing component (#11763)) #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" +<<<<<<< HEAD #include "code\datums\components\waddle.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" +#include "code\datums\components\antags\changeling\changeling.dm" +#include "code\datums\components\antags\changeling\helpers\absorbed_dna.dm" +#include "code\datums\components\antags\changeling\helpers\generic_equip_procs.dm" +#include "code\datums\components\antags\changeling\powers\absorb.dm" +#include "code\datums\components\antags\changeling\powers\armblade.dm" +#include "code\datums\components\antags\changeling\powers\armor.dm" +#include "code\datums\components\antags\changeling\powers\augmented_eyesight.dm" +#include "code\datums\components\antags\changeling\powers\bioelectrogenesis.dm" +#include "code\datums\components\antags\changeling\powers\blind_sting.dm" +#include "code\datums\components\antags\changeling\powers\boost_range.dm" +#include "code\datums\components\antags\changeling\powers\cryo_sting.dm" +#include "code\datums\components\antags\changeling\powers\darkvision.dm" +#include "code\datums\components\antags\changeling\powers\deaf_sting.dm" +#include "code\datums\components\antags\changeling\powers\delayed_toxin_sting.dm" +#include "code\datums\components\antags\changeling\powers\digital_camo.dm" +#include "code\datums\components\antags\changeling\powers\electric_lockpick.dm" +#include "code\datums\components\antags\changeling\powers\endoarmor.dm" +#include "code\datums\components\antags\changeling\powers\enfeebling_string.dm" +#include "code\datums\components\antags\changeling\powers\engorged_glands.dm" +#include "code\datums\components\antags\changeling\powers\enrage.dm" +#include "code\datums\components\antags\changeling\powers\epinephrine_overdose.dm" +#include "code\datums\components\antags\changeling\powers\escape_restraints.dm" +#include "code\datums\components\antags\changeling\powers\extract_dna_sting.dm" +#include "code\datums\components\antags\changeling\powers\fabricate_clothing.dm" +#include "code\datums\components\antags\changeling\powers\fake_death.dm" +#include "code\datums\components\antags\changeling\powers\fleshmend.dm" +#include "code\datums\components\antags\changeling\powers\hivemind.dm" +#include "code\datums\components\antags\changeling\powers\lesser_form.dm" +#include "code\datums\components\antags\changeling\powers\lsd_sting.dm" +#include "code\datums\components\antags\changeling\powers\mimic_voice.dm" +#include "code\datums\components\antags\changeling\powers\panacea.dm" +#include "code\datums\components\antags\changeling\powers\para_sting.dm" +#include "code\datums\components\antags\changeling\powers\rapid_regen.dm" +#include "code\datums\components\antags\changeling\powers\recursive_enhancement.dm" +#include "code\datums\components\antags\changeling\powers\respec.dm" +#include "code\datums\components\antags\changeling\powers\revive.dm" +#include "code\datums\components\antags\changeling\powers\self_respiration.dm" +#include "code\datums\components\antags\changeling\powers\shriek.dm" +#include "code\datums\components\antags\changeling\powers\silence_sting.dm" +#include "code\datums\components\antags\changeling\powers\transform.dm" +#include "code\datums\components\antags\changeling\powers\transform_sting.dm" +#include "code\datums\components\antags\changeling\powers\unfat_sting.dm" +#include "code\datums\components\antags\changeling\powers\visible_camouflage.dm" +>>>>>>> fa2aaa5293 ([MIRROR] Facing component (#11763)) #include "code\datums\components\crafting\crafting.dm" #include "code\datums\components\crafting\crafting_external.dm" #include "code\datums\components\crafting\recipes.dm"