@@ -0,0 +1,313 @@
|
||||
/datum/component
|
||||
var/dupe_mode = COMPONENT_DUPE_HIGHLANDER
|
||||
var/dupe_type
|
||||
var/datum/parent
|
||||
//only set to true if you are able to properly transfer this component
|
||||
//At a minimum RegisterWithParent and UnregisterFromParent should be used
|
||||
//Make sure you also implement PostTransfer for any post transfer handling
|
||||
var/can_transfer = FALSE
|
||||
|
||||
/datum/component/New(datum/P, ...)
|
||||
parent = P
|
||||
var/list/arguments = args.Copy(2)
|
||||
if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
|
||||
qdel(src, TRUE, TRUE)
|
||||
CRASH("Incompatible [type] assigned to a [P.type]! args: [json_encode(arguments)]")
|
||||
|
||||
_JoinParent(P)
|
||||
|
||||
/datum/component/proc/_JoinParent()
|
||||
var/datum/P = parent
|
||||
//lazy init the parent's dc list
|
||||
var/list/dc = P.datum_components
|
||||
if(!dc)
|
||||
P.datum_components = dc = list()
|
||||
|
||||
//set up the typecache
|
||||
var/our_type = type
|
||||
for(var/I in _GetInverseTypeList(our_type))
|
||||
var/test = dc[I]
|
||||
if(test) //already another component of this type here
|
||||
var/list/components_of_type
|
||||
if(!length(test))
|
||||
components_of_type = list(test)
|
||||
dc[I] = components_of_type
|
||||
else
|
||||
components_of_type = test
|
||||
if(I == our_type) //exact match, take priority
|
||||
var/inserted = FALSE
|
||||
for(var/J in 1 to components_of_type.len)
|
||||
var/datum/component/C = components_of_type[J]
|
||||
if(C.type != our_type) //but not over other exact matches
|
||||
components_of_type.Insert(J, I)
|
||||
inserted = TRUE
|
||||
break
|
||||
if(!inserted)
|
||||
components_of_type += src
|
||||
else //indirect match, back of the line with ya
|
||||
components_of_type += src
|
||||
else //only component of this type, no list
|
||||
dc[I] = src
|
||||
|
||||
RegisterWithParent()
|
||||
|
||||
// If you want/expect to be moving the component around between parents, use this to register on the parent for signals
|
||||
/datum/component/proc/RegisterWithParent()
|
||||
return
|
||||
|
||||
/datum/component/proc/Initialize(...)
|
||||
return
|
||||
|
||||
/datum/component/Destroy(force=FALSE, silent=FALSE)
|
||||
if(!force && parent)
|
||||
_RemoveFromParent()
|
||||
if(!silent)
|
||||
SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src)
|
||||
parent = null
|
||||
return ..()
|
||||
|
||||
/datum/component/proc/_RemoveFromParent()
|
||||
var/datum/P = parent
|
||||
var/list/dc = P.datum_components
|
||||
for(var/I in _GetInverseTypeList())
|
||||
var/list/components_of_type = dc[I]
|
||||
if(length(components_of_type)) //
|
||||
var/list/subtracted = components_of_type - src
|
||||
if(subtracted.len == 1) //only 1 guy left
|
||||
dc[I] = subtracted[1] //make him special
|
||||
else
|
||||
dc[I] = subtracted
|
||||
else //just us
|
||||
dc -= I
|
||||
if(!dc.len)
|
||||
P.datum_components = null
|
||||
|
||||
UnregisterFromParent()
|
||||
|
||||
/datum/component/proc/UnregisterFromParent()
|
||||
return
|
||||
|
||||
/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE)
|
||||
if(QDELETED(src) || QDELETED(target))
|
||||
return
|
||||
|
||||
var/list/procs = signal_procs
|
||||
if(!procs)
|
||||
signal_procs = procs = list()
|
||||
if(!procs[target])
|
||||
procs[target] = list()
|
||||
var/list/lookup = target.comp_lookup
|
||||
if(!lookup)
|
||||
target.comp_lookup = lookup = list()
|
||||
|
||||
var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types)
|
||||
for(var/sig_type in sig_types)
|
||||
if(!override && procs[target][sig_type])
|
||||
stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning")
|
||||
|
||||
procs[target][sig_type] = proctype
|
||||
|
||||
if(!lookup[sig_type]) // Nothing has registered here yet
|
||||
lookup[sig_type] = src
|
||||
else if(lookup[sig_type] == src) // We already registered here
|
||||
continue
|
||||
else if(!length(lookup[sig_type])) // One other thing registered here
|
||||
lookup[sig_type] = list(lookup[sig_type]=TRUE)
|
||||
lookup[sig_type][src] = TRUE
|
||||
else // Many other things have registered here
|
||||
lookup[sig_type][src] = TRUE
|
||||
|
||||
signal_enabled = TRUE
|
||||
|
||||
/datum/proc/UnregisterSignal(datum/target, sig_type_or_types)
|
||||
var/list/lookup = target.comp_lookup
|
||||
if(!signal_procs || !signal_procs[target] || !lookup)
|
||||
return
|
||||
if(!islist(sig_type_or_types))
|
||||
sig_type_or_types = list(sig_type_or_types)
|
||||
for(var/sig in sig_type_or_types)
|
||||
switch(length(lookup[sig]))
|
||||
if(2)
|
||||
lookup[sig] = (lookup[sig]-src)[1]
|
||||
if(1)
|
||||
stack_trace("[target] ([target.type]) somehow has single length list inside comp_lookup")
|
||||
if(src in lookup[sig])
|
||||
lookup -= sig
|
||||
if(!length(lookup))
|
||||
target.comp_lookup = null
|
||||
break
|
||||
if(0)
|
||||
lookup -= sig
|
||||
if(!length(lookup))
|
||||
target.comp_lookup = null
|
||||
break
|
||||
else
|
||||
lookup[sig] -= src
|
||||
|
||||
signal_procs[target] -= sig_type_or_types
|
||||
if(!signal_procs[target].len)
|
||||
signal_procs -= target
|
||||
|
||||
/datum/component/proc/InheritComponent(datum/component/C, i_am_original)
|
||||
return
|
||||
|
||||
/datum/component/proc/PreTransfer()
|
||||
return
|
||||
|
||||
/datum/component/proc/PostTransfer()
|
||||
return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it
|
||||
|
||||
/datum/component/proc/_GetInverseTypeList(our_type = type)
|
||||
//we can do this one simple trick
|
||||
var/current_type = parent_type
|
||||
. = list(our_type, current_type)
|
||||
//and since most components are root level + 1, this won't even have to run
|
||||
while (current_type != /datum/component)
|
||||
current_type = type2parent(current_type)
|
||||
. += current_type
|
||||
|
||||
/datum/proc/_SendSignal(sigtype, list/arguments)
|
||||
var/target = comp_lookup[sigtype]
|
||||
if(!length(target))
|
||||
var/datum/C = target
|
||||
if(!C.signal_enabled)
|
||||
return NONE
|
||||
var/proctype = C.signal_procs[src][sigtype]
|
||||
return NONE | CallAsync(C, proctype, arguments)
|
||||
. = NONE
|
||||
for(var/I in target)
|
||||
var/datum/C = I
|
||||
if(!C.signal_enabled)
|
||||
continue
|
||||
var/proctype = C.signal_procs[src][sigtype]
|
||||
. |= CallAsync(C, proctype, arguments)
|
||||
|
||||
// The type arg is casted so initial works, you shouldn't be passing a real instance into this
|
||||
/datum/proc/GetComponent(datum/component/c_type)
|
||||
if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED)
|
||||
stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
|
||||
var/list/dc = datum_components
|
||||
if(!dc)
|
||||
return null
|
||||
. = dc[c_type]
|
||||
if(length(.))
|
||||
return .[1]
|
||||
|
||||
/datum/proc/GetExactComponent(c_type)
|
||||
var/list/dc = datum_components
|
||||
if(!dc)
|
||||
return null
|
||||
var/datum/component/C = dc[c_type]
|
||||
if(C)
|
||||
if(length(C))
|
||||
C = C[1]
|
||||
if(C.type == c_type)
|
||||
return C
|
||||
return null
|
||||
|
||||
/datum/proc/GetComponents(c_type)
|
||||
var/list/dc = datum_components
|
||||
if(!dc)
|
||||
return null
|
||||
. = dc[c_type]
|
||||
if(!length(.))
|
||||
return list(.)
|
||||
|
||||
/datum/proc/AddComponent(new_type, ...)
|
||||
var/datum/component/nt = new_type
|
||||
var/dm = initial(nt.dupe_mode)
|
||||
var/dt = initial(nt.dupe_type)
|
||||
|
||||
var/datum/component/old_comp
|
||||
var/datum/component/new_comp
|
||||
|
||||
if(ispath(nt))
|
||||
if(nt == /datum/component)
|
||||
CRASH("[nt] attempted instantiation!")
|
||||
else
|
||||
new_comp = nt
|
||||
nt = new_comp.type
|
||||
|
||||
args[1] = src
|
||||
|
||||
if(dm != COMPONENT_DUPE_ALLOWED)
|
||||
if(!dt)
|
||||
old_comp = GetExactComponent(nt)
|
||||
else
|
||||
old_comp = GetComponent(dt)
|
||||
if(old_comp)
|
||||
switch(dm)
|
||||
if(COMPONENT_DUPE_UNIQUE)
|
||||
if(!new_comp)
|
||||
new_comp = new nt(arglist(args))
|
||||
if(!QDELETED(new_comp))
|
||||
old_comp.InheritComponent(new_comp, TRUE)
|
||||
QDEL_NULL(new_comp)
|
||||
if(COMPONENT_DUPE_HIGHLANDER)
|
||||
if(!new_comp)
|
||||
new_comp = new nt(arglist(args))
|
||||
if(!QDELETED(new_comp))
|
||||
new_comp.InheritComponent(old_comp, FALSE)
|
||||
QDEL_NULL(old_comp)
|
||||
if(COMPONENT_DUPE_UNIQUE_PASSARGS)
|
||||
if(!new_comp)
|
||||
var/list/arguments = args.Copy(2)
|
||||
old_comp.InheritComponent(null, TRUE, arguments)
|
||||
else
|
||||
old_comp.InheritComponent(new_comp, TRUE)
|
||||
else if(!new_comp)
|
||||
new_comp = new nt(arglist(args)) // There's a valid dupe mode but there's no old component, act like normal
|
||||
else if(!new_comp)
|
||||
new_comp = new nt(arglist(args)) // Dupes are allowed, act like normal
|
||||
|
||||
if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy
|
||||
SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp)
|
||||
return new_comp
|
||||
return old_comp
|
||||
|
||||
/datum/proc/LoadComponent(component_type, ...)
|
||||
. = GetComponent(component_type)
|
||||
if(!.)
|
||||
return AddComponent(arglist(args))
|
||||
|
||||
/datum/component/proc/RemoveComponent()
|
||||
if(!parent)
|
||||
return
|
||||
var/datum/old_parent = parent
|
||||
PreTransfer()
|
||||
_RemoveFromParent()
|
||||
parent = null
|
||||
SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src)
|
||||
|
||||
/datum/proc/TakeComponent(datum/component/target)
|
||||
if(!target || target.parent == src)
|
||||
return
|
||||
if(target.parent)
|
||||
target.RemoveComponent()
|
||||
target.parent = src
|
||||
var/result = target.PostTransfer()
|
||||
switch(result)
|
||||
if(COMPONENT_INCOMPATIBLE)
|
||||
var/c_type = target.type
|
||||
qdel(target)
|
||||
CRASH("Incompatible [c_type] transfer attempt to a [type]!")
|
||||
|
||||
if(target == AddComponent(target))
|
||||
target._JoinParent()
|
||||
|
||||
/datum/proc/TransferComponents(datum/target)
|
||||
var/list/dc = datum_components
|
||||
if(!dc)
|
||||
return
|
||||
var/comps = dc[/datum/component]
|
||||
if(islist(comps))
|
||||
for(var/datum/component/I in comps)
|
||||
if(I.can_transfer)
|
||||
target.TakeComponent(I)
|
||||
else
|
||||
var/datum/component/C = comps
|
||||
if(C.can_transfer)
|
||||
target.TakeComponent(comps)
|
||||
|
||||
/datum/component/ui_host()
|
||||
return parent
|
||||
@@ -0,0 +1,48 @@
|
||||
/datum/component/anti_magic
|
||||
var/magic = FALSE
|
||||
var/holy = FALSE
|
||||
var/psychic = FALSE
|
||||
var/allowed_slots = ~ITEM_SLOT_BACKPACK
|
||||
var/charges = INFINITY
|
||||
var/blocks_self = TRUE
|
||||
var/datum/callback/reaction
|
||||
var/datum/callback/expire
|
||||
|
||||
/datum/component/anti_magic/Initialize(_magic = FALSE, _holy = FALSE, _psychic = FALSE, _allowed_slots, _charges, _blocks_self = TRUE, datum/callback/_reaction, datum/callback/_expire)
|
||||
if(isitem(parent))
|
||||
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
|
||||
else if(ismob(parent))
|
||||
RegisterSignal(parent, COMSIG_MOB_RECEIVE_MAGIC, .proc/protect)
|
||||
else
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
magic = _magic
|
||||
holy = _holy
|
||||
psychic = _psychic
|
||||
if(_allowed_slots)
|
||||
allowed_slots = _allowed_slots
|
||||
if(!isnull(_charges))
|
||||
charges = _charges
|
||||
blocks_self = _blocks_self
|
||||
reaction = _reaction
|
||||
expire = _expire
|
||||
|
||||
/datum/component/anti_magic/proc/on_equip(datum/source, mob/equipper, slot)
|
||||
if(!CHECK_BITFIELD(allowed_slots, slotdefine2slotbit(slot))) //Check that the slot is valid for antimagic
|
||||
UnregisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC)
|
||||
return
|
||||
RegisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC, .proc/protect, TRUE)
|
||||
|
||||
/datum/component/anti_magic/proc/on_drop(datum/source, mob/user)
|
||||
UnregisterSignal(user, COMSIG_MOB_RECEIVE_MAGIC)
|
||||
|
||||
/datum/component/anti_magic/proc/protect(datum/source, mob/user, _magic, _holy, _psychic, chargecost = 1, self, list/protection_sources)
|
||||
if(((_magic && magic) || (_holy && holy) || (_psychic && psychic)) && (!self || blocks_self))
|
||||
protection_sources += parent
|
||||
reaction?.Invoke(user, chargecost)
|
||||
charges -= chargecost
|
||||
if(charges <= 0)
|
||||
expire?.Invoke(user)
|
||||
qdel(src)
|
||||
return COMPONENT_BLOCK_MAGIC
|
||||
@@ -0,0 +1,62 @@
|
||||
/datum/component/caltrop
|
||||
var/min_damage
|
||||
var/max_damage
|
||||
var/probability
|
||||
var/flags
|
||||
|
||||
var/cooldown = 0
|
||||
|
||||
/datum/component/caltrop/Initialize(_min_damage = 0, _max_damage = 0, _probability = 100, _flags = NONE)
|
||||
min_damage = _min_damage
|
||||
max_damage = max(_min_damage, _max_damage)
|
||||
probability = _probability
|
||||
flags = _flags
|
||||
|
||||
RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED), .proc/Crossed)
|
||||
|
||||
/datum/component/caltrop/proc/Crossed(datum/source, atom/movable/AM)
|
||||
var/atom/A = parent
|
||||
if(!A.has_gravity())
|
||||
return
|
||||
|
||||
if(!prob(probability))
|
||||
return
|
||||
|
||||
if(ishuman(AM))
|
||||
var/mob/living/carbon/human/H = AM
|
||||
if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE))
|
||||
return
|
||||
|
||||
if((flags & CALTROP_IGNORE_WALKERS) && H.m_intent == MOVE_INTENT_WALK)
|
||||
return
|
||||
|
||||
var/picked_def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
|
||||
var/obj/item/bodypart/O = H.get_bodypart(picked_def_zone)
|
||||
if(!istype(O))
|
||||
return
|
||||
if(O.status == BODYPART_ROBOTIC)
|
||||
return
|
||||
|
||||
var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET) || (H.shoes && (H.shoes.body_parts_covered & FEET)))
|
||||
|
||||
if(!(flags & CALTROP_BYPASS_SHOES) && feetCover)
|
||||
return
|
||||
|
||||
if((H.movement_type & FLYING) || H.buckled)
|
||||
return
|
||||
|
||||
var/damage = rand(min_damage, max_damage)
|
||||
if(HAS_TRAIT(H, TRAIT_LIGHT_STEP))
|
||||
damage *= 0.75
|
||||
H.apply_damage(damage, BRUTE, picked_def_zone)
|
||||
|
||||
if(cooldown < world.time - 10) //cooldown to avoid message spam.
|
||||
if(!H.incapacitated(ignore_restraints = TRUE))
|
||||
H.visible_message("<span class='danger'>[H] steps on [A].</span>", \
|
||||
"<span class='userdanger'>You step on [A]!</span>")
|
||||
else
|
||||
H.visible_message("<span class='danger'>[H] slides on [A]!</span>", \
|
||||
"<span class='userdanger'>You slide on [A]!</span>")
|
||||
|
||||
cooldown = world.time
|
||||
H.Knockdown(60)
|
||||
@@ -0,0 +1,143 @@
|
||||
// Used by /turf/open/chasm and subtypes to implement the "dropping" mechanic
|
||||
/datum/component/chasm
|
||||
var/turf/target_turf
|
||||
var/fall_message = "GAH! Ah... where are you?"
|
||||
var/oblivion_message = "You stumble and stare into the abyss before you. It stares back, and you fall into the enveloping dark."
|
||||
|
||||
var/static/list/falling_atoms = list() // Atoms currently falling into chasms
|
||||
var/static/list/forbidden_types = typecacheof(list(
|
||||
/obj/singularity,
|
||||
/obj/docking_port,
|
||||
/obj/structure/lattice,
|
||||
/obj/structure/stone_tile,
|
||||
/obj/item/projectile,
|
||||
/obj/effect/projectile,
|
||||
/obj/effect/portal,
|
||||
/obj/effect/abstract,
|
||||
/obj/effect/hotspot,
|
||||
/obj/effect/landmark,
|
||||
/obj/effect/temp_visual,
|
||||
/obj/effect/light_emitter/tendril,
|
||||
/obj/effect/collapse,
|
||||
/obj/effect/particle_effect/ion_trails,
|
||||
/obj/effect/dummy/phased_mob
|
||||
))
|
||||
|
||||
/datum/component/chasm/Initialize(turf/target)
|
||||
RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Entered)
|
||||
target_turf = target
|
||||
START_PROCESSING(SSobj, src) // process on create, in case stuff is still there
|
||||
|
||||
/datum/component/chasm/proc/Entered(datum/source, atom/movable/AM)
|
||||
START_PROCESSING(SSobj, src)
|
||||
drop_stuff(AM)
|
||||
|
||||
/datum/component/chasm/process()
|
||||
if (!drop_stuff())
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
|
||||
/datum/component/chasm/proc/is_safe()
|
||||
//if anything matching this typecache is found in the chasm, we don't drop things
|
||||
var/static/list/chasm_safeties_typecache = typecacheof(list(/obj/structure/lattice/catwalk, /obj/structure/stone_tile))
|
||||
|
||||
var/atom/parent = src.parent
|
||||
var/list/found_safeties = typecache_filter_list(parent.contents, chasm_safeties_typecache)
|
||||
for(var/obj/structure/stone_tile/S in found_safeties)
|
||||
if(S.fallen)
|
||||
LAZYREMOVE(found_safeties, S)
|
||||
return LAZYLEN(found_safeties)
|
||||
|
||||
/datum/component/chasm/proc/drop_stuff(AM)
|
||||
. = 0
|
||||
if (is_safe())
|
||||
return FALSE
|
||||
|
||||
var/atom/parent = src.parent
|
||||
var/to_check = AM ? list(AM) : parent.contents
|
||||
for (var/thing in to_check)
|
||||
if (droppable(thing))
|
||||
. = 1
|
||||
INVOKE_ASYNC(src, .proc/drop, thing)
|
||||
|
||||
/datum/component/chasm/proc/droppable(atom/movable/AM)
|
||||
// avoid an infinite loop, but allow falling a large distance
|
||||
if(falling_atoms[AM] && falling_atoms[AM] > 30)
|
||||
return FALSE
|
||||
if(!isliving(AM) && !isobj(AM))
|
||||
return FALSE
|
||||
if(is_type_in_typecache(AM, forbidden_types) || AM.throwing || (AM.movement_type & FLOATING))
|
||||
return FALSE
|
||||
//Flies right over the chasm
|
||||
if(ismob(AM))
|
||||
var/mob/M = AM
|
||||
if(M.buckled) //middle statement to prevent infinite loops just in case!
|
||||
var/mob/buckled_to = M.buckled
|
||||
if((!ismob(M.buckled) || (buckled_to.buckled != M)) && !droppable(M.buckled))
|
||||
return FALSE
|
||||
if(M.is_flying())
|
||||
return FALSE
|
||||
if(ishuman(AM))
|
||||
var/mob/living/carbon/human/H = AM
|
||||
if(istype(H.belt, /obj/item/wormhole_jaunter))
|
||||
var/obj/item/wormhole_jaunter/J = H.belt
|
||||
//To freak out any bystanders
|
||||
H.visible_message("<span class='boldwarning'>[H] falls into [parent]!</span>")
|
||||
J.chasm_react(H)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/component/chasm/proc/drop(atom/movable/AM)
|
||||
//Make sure the item is still there after our sleep
|
||||
if(!AM || QDELETED(AM))
|
||||
return
|
||||
falling_atoms[AM] = (falling_atoms[AM] || 0) + 1
|
||||
var/turf/T = target_turf
|
||||
|
||||
if(T)
|
||||
// send to the turf below
|
||||
AM.visible_message("<span class='boldwarning'>[AM] falls into [parent]!</span>", "<span class='userdanger'>[fall_message]</span>")
|
||||
T.visible_message("<span class='boldwarning'>[AM] falls from above!</span>")
|
||||
AM.forceMove(T)
|
||||
if(isliving(AM))
|
||||
var/mob/living/L = AM
|
||||
L.Knockdown(100)
|
||||
L.adjustBruteLoss(30)
|
||||
falling_atoms -= AM
|
||||
|
||||
else
|
||||
// send to oblivion
|
||||
AM.visible_message("<span class='boldwarning'>[AM] falls into [parent]!</span>", "<span class='userdanger'>[oblivion_message]</span>")
|
||||
if (isliving(AM))
|
||||
var/mob/living/L = AM
|
||||
L.notransform = TRUE
|
||||
L.Stun(200)
|
||||
L.resting = TRUE
|
||||
|
||||
var/oldtransform = AM.transform
|
||||
var/oldcolor = AM.color
|
||||
var/oldalpha = AM.alpha
|
||||
animate(AM, transform = matrix() - matrix(), alpha = 0, color = rgb(0, 0, 0), time = 10)
|
||||
for(var/i in 1 to 5)
|
||||
//Make sure the item is still there after our sleep
|
||||
if(!AM || QDELETED(AM))
|
||||
return
|
||||
AM.pixel_y--
|
||||
sleep(2)
|
||||
|
||||
//Make sure the item is still there after our sleep
|
||||
if(!AM || QDELETED(AM))
|
||||
return
|
||||
|
||||
if(iscyborg(AM))
|
||||
var/mob/living/silicon/robot/S = AM
|
||||
qdel(S.mmi)
|
||||
|
||||
falling_atoms -= AM
|
||||
qdel(AM)
|
||||
if(AM && !QDELETED(AM)) //It's indestructible
|
||||
var/atom/parent = src.parent
|
||||
parent.visible_message("<span class='boldwarning'>[parent] spits out [AM]!</span>")
|
||||
AM.alpha = oldalpha
|
||||
AM.color = oldcolor
|
||||
AM.transform = oldtransform
|
||||
AM.throw_at(get_edge_target_turf(parent,pick(GLOB.alldirs)),rand(1, 10),rand(1, 10))
|
||||
@@ -0,0 +1,46 @@
|
||||
/datum/component/cleaning
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
||||
|
||||
/datum/component/cleaning/Initialize()
|
||||
if(!ismovableatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/Clean)
|
||||
|
||||
/datum/component/cleaning/proc/Clean()
|
||||
var/atom/movable/AM = parent
|
||||
var/turf/T = AM.loc
|
||||
SEND_SIGNAL(T, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
|
||||
for(var/A in T)
|
||||
if(is_cleanable(A))
|
||||
qdel(A)
|
||||
else if(isitem(A))
|
||||
var/obj/item/cleaned_item = A
|
||||
SEND_SIGNAL(cleaned_item, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
|
||||
cleaned_item.clean_blood()
|
||||
if(ismob(cleaned_item.loc))
|
||||
var/mob/M = cleaned_item.loc
|
||||
M.regenerate_icons()
|
||||
else if(ishuman(A))
|
||||
var/mob/living/carbon/human/cleaned_human = A
|
||||
if(cleaned_human.lying)
|
||||
if(cleaned_human.head)
|
||||
SEND_SIGNAL(cleaned_human.head, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
|
||||
cleaned_human.head.clean_blood()
|
||||
cleaned_human.update_inv_head()
|
||||
if(cleaned_human.wear_suit)
|
||||
SEND_SIGNAL(cleaned_human.wear_suit, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
|
||||
cleaned_human.wear_suit.clean_blood()
|
||||
cleaned_human.update_inv_wear_suit()
|
||||
else if(cleaned_human.w_uniform)
|
||||
SEND_SIGNAL(cleaned_human.w_uniform, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
|
||||
cleaned_human.w_uniform.clean_blood()
|
||||
cleaned_human.update_inv_w_uniform()
|
||||
if(cleaned_human.shoes)
|
||||
SEND_SIGNAL(cleaned_human.shoes, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
|
||||
cleaned_human.shoes.clean_blood()
|
||||
cleaned_human.update_inv_shoes()
|
||||
SEND_SIGNAL(cleaned_human, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
|
||||
cleaned_human.clean_blood()
|
||||
cleaned_human.wash_cream()
|
||||
cleaned_human.regenerate_icons()
|
||||
to_chat(cleaned_human, "<span class='danger'>[src] cleans your face!</span>")
|
||||
@@ -0,0 +1,75 @@
|
||||
/datum/component/decal
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
can_transfer = TRUE
|
||||
var/cleanable
|
||||
var/description
|
||||
var/mutable_appearance/pic
|
||||
|
||||
var/first_dir // This only stores the dir arg from init
|
||||
|
||||
/datum/component/decal/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_GOD, _color, _layer=TURF_LAYER, _description, _alpha=255)
|
||||
if(!isatom(parent) || !generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
first_dir = _dir
|
||||
description = _description
|
||||
cleanable = _cleanable
|
||||
|
||||
apply()
|
||||
|
||||
/datum/component/decal/RegisterWithParent()
|
||||
if(first_dir)
|
||||
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_react)
|
||||
if(cleanable)
|
||||
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react)
|
||||
if(description)
|
||||
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
|
||||
|
||||
/datum/component/decal/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE))
|
||||
|
||||
/datum/component/decal/Destroy()
|
||||
remove()
|
||||
return ..()
|
||||
|
||||
/datum/component/decal/PreTransfer()
|
||||
remove()
|
||||
|
||||
/datum/component/decal/PostTransfer()
|
||||
remove()
|
||||
apply()
|
||||
|
||||
/datum/component/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha)
|
||||
if(!_icon || !_icon_state)
|
||||
return FALSE
|
||||
// It has to be made from an image or dir breaks because of a byond bug
|
||||
var/temp_image = image(_icon, null, _icon_state, _layer, _dir)
|
||||
pic = new(temp_image)
|
||||
pic.color = _color
|
||||
pic.alpha = _alpha
|
||||
return TRUE
|
||||
|
||||
/datum/component/decal/proc/apply(atom/thing)
|
||||
var/atom/master = thing || parent
|
||||
master.add_overlay(pic, TRUE)
|
||||
if(isitem(master))
|
||||
addtimer(CALLBACK(master, /obj/item/.proc/update_slot_icon), 0, TIMER_UNIQUE)
|
||||
|
||||
/datum/component/decal/proc/remove(atom/thing)
|
||||
var/atom/master = thing || parent
|
||||
master.cut_overlay(pic, TRUE)
|
||||
if(isitem(master))
|
||||
addtimer(CALLBACK(master, /obj/item/.proc/update_slot_icon), 0, TIMER_UNIQUE)
|
||||
|
||||
/datum/component/decal/proc/rotate_react(datum/source, old_dir, new_dir)
|
||||
if(old_dir == new_dir)
|
||||
return
|
||||
remove()
|
||||
pic.dir = turn(pic.dir, dir2angle(old_dir) - dir2angle(new_dir))
|
||||
apply()
|
||||
|
||||
/datum/component/decal/proc/clean_react(datum/source, strength)
|
||||
if(strength >= cleanable)
|
||||
qdel(src)
|
||||
|
||||
/datum/component/decal/proc/examine(datum/source, mob/user)
|
||||
to_chat(user, description)
|
||||
@@ -0,0 +1,13 @@
|
||||
/datum/component/decal/blood
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE
|
||||
|
||||
/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER)
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name)
|
||||
|
||||
/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override)
|
||||
var/atom/A = parent
|
||||
|
||||
return COMPONENT_EXNAME_CHANGED
|
||||
@@ -0,0 +1,11 @@
|
||||
/datum/component/wearertargeting/earprotection
|
||||
signals = list(COMSIG_CARBON_SOUNDBANG)
|
||||
mobtype = /mob/living/carbon
|
||||
proctype = .proc/reducebang
|
||||
|
||||
/datum/component/wearertargeting/earprotection/Initialize(_valid_slots)
|
||||
. = ..()
|
||||
valid_slots = _valid_slots
|
||||
|
||||
/datum/component/wearertargeting/earprotection/proc/reducebang(datum/source, list/reflist)
|
||||
reflist[1]--
|
||||
@@ -0,0 +1,108 @@
|
||||
/datum/component/footstep
|
||||
var/steps = 0
|
||||
var/volume
|
||||
var/e_range
|
||||
|
||||
/datum/component/footstep/Initialize(volume_ = 0.5, e_range_ = -1)
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
volume = volume_
|
||||
e_range = e_range_
|
||||
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/play_footstep)
|
||||
|
||||
/datum/component/footstep/proc/play_footstep()
|
||||
var/turf/open/T = get_turf(parent)
|
||||
if(!istype(T))
|
||||
return
|
||||
|
||||
var/mob/living/LM = parent
|
||||
var/v = volume
|
||||
var/e = e_range
|
||||
if(!T.footstep || LM.buckled || LM.lying || !LM.canmove || LM.resting || LM.buckled || LM.throwing || LM.movement_type & (VENTCRAWLING | FLYING))
|
||||
if (LM.lying && !LM.buckled && !(!T.footstep || LM.movement_type & (VENTCRAWLING | FLYING))) //play crawling sound if we're lying
|
||||
playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
|
||||
return
|
||||
|
||||
if(HAS_TRAIT(LM, TRAIT_SILENT_STEP))
|
||||
return
|
||||
|
||||
if(iscarbon(LM))
|
||||
var/mob/living/carbon/C = LM
|
||||
if(!C.get_bodypart(BODY_ZONE_L_LEG) && !C.get_bodypart(BODY_ZONE_R_LEG))
|
||||
return
|
||||
if(ishuman(C) && C.m_intent == MOVE_INTENT_WALK)
|
||||
v /= 2
|
||||
e -= 5
|
||||
steps++
|
||||
|
||||
if(steps >= 3)
|
||||
steps = 0
|
||||
|
||||
else
|
||||
return
|
||||
|
||||
if(prob(80) && !LM.has_gravity(T)) // don't need to step as often when you hop around
|
||||
return
|
||||
|
||||
//begin playsound shenanigans//
|
||||
|
||||
//for barefooted non-clawed mobs like monkeys
|
||||
if(isbarefoot(LM))
|
||||
playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
|
||||
GLOB.barefootstep[T.barefootstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.barefootstep[T.barefootstep][3] + e)
|
||||
return
|
||||
|
||||
//for xenomorphs, dogs, and other clawed mobs
|
||||
if(isclawfoot(LM))
|
||||
if(isalienadult(LM)) //xenos are stealthy and get quieter footsteps
|
||||
v /= 3
|
||||
e -= 5
|
||||
|
||||
playsound(T, pick(GLOB.clawfootstep[T.clawfootstep][1]),
|
||||
GLOB.clawfootstep[T.clawfootstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.clawfootstep[T.clawfootstep][3] + e)
|
||||
return
|
||||
|
||||
//for megafauna and other large and imtimidating mobs such as the bloodminer
|
||||
if(isheavyfoot(LM))
|
||||
playsound(T, pick(GLOB.heavyfootstep[T.heavyfootstep][1]),
|
||||
GLOB.heavyfootstep[T.heavyfootstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.heavyfootstep[T.heavyfootstep][3] + e)
|
||||
return
|
||||
|
||||
//for slimes
|
||||
if(isslime(LM))
|
||||
playsound(T, 'sound/effects/footstep/slime1.ogg', 15 * v)
|
||||
return
|
||||
|
||||
//for (simple) humanoid mobs (clowns, russians, pirates, etc.)
|
||||
if(isshoefoot(LM))
|
||||
if(!ishuman(LM))
|
||||
playsound(T, pick(GLOB.footstep[T.footstep][1]),
|
||||
GLOB.footstep[T.footstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.footstep[T.footstep][3] + e)
|
||||
return
|
||||
if(ishuman(LM)) //for proper humans, they're special
|
||||
var/mob/living/carbon/human/H = LM
|
||||
var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET) || (H.shoes && (H.shoes.body_parts_covered & FEET)))
|
||||
|
||||
if (H.dna.features["taur"] == "Naga" || H.dna.features["taur"] == "Tentacle") //are we a naga or tentacle taur creature
|
||||
playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
|
||||
return
|
||||
|
||||
if(feetCover) //are we wearing shoes
|
||||
playsound(T, pick(GLOB.footstep[T.footstep][1]),
|
||||
GLOB.footstep[T.footstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.footstep[T.footstep][3] + e)
|
||||
|
||||
if(!feetCover) //are we NOT wearing shoes
|
||||
playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
|
||||
GLOB.barefootstep[T.barefootstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.barefootstep[T.barefootstep][3] + e)
|
||||
@@ -0,0 +1,88 @@
|
||||
/datum/component/infective
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
var/list/datum/disease/diseases //make sure these are the static, non-processing versions!
|
||||
var/expire_time
|
||||
var/min_clean_strength = CLEAN_WEAK
|
||||
|
||||
/datum/component/infective/Initialize(list/datum/disease/_diseases, expire_in)
|
||||
if(islist(_diseases))
|
||||
diseases = _diseases
|
||||
else
|
||||
diseases = list(_diseases)
|
||||
if(expire_in)
|
||||
expire_time = world.time + expire_in
|
||||
QDEL_IN(src, expire_in)
|
||||
|
||||
if(!ismovableatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/try_infect_buckle)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_BUMP, .proc/try_infect_collide)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, .proc/try_infect_crossed)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_IMPACT_ZONE, .proc/try_infect_impact_zone)
|
||||
if(isitem(parent))
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK_ZONE, .proc/try_infect_attack_zone)
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/try_infect_attack)
|
||||
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/try_infect_equipped)
|
||||
if(istype(parent, /obj/item/reagent_containers/food/snacks))
|
||||
RegisterSignal(parent, COMSIG_FOOD_EATEN, .proc/try_infect_eat)
|
||||
else if(istype(parent, /obj/effect/decal/cleanable/blood/gibs))
|
||||
RegisterSignal(parent, COMSIG_GIBS_STREAK, .proc/try_infect_streak)
|
||||
|
||||
/datum/component/infective/proc/try_infect_eat(datum/source, mob/living/eater, mob/living/feeder)
|
||||
for(var/V in diseases)
|
||||
eater.ForceContractDisease(V)
|
||||
try_infect(feeder, BODY_ZONE_L_ARM)
|
||||
|
||||
/datum/component/infective/proc/clean(datum/source, clean_strength)
|
||||
if(clean_strength >= min_clean_strength)
|
||||
qdel(src)
|
||||
|
||||
/datum/component/infective/proc/try_infect_buckle(datum/source, mob/M, force)
|
||||
if(isliving(M))
|
||||
try_infect(M)
|
||||
|
||||
/datum/component/infective/proc/try_infect_collide(datum/source, atom/A)
|
||||
var/atom/movable/P = parent
|
||||
if(P.throwing)
|
||||
//this will be handled by try_infect_impact_zone()
|
||||
return
|
||||
if(isliving(A))
|
||||
try_infect(A)
|
||||
|
||||
/datum/component/infective/proc/try_infect_impact_zone(datum/source, mob/living/target, hit_zone)
|
||||
try_infect(target, hit_zone)
|
||||
|
||||
/datum/component/infective/proc/try_infect_attack_zone(datum/source, mob/living/carbon/target, mob/living/user, hit_zone)
|
||||
try_infect(user, BODY_ZONE_L_ARM)
|
||||
try_infect(target, hit_zone)
|
||||
|
||||
/datum/component/infective/proc/try_infect_attack(datum/source, mob/living/target, mob/living/user)
|
||||
if(!iscarbon(target)) //this case will be handled by try_infect_attack_zone
|
||||
try_infect(target)
|
||||
try_infect(user, BODY_ZONE_L_ARM)
|
||||
|
||||
/datum/component/infective/proc/try_infect_equipped(datum/source, mob/living/L, slot)
|
||||
var/old_permeability
|
||||
if(isitem(parent))
|
||||
//if you are putting an infective item on, it obviously will not protect you, so set its permeability high enough that it will never block ContactContractDisease()
|
||||
var/obj/item/I = parent
|
||||
old_permeability = I.permeability_coefficient
|
||||
I.permeability_coefficient = 1.01
|
||||
|
||||
try_infect(L, slot2body_zone(slot))
|
||||
|
||||
if(isitem(parent))
|
||||
var/obj/item/I = parent
|
||||
I.permeability_coefficient = old_permeability
|
||||
|
||||
/datum/component/infective/proc/try_infect_crossed(datum/source, atom/movable/M)
|
||||
if(isliving(M))
|
||||
try_infect(M, BODY_ZONE_PRECISE_L_FOOT)
|
||||
|
||||
/datum/component/infective/proc/try_infect_streak(datum/source, list/directions, list/output_diseases)
|
||||
output_diseases |= diseases
|
||||
|
||||
/datum/component/infective/proc/try_infect(mob/living/L, target_zone)
|
||||
for(var/V in diseases)
|
||||
L.ContactContractDisease(V, target_zone)
|
||||
@@ -0,0 +1,74 @@
|
||||
/datum/component/jousting
|
||||
var/current_direction = NONE
|
||||
var/max_tile_charge = 5
|
||||
var/min_tile_charge = 2 //tiles before this code gets into effect.
|
||||
var/current_tile_charge = 0
|
||||
var/movement_reset_tolerance = 2 //deciseconds
|
||||
var/unmounted_damage_boost_per_tile = 0
|
||||
var/unmounted_knockdown_chance_per_tile = 0
|
||||
var/unmounted_knockdown_time = 0
|
||||
var/mounted_damage_boost_per_tile = 2
|
||||
var/mounted_knockdown_chance_per_tile = 20
|
||||
var/mounted_knockdown_time = 20
|
||||
var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to.
|
||||
var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever.
|
||||
var/mob/current_holder
|
||||
var/current_timerid
|
||||
|
||||
/datum/component/jousting/Initialize()
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack)
|
||||
|
||||
/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot)
|
||||
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE)
|
||||
current_holder = user
|
||||
|
||||
/datum/component/jousting/proc/on_drop(datum/source, mob/user)
|
||||
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
|
||||
current_holder = null
|
||||
current_direction = NONE
|
||||
current_tile_charge = 0
|
||||
|
||||
/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user)
|
||||
if(user != current_holder)
|
||||
return
|
||||
var/current = current_tile_charge
|
||||
var/obj/item/I = parent
|
||||
var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are.
|
||||
if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge))
|
||||
return
|
||||
var/turf/target_turf = get_step(user, current_direction)
|
||||
if(target in range(1, target_turf))
|
||||
var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current
|
||||
var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time)
|
||||
var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current
|
||||
var/sharp = I.get_sharpness()
|
||||
var/msg
|
||||
if(damage)
|
||||
msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]"
|
||||
target.apply_damage(damage, BRUTE, user.zone_selected, 0)
|
||||
if(prob(knockdown_chance))
|
||||
msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]"
|
||||
if(target_buckled)
|
||||
target.buckled.unbuckle_mob(target)
|
||||
target.Knockdown(knockdown_time)
|
||||
if(length(msg))
|
||||
user.visible_message("<span class='danger'>[msg]!</span>")
|
||||
|
||||
/datum/component/jousting/proc/mob_move(datum/source, newloc, dir)
|
||||
if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled))))
|
||||
return
|
||||
if(dir != current_direction)
|
||||
current_tile_charge = 0
|
||||
current_direction = dir
|
||||
if(current_tile_charge < max_tile_charge)
|
||||
current_tile_charge++
|
||||
if(current_timerid)
|
||||
deltimer(current_timerid)
|
||||
current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE)
|
||||
|
||||
/datum/component/jousting/proc/reset_charge()
|
||||
current_tile_charge = 0
|
||||
@@ -0,0 +1,239 @@
|
||||
#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7
|
||||
#define LOCKON_IGNORE_RESULT "ignore_my_result"
|
||||
#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT}
|
||||
|
||||
/datum/component/lockon_aiming
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
var/lock_icon = 'icons/mob/cameramob.dmi'
|
||||
var/lock_icon_state = "marker"
|
||||
var/mutable_appearance/lock_appearance
|
||||
var/list/image/lock_images
|
||||
var/list/target_typecache
|
||||
var/list/immune_weakrefs //list(weakref = TRUE)
|
||||
var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious!
|
||||
var/lock_amount = 1
|
||||
var/lock_cursor_range = 5
|
||||
var/list/locked_weakrefs
|
||||
var/update_disabled = FALSE
|
||||
var/current_ranging_id = 0
|
||||
var/list/last_location
|
||||
var/datum/callback/on_lock
|
||||
var/datum/callback/can_target_callback
|
||||
|
||||
/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback)
|
||||
if(!ismob(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
if(target_callback)
|
||||
can_target_callback = target_callback
|
||||
else
|
||||
can_target_callback = CALLBACK(src, .proc/can_target)
|
||||
if(range)
|
||||
lock_cursor_range = range
|
||||
if(typecache)
|
||||
target_typecache = typecache
|
||||
if(amount)
|
||||
lock_amount = amount
|
||||
immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want..
|
||||
if(immune)
|
||||
for(var/i in immune)
|
||||
if(isweakref(i))
|
||||
immune_weakrefs[i] = TRUE
|
||||
else if(isatom(i))
|
||||
immune_weakrefs[WEAKREF(i)] = TRUE
|
||||
if(when_locked)
|
||||
on_lock = when_locked
|
||||
if(icon)
|
||||
lock_icon = icon
|
||||
if(icon_state)
|
||||
lock_icon_state = icon_state
|
||||
generate_lock_visuals()
|
||||
var/mob/M = parent
|
||||
LAZYOR(M.mousemove_intercept_objects, src)
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/datum/component/lockon_aiming/Destroy()
|
||||
var/mob/M = parent
|
||||
clear_visuals()
|
||||
LAZYREMOVE(M.mousemove_intercept_objects, src)
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
return ..()
|
||||
|
||||
/datum/component/lockon_aiming/proc/show_visuals()
|
||||
LAZYINITLIST(lock_images)
|
||||
var/mob/M = parent
|
||||
if(!M.client)
|
||||
return
|
||||
for(var/i in locked_weakrefs)
|
||||
var/datum/weakref/R = i
|
||||
var/atom/A = R.resolve()
|
||||
if(!A)
|
||||
continue //It'll be cleared by processing.
|
||||
var/image/I = new
|
||||
I.appearance = lock_appearance
|
||||
I.loc = A
|
||||
M.client.images |= I
|
||||
lock_images |= I
|
||||
|
||||
/datum/component/lockon_aiming/proc/clear_visuals()
|
||||
var/mob/M = parent
|
||||
if(!M.client)
|
||||
return
|
||||
if(!lock_images)
|
||||
return
|
||||
for(var/i in lock_images)
|
||||
M.client.images -= i
|
||||
qdel(i)
|
||||
lock_images.Cut()
|
||||
|
||||
/datum/component/lockon_aiming/proc/refresh_visuals()
|
||||
clear_visuals()
|
||||
show_visuals()
|
||||
|
||||
/datum/component/lockon_aiming/proc/generate_lock_visuals()
|
||||
lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER)
|
||||
|
||||
/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE)
|
||||
LAZYCLEARLIST(locked_weakrefs)
|
||||
if(refresh_vis)
|
||||
refresh_visuals()
|
||||
|
||||
/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE)
|
||||
if(!A.weak_reference)
|
||||
return
|
||||
LAZYREMOVE(locked_weakrefs, A.weak_reference)
|
||||
if(refresh_vis)
|
||||
refresh_visuals()
|
||||
|
||||
/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE)
|
||||
LAZYOR(locked_weakrefs, WEAKREF(A))
|
||||
if(refresh_vis)
|
||||
refresh_visuals()
|
||||
|
||||
/datum/component/lockon_aiming/proc/add_immune_atom(atom/A)
|
||||
var/datum/weakref/R = WEAKREF(A)
|
||||
if(immune_weakrefs && (immune_weakrefs[R]))
|
||||
return
|
||||
LAZYSET(immune_weakrefs, R, TRUE)
|
||||
|
||||
/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A)
|
||||
if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list?
|
||||
return
|
||||
LAZYREMOVE(immune_weakrefs, A.weak_reference)
|
||||
|
||||
/datum/component/lockon_aiming/onMouseMove(object,location,control,params)
|
||||
var/mob/M = parent
|
||||
if(!istype(M) || !M.client)
|
||||
return
|
||||
var/datum/position/P = mouse_absolute_datum_map_position_from_client(M.client)
|
||||
if(!P)
|
||||
return
|
||||
var/turf/T = P.return_turf()
|
||||
LAZYINITLIST(last_location)
|
||||
if(length(last_location) == 3 && last_location[1] == T.x && last_location[2] == T.y && last_location[3] == T.z)
|
||||
return //Same turf, don't bother.
|
||||
if(last_location)
|
||||
last_location.Cut()
|
||||
else
|
||||
last_location = list()
|
||||
last_location.len = 3
|
||||
last_location[1] = T.x
|
||||
last_location[2] = T.y
|
||||
last_location[3] = T.z
|
||||
autolock()
|
||||
|
||||
/datum/component/lockon_aiming/process()
|
||||
if(update_disabled)
|
||||
return
|
||||
if(!last_location)
|
||||
return
|
||||
var/changed = FALSE
|
||||
for(var/i in locked_weakrefs)
|
||||
var/datum/weakref/R = i
|
||||
if(istype(R))
|
||||
var/atom/thing = R.resolve()
|
||||
if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range))
|
||||
unlock(R)
|
||||
changed = TRUE
|
||||
else
|
||||
unlock(R)
|
||||
changed = TRUE
|
||||
if(changed)
|
||||
autolock()
|
||||
|
||||
/datum/component/lockon_aiming/proc/autolock()
|
||||
var/mob/M = parent
|
||||
if(!M.client)
|
||||
return FALSE
|
||||
var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client)
|
||||
var/turf/target = current.return_turf()
|
||||
var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range)
|
||||
if(targets == LOCKON_IGNORE_RESULT)
|
||||
return
|
||||
unlock_all(FALSE)
|
||||
for(var/i in targets)
|
||||
if(immune_weakrefs[WEAKREF(i)])
|
||||
continue
|
||||
lock(i, FALSE)
|
||||
refresh_visuals()
|
||||
on_lock.Invoke(locked_weakrefs)
|
||||
|
||||
/datum/component/lockon_aiming/proc/can_target(atom/A)
|
||||
var/mob/M = A
|
||||
return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)]
|
||||
|
||||
/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range)
|
||||
current_ranging_id++
|
||||
var/this_id = current_ranging_id
|
||||
var/list/L = list()
|
||||
var/turf/center = get_turf(T)
|
||||
if(amount < 1 || range < 0 || !istype(center) || !islist(typecache))
|
||||
return
|
||||
if(range == 0)
|
||||
return typecache_filter_list(T.contents + T, typecache)
|
||||
var/x = 0
|
||||
var/y = 0
|
||||
var/cd = 0
|
||||
while(cd <= range)
|
||||
x = center.x - cd + 1
|
||||
y = center.y + cd
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
for(x in x to center.x + cd)
|
||||
T = locate(x, y, center.z)
|
||||
if(T)
|
||||
L |= special_list_filter(T.contents, can_target_callback)
|
||||
if(L.len >= amount)
|
||||
L.Cut(amount+1)
|
||||
return L
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
y = center.y + cd - 1
|
||||
x = center.x + cd
|
||||
for(y in center.y - cd to y)
|
||||
T = locate(x, y, center.z)
|
||||
if(T)
|
||||
L |= special_list_filter(T.contents, can_target_callback)
|
||||
if(L.len >= amount)
|
||||
L.Cut(amount+1)
|
||||
return L
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
y = center.y - cd
|
||||
x = center.x + cd - 1
|
||||
for(x in center.x - cd to x)
|
||||
T = locate(x, y, center.z)
|
||||
if(T)
|
||||
L |= special_list_filter(T.contents, can_target_callback)
|
||||
if(L.len >= amount)
|
||||
L.Cut(amount+1)
|
||||
return L
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
y = center.y - cd + 1
|
||||
x = center.x - cd
|
||||
for(y in y to center.y + cd)
|
||||
T = locate(x, y, center.z)
|
||||
if(T)
|
||||
L |= special_list_filter(T.contents, can_target_callback)
|
||||
if(L.len >= amount)
|
||||
L.Cut(amount+1)
|
||||
return L
|
||||
LOCKON_RANGING_BREAK_CHECK
|
||||
cd++
|
||||
CHECK_TICK
|
||||
@@ -0,0 +1,42 @@
|
||||
/datum/component/mirage_border
|
||||
can_transfer = TRUE
|
||||
var/obj/effect/abstract/mirage_holder/holder
|
||||
|
||||
/datum/component/mirage_border/Initialize(turf/target, direction, range=world.view)
|
||||
if(!isturf(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
if(!target || !direction)
|
||||
. = COMPONENT_INCOMPATIBLE
|
||||
CRASH("[type] improperly instanced with the following args: target=\[[target]\], direction=\[[direction]\], range=\[[range]\]")
|
||||
|
||||
holder = new(parent)
|
||||
|
||||
var/x = target.x
|
||||
var/y = target.y
|
||||
var/z = target.z
|
||||
var/turf/southwest = locate(CLAMP(x - (direction & WEST ? range : 0), 1, world.maxx), CLAMP(y - (direction & SOUTH ? range : 0), 1, world.maxy), CLAMP(z, 1, world.maxz))
|
||||
var/turf/northeast = locate(CLAMP(x + (direction & EAST ? range : 0), 1, world.maxx), CLAMP(y + (direction & NORTH ? range : 0), 1, world.maxy), CLAMP(z, 1, world.maxz))
|
||||
//holder.vis_contents += block(southwest, northeast) // This doesnt work because of beta bug memes
|
||||
for(var/i in block(southwest, northeast))
|
||||
holder.vis_contents += i
|
||||
if(direction & SOUTH)
|
||||
holder.pixel_y -= world.icon_size * range
|
||||
if(direction & WEST)
|
||||
holder.pixel_x -= world.icon_size * range
|
||||
|
||||
/datum/component/mirage_border/Destroy()
|
||||
QDEL_NULL(holder)
|
||||
return ..()
|
||||
|
||||
/datum/component/mirage_border/PreTransfer()
|
||||
holder.moveToNullspace()
|
||||
|
||||
/datum/component/mirage_border/PostTransfer()
|
||||
if(!isturf(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
holder.forceMove(parent)
|
||||
|
||||
/obj/effect/abstract/mirage_holder
|
||||
name = "Mirage holder"
|
||||
anchored = TRUE
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
@@ -0,0 +1,283 @@
|
||||
#define MINOR_INSANITY_PEN 5
|
||||
#define MAJOR_INSANITY_PEN 10
|
||||
|
||||
/datum/component/mood
|
||||
var/mood //Real happiness
|
||||
var/sanity = 100 //Current sanity
|
||||
var/shown_mood //Shown happiness, this is what others can see when they try to examine you, prevents antag checking by noticing traitors are always very happy.
|
||||
var/mood_level = 5 //To track what stage of moodies they're on
|
||||
var/sanity_level = 5 //To track what stage of sanity they're on
|
||||
var/mood_modifier = 1 //Modifier to allow certain mobs to be less affected by moodlets
|
||||
var/list/datum/mood_event/mood_events = list()
|
||||
var/insanity_effect = 0 //is the owner being punished for low mood? If so, how much?
|
||||
var/obj/screen/mood/screen_obj
|
||||
|
||||
/datum/component/mood/Initialize()
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
START_PROCESSING(SSmood, src)
|
||||
|
||||
RegisterSignal(parent, COMSIG_ADD_MOOD_EVENT, .proc/add_event)
|
||||
RegisterSignal(parent, COMSIG_CLEAR_MOOD_EVENT, .proc/clear_event)
|
||||
RegisterSignal(parent, COMSIG_MODIFY_SANITY, .proc/modify_sanity)
|
||||
|
||||
RegisterSignal(parent, COMSIG_MOB_HUD_CREATED, .proc/modify_hud)
|
||||
var/mob/living/owner = parent
|
||||
if(owner.hud_used)
|
||||
modify_hud()
|
||||
var/datum/hud/hud = owner.hud_used
|
||||
hud.show_hud(hud.hud_version)
|
||||
|
||||
/datum/component/mood/Destroy()
|
||||
STOP_PROCESSING(SSmood, src)
|
||||
unmodify_hud()
|
||||
return ..()
|
||||
|
||||
/datum/component/mood/proc/print_mood(mob/user)
|
||||
var/msg = "<span class='info'>*---------*\n<EM>Your current mood</EM>\n"
|
||||
msg += "<span class='notice'>My mental status: </span>" //Long term
|
||||
switch(sanity)
|
||||
if(SANITY_GREAT to INFINITY)
|
||||
msg += "<span class='nicegreen'>My mind feels like a temple!<span>\n"
|
||||
if(SANITY_NEUTRAL to SANITY_GREAT)
|
||||
msg += "<span class='nicegreen'>I have been feeling great lately!<span>\n"
|
||||
if(SANITY_DISTURBED to SANITY_NEUTRAL)
|
||||
msg += "<span class='nicegreen'>I have felt quite decent lately.<span>\n"
|
||||
if(SANITY_UNSTABLE to SANITY_DISTURBED)
|
||||
msg += "<span class='warning'>I'm feeling a little bit unhinged...</span>\n"
|
||||
if(SANITY_CRAZY to SANITY_UNSTABLE)
|
||||
msg += "<span class='boldwarning'>I'm freaking out!!</span>\n"
|
||||
if(SANITY_INSANE to SANITY_CRAZY)
|
||||
msg += "<span class='boldwarning'>AHAHAHAHAHAHAHAHAHAH!!</span>\n"
|
||||
|
||||
msg += "<span class='notice'>My current mood: </span>" //Short term
|
||||
switch(mood_level)
|
||||
if(1)
|
||||
msg += "<span class='boldwarning'>I wish I was dead!<span>\n"
|
||||
if(2)
|
||||
msg += "<span class='boldwarning'>I feel terrible...<span>\n"
|
||||
if(3)
|
||||
msg += "<span class='boldwarning'>I feel very upset.<span>\n"
|
||||
if(4)
|
||||
msg += "<span class='boldwarning'>I'm a bit sad.<span>\n"
|
||||
if(5)
|
||||
msg += "<span class='nicegreen'>I'm alright.<span>\n"
|
||||
if(6)
|
||||
msg += "<span class='nicegreen'>I feel pretty okay.<span>\n"
|
||||
if(7)
|
||||
msg += "<span class='nicegreen'>I feel pretty good.<span>\n"
|
||||
if(8)
|
||||
msg += "<span class='nicegreen'>I feel amazing!<span>\n"
|
||||
if(9)
|
||||
msg += "<span class='nicegreen'>I love life!<span>\n"
|
||||
|
||||
msg += "<span class='notice'>Moodlets:\n</span>"//All moodlets
|
||||
if(mood_events.len)
|
||||
for(var/i in mood_events)
|
||||
var/datum/mood_event/event = mood_events[i]
|
||||
msg += event.description
|
||||
else
|
||||
msg += "<span class='nicegreen'>I don't have much of a reaction to anything right now.<span>\n"
|
||||
to_chat(user || parent, msg)
|
||||
|
||||
/datum/component/mood/proc/update_mood() //Called whenever a mood event is added or removed
|
||||
mood = 0
|
||||
shown_mood = 0
|
||||
for(var/i in mood_events)
|
||||
var/datum/mood_event/event = mood_events[i]
|
||||
mood += event.mood_change
|
||||
if(!event.hidden)
|
||||
shown_mood += event.mood_change
|
||||
mood *= mood_modifier
|
||||
shown_mood *= mood_modifier
|
||||
|
||||
switch(mood)
|
||||
if(-INFINITY to MOOD_LEVEL_SAD4)
|
||||
mood_level = 1
|
||||
if(MOOD_LEVEL_SAD4 to MOOD_LEVEL_SAD3)
|
||||
mood_level = 2
|
||||
if(MOOD_LEVEL_SAD3 to MOOD_LEVEL_SAD2)
|
||||
mood_level = 3
|
||||
if(MOOD_LEVEL_SAD2 to MOOD_LEVEL_SAD1)
|
||||
mood_level = 4
|
||||
if(MOOD_LEVEL_SAD1 to MOOD_LEVEL_HAPPY1)
|
||||
mood_level = 5
|
||||
if(MOOD_LEVEL_HAPPY1 to MOOD_LEVEL_HAPPY2)
|
||||
mood_level = 6
|
||||
if(MOOD_LEVEL_HAPPY2 to MOOD_LEVEL_HAPPY3)
|
||||
mood_level = 7
|
||||
if(MOOD_LEVEL_HAPPY3 to MOOD_LEVEL_HAPPY4)
|
||||
mood_level = 8
|
||||
if(MOOD_LEVEL_HAPPY4 to INFINITY)
|
||||
mood_level = 9
|
||||
update_mood_icon()
|
||||
|
||||
|
||||
/datum/component/mood/proc/update_mood_icon()
|
||||
var/mob/living/owner = parent
|
||||
if(owner.client && owner.hud_used)
|
||||
if(sanity < 25)
|
||||
screen_obj.icon_state = "mood_insane"
|
||||
else if (owner.has_status_effect(/datum/status_effect/chem/enthrall))//Fermichem enthral chem, maybe change?
|
||||
screen_obj.icon_state = "mood_entrance"
|
||||
else
|
||||
screen_obj.icon_state = "mood[mood_level]"
|
||||
|
||||
/datum/component/mood/process() //Called on SSmood process
|
||||
if(QDELETED(parent)) // workaround to an obnoxious sneaky periodical runtime.
|
||||
qdel(src)
|
||||
return
|
||||
var/mob/living/owner = parent
|
||||
|
||||
switch(mood_level)
|
||||
if(1)
|
||||
setSanity(sanity-0.2)
|
||||
if(2)
|
||||
setSanity(sanity-0.125, minimum=SANITY_CRAZY)
|
||||
if(3)
|
||||
setSanity(sanity-0.075, minimum=SANITY_UNSTABLE)
|
||||
if(4)
|
||||
setSanity(sanity-0.025, minimum=SANITY_DISTURBED)
|
||||
if(5)
|
||||
setSanity(sanity+0.1)
|
||||
if(6)
|
||||
setSanity(sanity+0.15)
|
||||
if(7)
|
||||
setSanity(sanity+0.20)
|
||||
if(8)
|
||||
setSanity(sanity+0.25, maximum=SANITY_GREAT)
|
||||
if(9)
|
||||
setSanity(sanity+0.4, maximum=SANITY_GREAT)
|
||||
|
||||
if(HAS_TRAIT(owner, TRAIT_DEPRESSION))
|
||||
if(prob(0.05))
|
||||
add_event(null, "depression", /datum/mood_event/depression)
|
||||
clear_event(null, "jolly")
|
||||
if(HAS_TRAIT(owner, TRAIT_JOLLY))
|
||||
if(prob(0.05))
|
||||
add_event(null, "jolly", /datum/mood_event/jolly)
|
||||
clear_event(null, "depression")
|
||||
|
||||
HandleNutrition(owner)
|
||||
|
||||
/datum/component/mood/proc/setSanity(amount, minimum=SANITY_INSANE, maximum=SANITY_NEUTRAL)//I'm sure bunging this in here will have no negative repercussions.
|
||||
var/mob/living/master = parent
|
||||
|
||||
if(amount == sanity)
|
||||
return
|
||||
// If we're out of the acceptable minimum-maximum range move back towards it in steps of 0.5
|
||||
// If the new amount would move towards the acceptable range faster then use it instead
|
||||
if(sanity < minimum && amount < sanity + 0.5)
|
||||
amount = sanity + 0.5
|
||||
else if(sanity > maximum && amount > sanity - 0.5)
|
||||
amount = sanity - 0.5
|
||||
|
||||
// Disturbed stops you from getting any more sane
|
||||
if(HAS_TRAIT(master, TRAIT_UNSTABLE))
|
||||
sanity = min(amount,sanity)
|
||||
else
|
||||
sanity = amount
|
||||
|
||||
switch(sanity)
|
||||
if(-INFINITY to SANITY_CRAZY)
|
||||
setInsanityEffect(MAJOR_INSANITY_PEN)
|
||||
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=1.5) //Did we change something ? movetypes is runtiming, movetypes=(~FLYING))
|
||||
sanity_level = 6
|
||||
if(SANITY_CRAZY to SANITY_UNSTABLE)
|
||||
setInsanityEffect(MINOR_INSANITY_PEN)
|
||||
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=1)//, movetypes=(~FLYING))
|
||||
sanity_level = 5
|
||||
if(SANITY_UNSTABLE to SANITY_DISTURBED)
|
||||
setInsanityEffect(0)
|
||||
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.5)//, movetypes=(~FLYING))
|
||||
sanity_level = 4
|
||||
if(SANITY_DISTURBED to SANITY_NEUTRAL)
|
||||
setInsanityEffect(0)
|
||||
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
|
||||
sanity_level = 3
|
||||
if(SANITY_NEUTRAL+1 to SANITY_GREAT+1) //shitty hack but +1 to prevent it from responding to super small differences
|
||||
setInsanityEffect(0)
|
||||
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
|
||||
sanity_level = 2
|
||||
if(SANITY_GREAT+1 to INFINITY)
|
||||
setInsanityEffect(0)
|
||||
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
|
||||
sanity_level = 1
|
||||
//update_mood_icon()
|
||||
|
||||
/datum/component/mood/proc/setInsanityEffect(newval)//More code so that the previous proc works
|
||||
if(newval == insanity_effect)
|
||||
return
|
||||
//var/mob/living/master = parent
|
||||
//master.crit_threshold = (master.crit_threshold - insanity_effect) + newval
|
||||
insanity_effect = newval
|
||||
|
||||
/datum/component/mood/proc/modify_sanity(datum/source, amount, minimum = -INFINITY, maximum = INFINITY)
|
||||
setSanity(sanity + amount, minimum, maximum)
|
||||
|
||||
/datum/component/mood/proc/add_event(datum/source, category, type, param) //Category will override any events in the same category, should be unique unless the event is based on the same thing like hunger.
|
||||
var/datum/mood_event/the_event
|
||||
if(mood_events[category])
|
||||
the_event = mood_events[category]
|
||||
if(the_event.type != type)
|
||||
clear_event(null, category)
|
||||
else
|
||||
if(the_event.timeout)
|
||||
addtimer(CALLBACK(src, .proc/clear_event, null, category), the_event.timeout, TIMER_UNIQUE|TIMER_OVERRIDE)
|
||||
return 0 //Don't have to update the event.
|
||||
the_event = new type(src, param)//This causes a runtime for some reason, was this me? No - there's an event floating around missing a definition.
|
||||
|
||||
mood_events[category] = the_event
|
||||
update_mood()
|
||||
|
||||
if(the_event.timeout)
|
||||
addtimer(CALLBACK(src, .proc/clear_event, null, category), the_event.timeout, TIMER_UNIQUE|TIMER_OVERRIDE)
|
||||
|
||||
/datum/component/mood/proc/clear_event(datum/source, category)
|
||||
var/datum/mood_event/event = mood_events[category]
|
||||
if(!event)
|
||||
return 0
|
||||
|
||||
mood_events -= category
|
||||
qdel(event)
|
||||
update_mood()
|
||||
|
||||
/datum/component/mood/proc/modify_hud(datum/source)
|
||||
var/mob/living/owner = parent
|
||||
var/datum/hud/hud = owner.hud_used
|
||||
screen_obj = new
|
||||
hud.infodisplay += screen_obj
|
||||
RegisterSignal(hud, COMSIG_PARENT_QDELETED, .proc/unmodify_hud)
|
||||
RegisterSignal(screen_obj, COMSIG_CLICK, .proc/hud_click)
|
||||
|
||||
/datum/component/mood/proc/unmodify_hud(datum/source)
|
||||
if(!screen_obj || !parent)
|
||||
return
|
||||
var/mob/living/owner = parent
|
||||
var/datum/hud/hud = owner.hud_used
|
||||
if(hud && hud.infodisplay)
|
||||
hud.infodisplay -= screen_obj
|
||||
QDEL_NULL(screen_obj)
|
||||
|
||||
/datum/component/mood/proc/hud_click(datum/source, location, control, params, mob/user)
|
||||
print_mood(user)
|
||||
|
||||
|
||||
/datum/component/mood/proc/HandleNutrition(mob/living/L)
|
||||
switch(L.nutrition)
|
||||
if(NUTRITION_LEVEL_FULL to INFINITY)
|
||||
add_event(null, "nutrition", /datum/mood_event/fat)
|
||||
if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL)
|
||||
add_event(null, "nutrition", /datum/mood_event/wellfed)
|
||||
if( NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED)
|
||||
add_event(null, "nutrition", /datum/mood_event/fed)
|
||||
if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED)
|
||||
clear_event(null, "nutrition")
|
||||
if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY)
|
||||
add_event(null, "nutrition", /datum/mood_event/hungry)
|
||||
if(0 to NUTRITION_LEVEL_STARVING)
|
||||
add_event(null, "nutrition", /datum/mood_event/starving)
|
||||
|
||||
#undef MINOR_INSANITY_PEN
|
||||
#undef MAJOR_INSANITY_PEN
|
||||
@@ -0,0 +1,151 @@
|
||||
/datum/component/orbiter
|
||||
can_transfer = TRUE
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
||||
var/list/orbiters
|
||||
|
||||
//radius: range to orbit at, radius of the circle formed by orbiting (in pixels)
|
||||
//clockwise: whether you orbit clockwise or anti clockwise
|
||||
//rotation_speed: how fast to rotate (how many ds should it take for a rotation to complete)
|
||||
//rotation_segments: the resolution of the orbit circle, less = a more block circle, this can be used to produce hexagons (6 segments) triangles (3 segments), and so on, 36 is the best default.
|
||||
//pre_rotation: Chooses to rotate src 90 degress towards the orbit dir (clockwise/anticlockwise), useful for things to go "head first" like ghosts
|
||||
/datum/component/orbiter/Initialize(atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
|
||||
if(!istype(orbiter) || !isatom(parent) || isarea(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
orbiters = list()
|
||||
|
||||
var/atom/master = parent
|
||||
master.orbiters = src
|
||||
|
||||
begin_orbit(orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
|
||||
|
||||
/datum/component/orbiter/RegisterWithParent()
|
||||
var/atom/target = parent
|
||||
while(ismovableatom(target))
|
||||
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react)
|
||||
target = target.loc
|
||||
|
||||
/datum/component/orbiter/UnregisterFromParent()
|
||||
var/atom/target = parent
|
||||
while(ismovableatom(target))
|
||||
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
|
||||
target = target.loc
|
||||
|
||||
/datum/component/orbiter/Destroy()
|
||||
var/atom/master = parent
|
||||
master.orbiters = null
|
||||
for(var/i in orbiters)
|
||||
end_orbit(i)
|
||||
orbiters = null
|
||||
return ..()
|
||||
|
||||
/datum/component/orbiter/InheritComponent(datum/component/orbiter/newcomp, original, list/arguments)
|
||||
if(arguments)
|
||||
begin_orbit(arglist(arguments))
|
||||
return
|
||||
// The following only happens on component transfers
|
||||
orbiters += newcomp.orbiters
|
||||
|
||||
/datum/component/orbiter/PostTransfer()
|
||||
if(!isatom(parent) || isarea(parent) || !get_turf(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
move_react()
|
||||
|
||||
/datum/component/orbiter/proc/begin_orbit(atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
|
||||
if(orbiter.orbiting)
|
||||
if(orbiter.orbiting == src)
|
||||
orbiter.orbiting.end_orbit(orbiter, TRUE)
|
||||
else
|
||||
orbiter.orbiting.end_orbit(orbiter)
|
||||
orbiters[orbiter] = TRUE
|
||||
orbiter.orbiting = src
|
||||
RegisterSignal(orbiter, COMSIG_MOVABLE_MOVED, .proc/orbiter_move_react)
|
||||
var/matrix/initial_transform = matrix(orbiter.transform)
|
||||
|
||||
// Head first!
|
||||
if(pre_rotation)
|
||||
var/matrix/M = matrix(orbiter.transform)
|
||||
var/pre_rot = 90
|
||||
if(!clockwise)
|
||||
pre_rot = -90
|
||||
M.Turn(pre_rot)
|
||||
orbiter.transform = M
|
||||
|
||||
var/matrix/shift = matrix(orbiter.transform)
|
||||
shift.Translate(0, radius)
|
||||
orbiter.transform = shift
|
||||
|
||||
orbiter.SpinAnimation(rotation_speed, -1, clockwise, rotation_segments, parallel = FALSE)
|
||||
|
||||
//we stack the orbits up client side, so we can assign this back to normal server side without it breaking the orbit
|
||||
orbiter.transform = initial_transform
|
||||
orbiter.forceMove(get_turf(parent))
|
||||
to_chat(orbiter, "<span class='notice'>Now orbiting [parent].</span>")
|
||||
|
||||
/datum/component/orbiter/proc/end_orbit(atom/movable/orbiter, refreshing=FALSE)
|
||||
if(!orbiters[orbiter])
|
||||
return
|
||||
UnregisterSignal(orbiter, COMSIG_MOVABLE_MOVED)
|
||||
orbiter.SpinAnimation(0, 0)
|
||||
orbiters -= orbiter
|
||||
orbiter.stop_orbit(src)
|
||||
orbiter.orbiting = null
|
||||
if(!refreshing && !length(orbiters) && !QDELING(src))
|
||||
qdel(src)
|
||||
|
||||
// This proc can receive signals by either the thing being directly orbited or anything holding it
|
||||
/datum/component/orbiter/proc/move_react(atom/orbited, atom/oldloc, direction)
|
||||
set waitfor = FALSE // Transfer calls this directly and it doesnt care if the ghosts arent done moving
|
||||
|
||||
var/atom/movable/master = parent
|
||||
if(master.loc == oldloc)
|
||||
return
|
||||
|
||||
var/turf/newturf = get_turf(master)
|
||||
if(!newturf)
|
||||
qdel(src)
|
||||
|
||||
// Handling the signals of stuff holding us (or not anymore)
|
||||
// These are prety rarely activated, how often are you following something in a bag?
|
||||
if(oldloc && !isturf(oldloc)) // We used to be registered to it, probably
|
||||
var/atom/target = oldloc
|
||||
while(ismovableatom(target))
|
||||
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
|
||||
target = target.loc
|
||||
if(orbited?.loc && orbited.loc != newturf) // We want to know when anything holding us moves too
|
||||
var/atom/target = orbited.loc
|
||||
while(ismovableatom(target))
|
||||
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react, TRUE)
|
||||
target = target.loc
|
||||
|
||||
var/atom/curloc = master.loc
|
||||
for(var/i in orbiters)
|
||||
var/atom/movable/thing = i
|
||||
if(QDELETED(thing) || thing.loc == newturf)
|
||||
continue
|
||||
thing.forceMove(newturf)
|
||||
if(CHECK_TICK && master.loc != curloc)
|
||||
// We moved again during the checktick, cancel current operation
|
||||
break
|
||||
|
||||
|
||||
/datum/component/orbiter/proc/orbiter_move_react(atom/movable/orbiter, atom/oldloc, direction)
|
||||
if(orbiter.loc == get_turf(parent))
|
||||
return
|
||||
end_orbit(orbiter)
|
||||
|
||||
/////////////////////
|
||||
|
||||
/atom/movable/proc/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE)
|
||||
if(!istype(A) || !get_turf(A) || A == src)
|
||||
return
|
||||
|
||||
return A.AddComponent(/datum/component/orbiter, src, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
|
||||
|
||||
/atom/movable/proc/stop_orbit(datum/component/orbiter/orbits)
|
||||
return // We're just a simple hook
|
||||
|
||||
/atom/proc/transfer_observers_to(atom/target)
|
||||
if(!orbiters || !istype(target) || !get_turf(target) || target == src)
|
||||
return
|
||||
target.TakeComponent(orbiters)
|
||||
@@ -0,0 +1,164 @@
|
||||
#define ROTATION_ALTCLICK (1<<0)
|
||||
#define ROTATION_WRENCH (1<<1)
|
||||
#define ROTATION_VERBS (1<<2)
|
||||
#define ROTATION_COUNTERCLOCKWISE (1<<3)
|
||||
#define ROTATION_CLOCKWISE (1<<4)
|
||||
#define ROTATION_FLIP (1<<5)
|
||||
|
||||
/datum/component/simple_rotation
|
||||
var/datum/callback/can_user_rotate //Checks if user can rotate
|
||||
var/datum/callback/can_be_rotated //Check if object can be rotated at all
|
||||
var/datum/callback/after_rotation //Additional stuff to do after rotation
|
||||
|
||||
var/rotation_flags = NONE
|
||||
var/default_rotation_direction = ROTATION_CLOCKWISE
|
||||
|
||||
/datum/component/simple_rotation/Initialize(rotation_flags = NONE ,can_user_rotate,can_be_rotated,after_rotation)
|
||||
if(!ismovableatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
//throw if no rotation direction is specificed ?
|
||||
|
||||
src.rotation_flags = rotation_flags
|
||||
|
||||
if(can_user_rotate)
|
||||
src.can_user_rotate = can_user_rotate
|
||||
else
|
||||
src.can_user_rotate = CALLBACK(src,.proc/default_can_user_rotate)
|
||||
|
||||
if(can_be_rotated)
|
||||
src.can_be_rotated = can_be_rotated
|
||||
else
|
||||
src.can_be_rotated = CALLBACK(src,.proc/default_can_be_rotated)
|
||||
|
||||
if(after_rotation)
|
||||
src.after_rotation = after_rotation
|
||||
else
|
||||
src.after_rotation = CALLBACK(src,.proc/default_after_rotation)
|
||||
|
||||
//Try Clockwise,counter,flip in order
|
||||
if(src.rotation_flags & ROTATION_FLIP)
|
||||
default_rotation_direction = ROTATION_FLIP
|
||||
if(src.rotation_flags & ROTATION_COUNTERCLOCKWISE)
|
||||
default_rotation_direction = ROTATION_COUNTERCLOCKWISE
|
||||
if(src.rotation_flags & ROTATION_CLOCKWISE)
|
||||
default_rotation_direction = ROTATION_CLOCKWISE
|
||||
|
||||
/datum/component/simple_rotation/proc/add_signals()
|
||||
if(rotation_flags & ROTATION_ALTCLICK)
|
||||
RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/HandRot)
|
||||
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/ExamineMessage)
|
||||
if(rotation_flags & ROTATION_WRENCH)
|
||||
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/WrenchRot)
|
||||
|
||||
/datum/component/simple_rotation/proc/add_verbs()
|
||||
if(rotation_flags & ROTATION_VERBS)
|
||||
var/atom/movable/AM = parent
|
||||
if(rotation_flags & ROTATION_FLIP)
|
||||
AM.verbs += /atom/movable/proc/simple_rotate_flip
|
||||
if(rotation_flags & ROTATION_CLOCKWISE)
|
||||
AM.verbs += /atom/movable/proc/simple_rotate_clockwise
|
||||
if(rotation_flags & ROTATION_COUNTERCLOCKWISE)
|
||||
AM.verbs += /atom/movable/proc/simple_rotate_counterclockwise
|
||||
|
||||
/datum/component/simple_rotation/proc/remove_verbs()
|
||||
if(parent)
|
||||
var/atom/movable/AM = parent
|
||||
AM.verbs -= /atom/movable/proc/simple_rotate_flip
|
||||
AM.verbs -= /atom/movable/proc/simple_rotate_clockwise
|
||||
AM.verbs -= /atom/movable/proc/simple_rotate_counterclockwise
|
||||
|
||||
/datum/component/simple_rotation/proc/remove_signals()
|
||||
UnregisterSignal(parent, list(COMSIG_CLICK_ALT, COMSIG_PARENT_EXAMINE, COMSIG_PARENT_ATTACKBY))
|
||||
|
||||
/datum/component/simple_rotation/RegisterWithParent()
|
||||
add_verbs()
|
||||
add_signals()
|
||||
. = ..()
|
||||
|
||||
/datum/component/simple_rotation/PostTransfer()
|
||||
//Because of the callbacks which we don't track cleanly we can't transfer this
|
||||
//item cleanly, better to let the new of the new item create a new rotation datum
|
||||
//instead (there's no real state worth transferring)
|
||||
return COMPONENT_NOTRANSFER
|
||||
|
||||
/datum/component/simple_rotation/UnregisterFromParent()
|
||||
remove_verbs()
|
||||
remove_signals()
|
||||
. = ..()
|
||||
|
||||
/datum/component/simple_rotation/Destroy()
|
||||
QDEL_NULL(can_user_rotate)
|
||||
QDEL_NULL(can_be_rotated)
|
||||
QDEL_NULL(after_rotation)
|
||||
//Signals + verbs removed via UnRegister
|
||||
. = ..()
|
||||
|
||||
/datum/component/simple_rotation/RemoveComponent()
|
||||
remove_verbs()
|
||||
. = ..()
|
||||
|
||||
/datum/component/simple_rotation/proc/ExamineMessage(datum/source, mob/user)
|
||||
if(rotation_flags & ROTATION_ALTCLICK)
|
||||
to_chat(user, "<span class='notice'>Alt-click to rotate it clockwise.</span>")
|
||||
|
||||
/datum/component/simple_rotation/proc/HandRot(datum/source, mob/user, rotation = default_rotation_direction)
|
||||
if(!can_be_rotated.Invoke(user, rotation) || !can_user_rotate.Invoke(user, rotation))
|
||||
return
|
||||
BaseRot(user, rotation)
|
||||
|
||||
/datum/component/simple_rotation/proc/WrenchRot(datum/source, obj/item/I, mob/living/user)
|
||||
if(!can_be_rotated.Invoke(user,default_rotation_direction) || !can_user_rotate.Invoke(user,default_rotation_direction))
|
||||
return
|
||||
if(istype(I,/obj/item/wrench))
|
||||
BaseRot(user,default_rotation_direction)
|
||||
return COMPONENT_NO_AFTERATTACK
|
||||
|
||||
/datum/component/simple_rotation/proc/BaseRot(mob/user,rotation_type)
|
||||
var/atom/movable/AM = parent
|
||||
var/rot_degree
|
||||
switch(rotation_type)
|
||||
if(ROTATION_CLOCKWISE)
|
||||
rot_degree = -90
|
||||
if(ROTATION_COUNTERCLOCKWISE)
|
||||
rot_degree = 90
|
||||
if(ROTATION_FLIP)
|
||||
rot_degree = 180
|
||||
AM.setDir(turn(AM.dir,rot_degree))
|
||||
after_rotation.Invoke(user,rotation_type)
|
||||
|
||||
/datum/component/simple_rotation/proc/default_can_user_rotate(mob/living/user, rotation_type)
|
||||
if(!istype(user) || !user.canUseTopic(parent, BE_CLOSE, NO_DEXTERY))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/component/simple_rotation/proc/default_can_be_rotated(mob/user, rotation_type)
|
||||
var/atom/movable/AM = parent
|
||||
return !AM.anchored
|
||||
|
||||
/datum/component/simple_rotation/proc/default_after_rotation(mob/user, rotation_type)
|
||||
to_chat(user,"<span class='notice'>You [rotation_type == ROTATION_FLIP ? "flip" : "rotate"] [parent].</span>")
|
||||
|
||||
/atom/movable/proc/simple_rotate_clockwise()
|
||||
set name = "Rotate Clockwise"
|
||||
set category = "Object"
|
||||
set src in oview(1)
|
||||
var/datum/component/simple_rotation/rotcomp = GetComponent(/datum/component/simple_rotation)
|
||||
if(rotcomp)
|
||||
rotcomp.HandRot(null,usr,ROTATION_CLOCKWISE)
|
||||
|
||||
/atom/movable/proc/simple_rotate_counterclockwise()
|
||||
set name = "Rotate Counter-Clockwise"
|
||||
set category = "Object"
|
||||
set src in oview(1)
|
||||
var/datum/component/simple_rotation/rotcomp = GetComponent(/datum/component/simple_rotation)
|
||||
if(rotcomp)
|
||||
rotcomp.HandRot(null,usr,ROTATION_COUNTERCLOCKWISE)
|
||||
|
||||
/atom/movable/proc/simple_rotate_flip()
|
||||
set name = "Flip"
|
||||
set category = "Object"
|
||||
set src in oview(1)
|
||||
var/datum/component/simple_rotation/rotcomp = GetComponent(/datum/component/simple_rotation)
|
||||
if(rotcomp)
|
||||
rotcomp.HandRot(null,usr,ROTATION_FLIP)
|
||||
@@ -0,0 +1,89 @@
|
||||
/datum/component/storage/concrete/pockets
|
||||
max_items = 2
|
||||
max_w_class = WEIGHT_CLASS_SMALL
|
||||
max_combined_w_class = 50
|
||||
rustle_sound = FALSE
|
||||
|
||||
/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user)
|
||||
. = ..()
|
||||
if(. && silent && !prevent_warning)
|
||||
if(quickdraw)
|
||||
to_chat(user, "<span class='notice'>You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>You discreetly slip [I] into [parent].</span>")
|
||||
|
||||
/datum/component/storage/concrete/pockets
|
||||
max_w_class = WEIGHT_CLASS_NORMAL
|
||||
|
||||
/datum/component/storage/concrete/pockets/small
|
||||
max_items = 1
|
||||
attack_hand_interact = FALSE
|
||||
|
||||
/datum/component/storage/concrete/pockets/small/collar
|
||||
max_items = 1
|
||||
|
||||
/datum/component/storage/concrete/pockets/small/collar/Initialize()
|
||||
. = ..()
|
||||
can_hold = typecacheof(list(
|
||||
/obj/item/reagent_containers/food/snacks/cookie,
|
||||
/obj/item/reagent_containers/food/snacks/sugarcookie))
|
||||
|
||||
/datum/component/storage/concrete/pockets/small/collar/locked/Initialize()
|
||||
. = ..()
|
||||
can_hold = typecacheof(list(
|
||||
/obj/item/reagent_containers/food/snacks/cookie,
|
||||
/obj/item/reagent_containers/food/snacks/sugarcookie,
|
||||
/obj/item/key/collar))
|
||||
|
||||
/datum/component/storage/concrete/pockets/tiny
|
||||
max_items = 1
|
||||
max_w_class = WEIGHT_CLASS_TINY
|
||||
attack_hand_interact = FALSE
|
||||
|
||||
/datum/component/storage/concrete/pockets/small/detective
|
||||
attack_hand_interact = TRUE // so the detectives would discover pockets in their hats
|
||||
|
||||
/datum/component/storage/concrete/pockets/shoes
|
||||
attack_hand_interact = FALSE
|
||||
quickdraw = TRUE
|
||||
silent = TRUE
|
||||
|
||||
/datum/component/storage/concrete/pockets/shoes/Initialize()
|
||||
. = ..()
|
||||
cant_hold = typecacheof(list(/obj/item/screwdriver/power))
|
||||
can_hold = typecacheof(list(
|
||||
/obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, /obj/item/melee/cultblade/dagger,
|
||||
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
|
||||
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
|
||||
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
|
||||
/obj/item/firing_pin, /obj/item/gun/ballistic/automatic/pistol
|
||||
))
|
||||
|
||||
/datum/component/storage/concrete/pockets/shoes/clown/Initialize()
|
||||
. = ..()
|
||||
cant_hold = typecacheof(list(/obj/item/screwdriver/power))
|
||||
can_hold = typecacheof(list(
|
||||
/obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen, /obj/item/melee/cultblade/dagger,
|
||||
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
|
||||
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
|
||||
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
|
||||
/obj/item/firing_pin, /obj/item/bikehorn, /obj/item/gun/ballistic/automatic/pistol))
|
||||
|
||||
/datum/component/storage/concrete/pockets/pocketprotector
|
||||
max_items = 3
|
||||
max_w_class = WEIGHT_CLASS_TINY
|
||||
var/atom/original_parent
|
||||
|
||||
/datum/component/storage/concrete/pockets/pocketprotector/Initialize()
|
||||
original_parent = parent
|
||||
. = ..()
|
||||
can_hold = typecacheof(list( //Same items as a PDA
|
||||
/obj/item/pen,
|
||||
/obj/item/toy/crayon,
|
||||
/obj/item/lipstick,
|
||||
/obj/item/flashlight/pen,
|
||||
/obj/item/clothing/mask/cigarette))
|
||||
|
||||
/datum/component/storage/concrete/pockets/pocketprotector/real_location()
|
||||
// if the component is reparented to a jumpsuit, the items still go in the protector
|
||||
return original_parent
|
||||
@@ -0,0 +1,5 @@
|
||||
/datum/component/storage/concrete/secret_satchel/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M)
|
||||
if(SSpersistence.spawned_objects[I])
|
||||
to_chat(M, "<span class='warning'>[I] is unstable after its journey through space and time, it wouldn't survive another trip.</span>")
|
||||
return FALSE
|
||||
return ..()
|
||||
@@ -309,7 +309,6 @@
|
||||
else
|
||||
var/datum/numbered_display/ND = .[I.type]
|
||||
ND.number++
|
||||
. = sortTim(., /proc/cmp_numbered_displays_name_asc, associative = TRUE)
|
||||
|
||||
//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing.
|
||||
/datum/component/storage/proc/orient2hud(mob/user, maxcolumns)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/datum/component/swarming
|
||||
var/offset_x = 0
|
||||
var/offset_y = 0
|
||||
var/is_swarming = FALSE
|
||||
var/list/swarm_members = list()
|
||||
|
||||
/datum/component/swarming/Initialize(max_x = 24, max_y = 24)
|
||||
offset_x = rand(-max_x, max_x)
|
||||
offset_y = rand(-max_y, max_y)
|
||||
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, .proc/join_swarm)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/leave_swarm)
|
||||
|
||||
/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/AM)
|
||||
var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
|
||||
if(!other_swarm)
|
||||
return
|
||||
swarm()
|
||||
swarm_members |= other_swarm
|
||||
other_swarm.swarm()
|
||||
other_swarm.swarm_members |= src
|
||||
|
||||
/datum/component/swarming/proc/leave_swarm(datum/source, atom/movable/AM)
|
||||
var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
|
||||
if(!other_swarm || !(other_swarm in swarm_members))
|
||||
return
|
||||
swarm_members -= other_swarm
|
||||
if(!swarm_members.len)
|
||||
unswarm()
|
||||
other_swarm.swarm_members -= src
|
||||
if(!other_swarm.swarm_members.len)
|
||||
other_swarm.unswarm()
|
||||
|
||||
/datum/component/swarming/proc/swarm()
|
||||
var/atom/movable/owner = parent
|
||||
if(!is_swarming)
|
||||
is_swarming = TRUE
|
||||
animate(owner, pixel_x = owner.pixel_x + offset_x, pixel_y = owner.pixel_y + offset_y, time = 2)
|
||||
|
||||
/datum/component/swarming/proc/unswarm()
|
||||
var/atom/movable/owner = parent
|
||||
if(is_swarming)
|
||||
animate(owner, pixel_x = owner.pixel_x - offset_x, pixel_y = owner.pixel_y - offset_y, time = 2)
|
||||
is_swarming = FALSE
|
||||
@@ -0,0 +1,81 @@
|
||||
/datum/component/thermite
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
||||
var/amount
|
||||
var/overlay
|
||||
|
||||
var/static/list/blacklist = typecacheof(list(
|
||||
/turf/open/lava,
|
||||
/turf/open/space,
|
||||
/turf/open/water,
|
||||
/turf/open/chasm)
|
||||
)
|
||||
|
||||
var/static/list/immunelist = typecacheof(list(
|
||||
/turf/closed/wall/mineral/diamond,
|
||||
/turf/closed/indestructible,
|
||||
/turf/open/indestructible)
|
||||
)
|
||||
|
||||
var/static/list/resistlist = typecacheof(
|
||||
/turf/closed/wall/r_wall
|
||||
)
|
||||
|
||||
/datum/component/thermite/Initialize(_amount)
|
||||
if(!istype(parent, /turf) || blacklist[parent.type])
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
if(immunelist[parent.type])
|
||||
_amount*=0 //Yeah the overlay can still go on it and be cleaned but you arent burning down a diamond wall
|
||||
if(resistlist[parent.type])
|
||||
_amount*=0.25
|
||||
|
||||
amount = _amount*10
|
||||
|
||||
var/turf/master = parent
|
||||
overlay = mutable_appearance('icons/effects/effects.dmi', "thermite")
|
||||
master.add_overlay(overlay)
|
||||
|
||||
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react)
|
||||
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby_react)
|
||||
RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, .proc/flame_react)
|
||||
|
||||
/datum/component/thermite/Destroy()
|
||||
var/turf/master = parent
|
||||
master.cut_overlay(overlay)
|
||||
return ..()
|
||||
|
||||
/datum/component/thermite/InheritComponent(datum/component/thermite/newC, i_am_original, list/arguments)
|
||||
if(!i_am_original)
|
||||
return
|
||||
if(newC)
|
||||
amount += newC.amount
|
||||
else
|
||||
amount += arguments[1]
|
||||
|
||||
/datum/component/thermite/proc/thermite_melt(mob/user)
|
||||
var/turf/master = parent
|
||||
master.cut_overlay(overlay)
|
||||
var/obj/effect/overlay/thermite/fakefire = new(master)
|
||||
|
||||
playsound(master, 'sound/items/welder.ogg', 100, 1)
|
||||
|
||||
if(amount >= 50)
|
||||
var/burning_time = max(100, 100-amount)
|
||||
master = master.Melt()
|
||||
master.burn_tile()
|
||||
if(user)
|
||||
master.add_hiddenprint(user)
|
||||
QDEL_IN(fakefire, burning_time)
|
||||
else
|
||||
QDEL_IN(fakefire, 50)
|
||||
|
||||
/datum/component/thermite/proc/clean_react(datum/source, strength)
|
||||
//Thermite is just some loose powder, you could probably clean it with your hands. << todo?
|
||||
qdel(src)
|
||||
|
||||
/datum/component/thermite/proc/flame_react(datum/source, exposed_temperature, exposed_volume)
|
||||
if(exposed_temperature > 1922) // This is roughly the real life requirement to ignite thermite
|
||||
thermite_melt()
|
||||
|
||||
/datum/component/thermite/proc/attackby_react(datum/source, obj/item/thing, mob/user, params)
|
||||
if(thing.get_temperature())
|
||||
thermite_melt(user)
|
||||
@@ -0,0 +1,128 @@
|
||||
/datum/component/virtual_reality
|
||||
can_transfer = TRUE
|
||||
var/datum/mind/mastermind // where is my mind t. pixies
|
||||
var/datum/mind/current_mind
|
||||
var/obj/machinery/vr_sleeper/vr_sleeper
|
||||
var/datum/action/quit_vr/quit_action
|
||||
var/you_die_in_the_game_you_die_for_real = FALSE
|
||||
var/datum/component/virtual_reality/inception //The component works on a very fragile link betwixt mind, ckey and death.
|
||||
|
||||
/datum/component/virtual_reality/Initialize(mob/M, obj/machinery/vr_sleeper/gaming_pod, yolo = FALSE, new_char = TRUE)
|
||||
if(!ismob(parent) || !istype(M))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
var/mob/vr_M = parent
|
||||
mastermind = M.mind
|
||||
RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED), .proc/game_over)
|
||||
RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/switch_player)
|
||||
RegisterSignal(mastermind, COMSIG_MIND_TRANSFER, .proc/switch_player)
|
||||
you_die_in_the_game_you_die_for_real = yolo
|
||||
quit_action = new()
|
||||
if(gaming_pod)
|
||||
vr_sleeper = gaming_pod
|
||||
RegisterSignal(vr_sleeper, COMSIG_ATOM_EMAG_ACT, .proc/you_only_live_once)
|
||||
RegisterSignal(vr_sleeper, COMSIG_MACHINE_EJECT_OCCUPANT, .proc/revert_to_reality)
|
||||
vr_M.ckey = M.ckey
|
||||
var/datum/component/virtual_reality/clusterfk = M.GetComponent(/datum/component/virtual_reality)
|
||||
if(clusterfk && !clusterfk.inception)
|
||||
clusterfk.inception = src
|
||||
SStgui.close_user_uis(M, src)
|
||||
|
||||
/datum/component/virtual_reality/RegisterWithParent()
|
||||
var/mob/M = parent
|
||||
current_mind = M.mind
|
||||
quit_action.Grant(M)
|
||||
RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/revert_to_reality)
|
||||
RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED), .proc/game_over)
|
||||
RegisterSignal(M, COMSIG_MOB_GHOSTIZE, .proc/be_a_quitter)
|
||||
RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/pass_me_the_remote)
|
||||
RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/pass_me_the_remote)
|
||||
mastermind.current.audiovisual_redirect = M
|
||||
if(vr_sleeper)
|
||||
vr_sleeper.vr_mob = M
|
||||
|
||||
/datum/component/virtual_reality/UnregisterFromParent()
|
||||
quit_action.Remove(parent)
|
||||
UnregisterSignal(parent, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETED, COMSIG_MOB_KEY_CHANGE, COMSIG_MOB_GHOSTIZE))
|
||||
UnregisterSignal(current_mind, COMSIG_MIND_TRANSFER)
|
||||
UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER)
|
||||
current_mind = null
|
||||
mastermind.current.audiovisual_redirect = null
|
||||
|
||||
/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob)
|
||||
if(vr_sleeper || !new_mob.mind)
|
||||
// Machineries currently don't deal up with the occupant being polymorphed et similar... Or did something fuck up?
|
||||
revert_to_reality()
|
||||
return
|
||||
old_mob.audiovisual_redirect = null
|
||||
new_mob.audiovisual_redirect = parent
|
||||
|
||||
/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source)
|
||||
if(source != quit_action)
|
||||
return COMPONENT_ACTION_BLOCK_TRIGGER
|
||||
revert_to_reality(signal_source)
|
||||
|
||||
/datum/component/virtual_reality/proc/you_only_live_once()
|
||||
if(you_die_in_the_game_you_die_for_real || vr_sleeper?.only_current_user_can_interact)
|
||||
return FALSE
|
||||
you_die_in_the_game_you_die_for_real = TRUE
|
||||
return TRUE
|
||||
|
||||
/datum/component/virtual_reality/proc/pass_me_the_remote(datum/source, mob/new_mob)
|
||||
if(new_mob == mastermind.current)
|
||||
revert_to_reality(source)
|
||||
return TRUE
|
||||
new_mob.TakeComponent(src)
|
||||
return TRUE
|
||||
|
||||
/datum/component/virtual_reality/PostTransfer()
|
||||
if(!ismob(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
/datum/component/virtual_reality/proc/revert_to_reality(datum/source)
|
||||
quit_it()
|
||||
|
||||
/datum/component/virtual_reality/proc/game_over(datum/source)
|
||||
quit_it(TRUE, TRUE)
|
||||
|
||||
/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse)
|
||||
quit_it()
|
||||
return COMPONENT_BLOCK_GHOSTING
|
||||
|
||||
/datum/component/virtual_reality/proc/virtual_reality_in_a_virtual_reality(mob/player, killme = FALSE, datum/component/virtual_reality/yo_dawg)
|
||||
var/mob/M = parent
|
||||
quit_it(FALSE, killme, player, yo_dawg)
|
||||
yo_dawg.inception = null
|
||||
if(killme)
|
||||
M.death(FALSE)
|
||||
|
||||
/datum/component/virtual_reality/proc/quit_it(deathcheck = FALSE, cleanup = FALSE, mob/override)
|
||||
var/mob/M = parent
|
||||
var/mob/dreamer = override ? override : mastermind.current
|
||||
if(!mastermind)
|
||||
to_chat(M, "<span class='warning'>You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...</span>")
|
||||
else
|
||||
var/key_transfer = FALSE
|
||||
if(inception?.parent)
|
||||
inception.virtual_reality_in_a_virtual_reality(dreamer, cleanup, src)
|
||||
else
|
||||
key_transfer = TRUE
|
||||
if(key_transfer)
|
||||
M.transfer_ckey(dreamer, FALSE)
|
||||
dreamer.stop_sound_channel(CHANNEL_HEARTBEAT)
|
||||
dreamer.audiovisual_redirect = null
|
||||
if(deathcheck && you_die_in_the_game_you_die_for_real)
|
||||
to_chat(mastermind, "<span class='warning'>You feel everything fading away...</span>")
|
||||
dreamer.death(FALSE)
|
||||
if(cleanup)
|
||||
var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M)
|
||||
if(cleanbot)
|
||||
LAZYADD(cleanbot.corpse_party, M)
|
||||
if(vr_sleeper)
|
||||
vr_sleeper.vr_mob = null
|
||||
vr_sleeper = null
|
||||
qdel(src)
|
||||
|
||||
/datum/component/virtual_reality/Destroy()
|
||||
var/datum/action/quit_vr/delet_me = quit_action
|
||||
. = ..()
|
||||
qdel(delet_me)
|
||||
@@ -0,0 +1,15 @@
|
||||
/datum/component/waddling
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
||||
|
||||
/datum/component/waddling/Initialize()
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/Waddle)
|
||||
|
||||
/datum/component/waddling/proc/Waddle()
|
||||
var/mob/living/L = parent
|
||||
if(L.incapacitated() || L.lying)
|
||||
return
|
||||
animate(L, pixel_z = 4, time = 0)
|
||||
animate(pixel_z = 0, transform = turn(matrix(), pick(-12, 0, 12)), time=2)
|
||||
animate(pixel_z = 0, transform = matrix(), time = 0)
|
||||
@@ -0,0 +1,22 @@
|
||||
// A dummy parent type used for easily making components that target an item's wearer rather than the item itself.
|
||||
|
||||
/datum/component/wearertargeting
|
||||
var/list/valid_slots = list()
|
||||
var/list/signals = list()
|
||||
var/proctype = .proc/pass
|
||||
var/mobtype = /mob/living
|
||||
|
||||
/datum/component/wearertargeting/Initialize()
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
|
||||
|
||||
/datum/component/wearertargeting/proc/on_equip(datum/source, mob/equipper, slot)
|
||||
if((slot in valid_slots) && istype(equipper, mobtype))
|
||||
RegisterSignal(equipper, signals, proctype, TRUE)
|
||||
else
|
||||
UnregisterSignal(equipper, signals)
|
||||
|
||||
/datum/component/wearertargeting/proc/on_drop(datum/source, mob/user)
|
||||
UnregisterSignal(user, signals)
|
||||
@@ -0,0 +1,209 @@
|
||||
/datum/component/wet_floor
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
||||
can_transfer = TRUE
|
||||
var/highest_strength = TURF_DRY
|
||||
var/lube_flags = NONE //why do we have this?
|
||||
var/list/time_left_list //In deciseconds.
|
||||
var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor")
|
||||
var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor")
|
||||
var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static")
|
||||
var/static/mutable_appearance/generic_turf_overlay = mutable_appearance('icons/effects/water.dmi', "wet_static")
|
||||
var/current_overlay
|
||||
var/permanent = FALSE
|
||||
var/last_process = 0
|
||||
|
||||
/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, argslist)
|
||||
if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component
|
||||
add_wet(arglist(argslist))
|
||||
else //We are being passed in a full blown component
|
||||
var/datum/component/wet_floor/WF = newcomp //Lets make an assumption
|
||||
if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us.
|
||||
CRASH("Wet floor component tried to inherit another, but the other was able to garbage collect while being inherited! What a waste of time!")
|
||||
return
|
||||
for(var/i in WF.time_left_list)
|
||||
add_wet(text2num(i), WF.time_left_list[i])
|
||||
|
||||
/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE)
|
||||
if(!isopenturf(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
add_wet(strength, duration_minimum, duration_add, duration_maximum)
|
||||
permanent = _permanent
|
||||
if(!permanent)
|
||||
START_PROCESSING(SSwet_floors, src)
|
||||
addtimer(CALLBACK(src, .proc/gc, TRUE), 1) //GC after initialization.
|
||||
last_process = world.time
|
||||
|
||||
/datum/component/wet_floor/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet)
|
||||
RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry)
|
||||
|
||||
/datum/component/wet_floor/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY))
|
||||
|
||||
/datum/component/wet_floor/Destroy()
|
||||
STOP_PROCESSING(SSwet_floors, src)
|
||||
var/turf/T = parent
|
||||
qdel(T.GetComponent(/datum/component/slippery))
|
||||
if(istype(T)) //If this is false there is so many things wrong with it.
|
||||
T.cut_overlay(current_overlay)
|
||||
else
|
||||
stack_trace("Warning: Wet floor component wasn't on a turf when being destroyed! This is really bad!")
|
||||
return ..()
|
||||
|
||||
/datum/component/wet_floor/proc/update_overlay()
|
||||
var/intended
|
||||
if(!istype(parent, /turf/open/floor))
|
||||
intended = generic_turf_overlay
|
||||
else
|
||||
switch(highest_strength)
|
||||
if(TURF_WET_PERMAFROST)
|
||||
intended = permafrost_overlay
|
||||
if(TURF_WET_ICE)
|
||||
intended = ice_overlay
|
||||
else
|
||||
intended = water_overlay
|
||||
if(current_overlay != intended)
|
||||
var/turf/T = parent
|
||||
T.cut_overlay(current_overlay)
|
||||
T.add_overlay(intended)
|
||||
current_overlay = intended
|
||||
|
||||
/datum/component/wet_floor/proc/AfterSlip(mob/living/L)
|
||||
if(highest_strength == TURF_WET_LUBE)
|
||||
L.confused = max(L.confused, 8)
|
||||
|
||||
/datum/component/wet_floor/proc/update_flags()
|
||||
var/intensity
|
||||
lube_flags = NONE
|
||||
switch(highest_strength)
|
||||
if(TURF_WET_WATER)
|
||||
intensity = 60
|
||||
lube_flags = NO_SLIP_WHEN_WALKING
|
||||
if(TURF_WET_LUBE)
|
||||
intensity = 80
|
||||
lube_flags = SLIDE | GALOSHES_DONT_HELP
|
||||
if(TURF_WET_ICE)
|
||||
intensity = 120
|
||||
lube_flags = SLIDE | GALOSHES_DONT_HELP
|
||||
if(TURF_WET_PERMAFROST)
|
||||
intensity = 120
|
||||
lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP
|
||||
if(TURF_WET_SUPERLUBE)
|
||||
intensity = 120
|
||||
lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING
|
||||
else
|
||||
qdel(parent.GetComponent(/datum/component/slippery))
|
||||
return
|
||||
|
||||
var/datum/component/slippery/S = parent.LoadComponent(/datum/component/slippery, NONE, CALLBACK(src, .proc/AfterSlip))
|
||||
S.intensity = intensity
|
||||
S.lube_flags = lube_flags
|
||||
|
||||
/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY)
|
||||
for(var/i in time_left_list)
|
||||
if(text2num(i) <= strength)
|
||||
time_left_list[i] = max(0, time_left_list[i] - duration_decrease)
|
||||
if(immediate)
|
||||
check()
|
||||
|
||||
/datum/component/wet_floor/proc/max_time_left()
|
||||
. = 0
|
||||
for(var/i in time_left_list)
|
||||
. = max(., time_left_list[i])
|
||||
|
||||
/datum/component/wet_floor/process()
|
||||
var/turf/open/T = parent
|
||||
var/diff = world.time - last_process
|
||||
var/decrease = 0
|
||||
var/t = T.GetTemperature()
|
||||
switch(t)
|
||||
if(-INFINITY to T0C)
|
||||
add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice!
|
||||
if(T0C to T0C + 100)
|
||||
decrease = ((T.air.temperature - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio)
|
||||
if(T0C + 100 to INFINITY)
|
||||
decrease = INFINITY
|
||||
decrease = max(0, decrease)
|
||||
if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water!
|
||||
for(var/obj/O in T.contents)
|
||||
if(O.obj_flags & FROZEN)
|
||||
O.make_unfrozen()
|
||||
add_wet(TURF_WET_WATER, max_time_left())
|
||||
dry(null, TURF_WET_ICE)
|
||||
dry(null, ALL, FALSE, decrease)
|
||||
check()
|
||||
last_process = world.time
|
||||
|
||||
/datum/component/wet_floor/proc/update_strength()
|
||||
highest_strength = 0 //Not bitflag.
|
||||
for(var/i in time_left_list)
|
||||
highest_strength = max(highest_strength, text2num(i))
|
||||
|
||||
/datum/component/wet_floor/proc/is_wet()
|
||||
. = 0
|
||||
for(var/i in time_left_list)
|
||||
. |= text2num(i)
|
||||
|
||||
/datum/component/wet_floor/PreTransfer()
|
||||
var/turf/O = parent
|
||||
O.cut_overlay(current_overlay)
|
||||
//That turf is no longer slippery, we're out of here
|
||||
//Slippery components don't transfer due to callbacks
|
||||
qdel(O.GetComponent(/datum/component/slippery))
|
||||
|
||||
/datum/component/wet_floor/PostTransfer()
|
||||
if(!isopenturf(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
var/turf/T = parent
|
||||
T.add_overlay(current_overlay)
|
||||
//Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent)
|
||||
update_flags()
|
||||
|
||||
//NB it's possible we get deleted after this, due to inherit
|
||||
|
||||
/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE)
|
||||
var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE)
|
||||
if(duration_minimum <= 0 || !type)
|
||||
return FALSE
|
||||
if(type in allowed_types)
|
||||
return _do_add_wet(type, duration_minimum, duration_add, duration_maximum)
|
||||
else
|
||||
. = NONE
|
||||
for(var/i in allowed_types)
|
||||
if(!(type & i))
|
||||
continue
|
||||
. |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum)
|
||||
if(_permanent)
|
||||
permanent = TRUE
|
||||
STOP_PROCESSING(SSwet_floors, src)
|
||||
|
||||
/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum)
|
||||
var/time = 0
|
||||
if(LAZYACCESS(time_left_list, "[type]"))
|
||||
time = CLAMP(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum)
|
||||
else
|
||||
time = min(duration_minimum, duration_maximum)
|
||||
LAZYSET(time_left_list, "[type]", time)
|
||||
check(TRUE)
|
||||
return TRUE
|
||||
|
||||
/datum/component/wet_floor/proc/gc(on_init = FALSE)
|
||||
if(!LAZYLEN(time_left_list))
|
||||
if(on_init)
|
||||
var/turf/T = parent
|
||||
stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].")
|
||||
qdel(src)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/datum/component/wet_floor/proc/check(force_update = FALSE)
|
||||
var/changed = FALSE
|
||||
for(var/i in time_left_list)
|
||||
if(time_left_list[i] <= 0)
|
||||
time_left_list -= i
|
||||
changed = TRUE
|
||||
if(changed || force_update)
|
||||
update_strength()
|
||||
update_overlay()
|
||||
update_flags()
|
||||
gc()
|
||||
Reference in New Issue
Block a user