/** * Recursive move listener * Can be added willy-nilly to anything where the COMSIG_OBSERVER_MOVE signal should also trigger on a parent moving. * Previously there was a system where COMSIG_OBSERVER_MOVE was always recursively propogated, but that was unnecessary bloat. */ /datum/component/recursive_move dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS //This makes it so pretty much nothing happens when a duplicate component is created since we only use it to regenerate our parent list var/atom/movable/holder var/list/parents = list() var/noparents = FALSE /datum/component/recursive_move/RegisterWithParent() . = ..() holder = parent RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(on_holder_qdel)) spawn(0) // Delayed action if our holder is spawned in nullspace and then loc = target, hopefully this catches it. VV Add item does this, for example. if(!QDELETED(src)) setup_parents() /datum/component/recursive_move/InheritComponent(datum/component/recursive_move/C, i_am_original) if(!i_am_original) return reset_parents() setup_parents() /datum/component/recursive_move/proc/setup_parents() SIGNAL_HANDLER if(length(parents)) // safety check just incase this was called without clearing reset_parents() var/atom/movable/cur_parent = holder?.loc // first loc could be null var/recursion = 0 // safety check - max iterations while(istype(cur_parent) && (recursion < 64)) if(cur_parent == cur_parent.loc) //safety check incase a thing is somehow inside itself, cancel log_runtime("RECURSIVE_MOVE: Parent is inside itself. ([holder]) ([holder.type])") reset_parents() break if(cur_parent in parents) //safety check incase of circular contents. (A inside B, B inside C, C inside A), cancel log_runtime("RECURSIVE_MOVE: Parent is inside a circular inventory. ([holder]) ([holder.type])") reset_parents() break recursion++ parents += cur_parent RegisterSignal(cur_parent, COMSIG_ATOM_EXITED, PROC_REF(heirarchy_changed)) RegisterSignal(cur_parent, COMSIG_QDELETING, PROC_REF(on_qdel)) // Because the turf is not considered to be in the heirarchy by the component, picking // up a bag with an recursive item inside it will not rebuild the heirarchy when it // enters the mob unless we fire this. Can't use pickup signal as it happens too early... RegisterSignal(cur_parent, COMSIG_ITEM_EQUIPPED, PROC_REF(heirarchy_changed)) cur_parent = cur_parent.loc if(recursion >= 64) // If we escaped due to iteration limit, cancel log_runtime("RECURSIVE_MOVE: Parent hit recursion limit. ([holder]) ([holder.type])") reset_parents() parents.Cut() if(length(parents)) //Only need to watch top parent for movement. Everything is covered by Exited RegisterSignal(parents[parents.len], COMSIG_ATOM_ENTERING, PROC_REF(top_moved)) //If we have no parents of type atom/movable then we wait to see if that changes, checking every time our holder moves. if(!length(parents) && !noparents) noparents = TRUE RegisterSignal(holder, COMSIG_ATOM_ENTERING, PROC_REF(setup_parents)) if(length(parents) && noparents) noparents = FALSE UnregisterSignal(holder, COMSIG_ATOM_ENTERING) /datum/component/recursive_move/proc/unregister_signals() if(noparents) // safety check noparents = FALSE UnregisterSignal(holder, COMSIG_ATOM_ENTERING) if(!length(parents)) return for(var/atom/movable/cur_parent in parents) UnregisterSignal(cur_parent, COMSIG_QDELETING) UnregisterSignal(cur_parent, COMSIG_ATOM_EXITED) UnregisterSignal(cur_parent, COMSIG_ITEM_EQUIPPED) UnregisterSignal(parents[parents.len], COMSIG_ATOM_ENTERING) //Parent at top of heirarchy moved. /datum/component/recursive_move/proc/top_moved(var/atom/movable/am, var/atom/new_loc, var/atom/old_loc) SIGNAL_HANDLER SEND_SIGNAL(holder, COMSIG_OBSERVER_MOVED, old_loc, new_loc) //One of the parents other than the top parent moved. /datum/component/recursive_move/proc/heirarchy_changed(var/atom/old_loc, var/atom/movable/am, var/atom/new_loc) SIGNAL_HANDLER SEND_SIGNAL(holder, COMSIG_OBSERVER_MOVED, old_loc, new_loc) //Rebuild our list of parents reset_parents() setup_parents() //Some things will move their contents on qdel so we should prepare ourselves to be moved. //If this qdel does destroy our holder, on_holder_qdel will handle preperations for GC /datum/component/recursive_move/proc/on_qdel() SIGNAL_HANDLER reset_parents() noparents = TRUE RegisterSignal(holder, COMSIG_ATOM_ENTERING, PROC_REF(setup_parents)) /datum/component/recursive_move/proc/on_holder_qdel() SIGNAL_HANDLER UnregisterSignal(holder, COMSIG_QDELETING) reset_parents() holder = null qdel(src) /datum/component/recursive_move/Destroy() . = ..() reset_parents() if(holder) UnregisterSignal(holder, COMSIG_QDELETING) holder = null /datum/component/recursive_move/proc/reset_parents() unregister_signals() parents.Cut() //the banana peel of testing stays /obj/item/bananapeel/test name = "banana peel of testing" desc = "spams world log with debugging information" /obj/item/bananapeel/test/proc/shmove(var/atom/source, var/atom/old_loc, var/atom/new_loc) SIGNAL_HANDLER world.log << "the [source] moved from [old_loc]([old_loc.x],[old_loc.y],[old_loc.z]) to [new_loc]([new_loc.x],[new_loc.y],[new_loc.z])" /obj/item/bananapeel/test/Initialize(mapload) . = ..() AddComponent(/datum/component/recursive_move) RegisterSignal(src, COMSIG_OBSERVER_MOVED, PROC_REF(shmove))