[READY][FINALLY] Various Crossbreed Updates (#38039)

* A Variety Of Updates

* Lovey Sprites!

* Adds implementation for listening status effects.

* Fixes implementation, uses defines instead of magic strings.

* Completely reworks status effect listeners

* Fixes Love Potions

* Fixes Approximately All The Things.jpg
This commit is contained in:
fludd12
2018-06-08 08:51:03 -04:00
committed by vuonojenmustaturska
parent 7e38b8635e
commit 0d429e01b8
22 changed files with 205 additions and 18 deletions

View File

@@ -87,6 +87,11 @@
#define COMSIG_MOVABLE_THROW "movable_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin) #define COMSIG_MOVABLE_THROW "movable_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin)
#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" //from base of atom/movable/onTransitZ(): (old_z, new_z) #define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" //from base of atom/movable/onTransitZ(): (old_z, new_z)
// /mob/living signals
#define COMSIG_LIVING_RESIST "living_resist" //from base of mob/living/resist() (/mob/living)
#define COMSIG_LIVING_IGNITED "living_ignite" //from base of mob/living/IgniteMob() (/mob/living)
#define COMSIG_LIVING_EXTINGUISHED "living_extinguished" //from base of mob/living/ExtinguishMob() (/mob/living)
// /mob/living/carbon signals // /mob/living/carbon signals
#define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity)) #define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity))

View File

@@ -80,6 +80,8 @@
#define STATUS_EFFECT_SYPHONMARK /datum/status_effect/syphon_mark //tracks kills for the KA death syphon module #define STATUS_EFFECT_SYPHONMARK /datum/status_effect/syphon_mark //tracks kills for the KA death syphon module
#define STATUS_EFFECT_INLOVE /datum/status_effect/in_love //Displays you as being in love with someone else, and makes hearts appear around them.
///////////// /////////////
// SLIME // // SLIME //
///////////// /////////////

View File

@@ -50,6 +50,9 @@ Stands have a lot of procs which mimic mob procs. Rather than inserting hooks fo
1. `/datum/component/var/datum/parent` (protected, read-only) 1. `/datum/component/var/datum/parent` (protected, read-only)
* The datum this component belongs to * The datum this component belongs to
* Never `null` in child procs * Never `null` in child procs
1. `report_signal_origin` (protected, boolean)
* If `TRUE`, will invoke the callback when signalled with the signal type as the first argument.
* `FALSE` by default.
### Procs ### Procs

View File

@@ -3,6 +3,7 @@
var/dupe_mode = COMPONENT_DUPE_HIGHLANDER var/dupe_mode = COMPONENT_DUPE_HIGHLANDER
var/dupe_type var/dupe_type
var/list/signal_procs var/list/signal_procs
var/report_signal_origin = FALSE
var/datum/parent var/datum/parent
/datum/component/New(datum/P, ...) /datum/component/New(datum/P, ...)
@@ -129,6 +130,8 @@
var/datum/callback/CB = C.signal_procs[sigtype] var/datum/callback/CB = C.signal_procs[sigtype]
if(!CB) if(!CB)
return NONE return NONE
if(initial(C.report_signal_origin))
arguments = list(sigtype) + arguments
return CB.InvokeAsync(arglist(arguments)) return CB.InvokeAsync(arglist(arguments))
. = NONE . = NONE
for(var/I in target) for(var/I in target)
@@ -138,7 +141,10 @@
var/datum/callback/CB = C.signal_procs[sigtype] var/datum/callback/CB = C.signal_procs[sigtype]
if(!CB) if(!CB)
continue continue
. |= CB.InvokeAsync(arglist(arguments)) if(initial(C.report_signal_origin))
. |= CB.InvokeAsync(arglist(list(sigtype) + arguments))
else
. |= CB.InvokeAsync(arglist(arguments))
/datum/proc/GetComponent(c_type) /datum/proc/GetComponent(c_type)
var/list/dc = datum_components var/list/dc = datum_components

View File

@@ -0,0 +1,38 @@
/datum/component/status_effect_listener
dupe_mode = COMPONENT_DUPE_UNIQUE
report_signal_origin = TRUE
var/list/effect_signals = list()
/datum/component/status_effect_listener/proc/RegisterEffectSignal(datum/status_effect/se, sig_type_or_types, proc_or_callback)
var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types)
RegisterSignal(sig_type_or_types, .proc/signal, override = TRUE)
for(var/type in sig_types)
var/list/callbacks = effect_signals[type]
if(!callbacks)
callbacks = list()
effect_signals[type] = callbacks
if(!istype(proc_or_callback, /datum/callback)) //if it wasnt a callback before, it is now
proc_or_callback = CALLBACK(se, proc_or_callback)
effect_signals[type] += proc_or_callback
/datum/component/status_effect_listener/proc/ClearSignalRegister(datum/status_effect/se)
for(var/type in effect_signals)
for(var/datum/callback/cb in effect_signals[type])
if(cb.object == se)
effect_signals[type] -= cb
QDEL_NULL(cb)
if(effect_signals[type].len <= 0)
QDEL_LIST(effect_signals[type])
effect_signals[type] = null
effect_signals -= type
if(effect_signals.len <= 0)
QDEL_LIST(effect_signals)
qdel(src)
/datum/component/status_effect_listener/proc/signal(sigtype, ...)
var/list/arguments = args.Copy(2)
if(effect_signals[sigtype])
for(var/datum/callback/CB in effect_signals[sigtype])
if(!CB)
continue
. |= CB.InvokeAsync(arglist(arguments))

View File

@@ -12,6 +12,7 @@
icon_state = "frozen" icon_state = "frozen"
/datum/status_effect/freon/on_apply() /datum/status_effect/freon/on_apply()
RegisterEffectSignal(COMSIG_LIVING_RESIST, .proc/owner_resist)
if(!owner.stat) if(!owner.stat)
to_chat(owner, "<span class='userdanger'>You become frozen in a cube!</span>") to_chat(owner, "<span class='userdanger'>You become frozen in a cube!</span>")
cube = icon('icons/effects/freeze.dmi', "ice_cube") cube = icon('icons/effects/freeze.dmi', "ice_cube")
@@ -24,6 +25,14 @@
if(can_melt && owner.bodytemperature >= BODYTEMP_NORMAL) if(can_melt && owner.bodytemperature >= BODYTEMP_NORMAL)
qdel(src) qdel(src)
/datum/status_effect/freon/proc/owner_resist()
to_chat(owner, "You start breaking out of the ice cube!")
if(do_mob(owner, owner, 40))
if(!QDELETED(src))
to_chat(owner, "You break out of the ice cube!")
owner.remove_status_effect(/datum/status_effect/freon)
owner.update_canmove()
/datum/status_effect/freon/on_remove() /datum/status_effect/freon/on_remove()
if(!owner.stat) if(!owner.stat)
to_chat(owner, "The cube melts!") to_chat(owner, "The cube melts!")

View File

@@ -47,3 +47,25 @@
/datum/status_effect/syphon_mark/on_remove() /datum/status_effect/syphon_mark/on_remove()
get_kill() get_kill()
. = ..() . = ..()
/obj/screen/alert/status_effect/in_love
name = "In Love"
desc = "You feel so wonderfully in love!"
icon_state = "in_love"
/datum/status_effect/in_love
id = "in_love"
duration = -1
status_type = STATUS_EFFECT_UNIQUE
alert_type = /obj/screen/alert/status_effect/in_love
var/mob/living/date
/datum/status_effect/in_love/on_creation(mob/living/new_owner, mob/living/love_interest)
. = ..()
if(.)
date = love_interest
linked_alert.desc = "You're in love with [date.real_name]! How lovely."
/datum/status_effect/in_love/tick()
if(date)
new /obj/effect/temp_visual/love_heart/invisible(get_turf(date.loc), owner)

View File

@@ -13,9 +13,20 @@
var/alert_type = /obj/screen/alert/status_effect //the alert thrown by the status effect, contains name and description var/alert_type = /obj/screen/alert/status_effect //the alert thrown by the status effect, contains name and description
var/obj/screen/alert/status_effect/linked_alert = null //the alert itself, if it exists var/obj/screen/alert/status_effect/linked_alert = null //the alert itself, if it exists
var/listening = FALSE //Whether or not the status effect listens to mob interaction, using the status_effect_listener. Don't touch!
/datum/status_effect/New(list/arguments) /datum/status_effect/New(list/arguments)
on_creation(arglist(arguments)) on_creation(arglist(arguments))
/datum/status_effect/proc/RegisterEffectSignal(sig_type_or_types, proc_or_callback)
if(!owner)
CRASH("[src] attempted to apply a status effect listener before it had an owner.")
GET_COMPONENT_FROM(listener, /datum/component/status_effect_listener, owner)
if(!listener)
listener = owner.AddComponent(/datum/component/status_effect_listener)
listener.RegisterEffectSignal(src,sig_type_or_types, proc_or_callback)
listening = TRUE
/datum/status_effect/proc/on_creation(mob/living/new_owner, ...) /datum/status_effect/proc/on_creation(mob/living/new_owner, ...)
if(new_owner) if(new_owner)
owner = new_owner owner = new_owner
@@ -40,6 +51,10 @@
owner.clear_alert(id) owner.clear_alert(id)
LAZYREMOVE(owner.status_effects, src) LAZYREMOVE(owner.status_effects, src)
on_remove() on_remove()
if(listening)
GET_COMPONENT_FROM(listener, /datum/component/status_effect_listener, owner)
if(listener)
listener.ClearSignalRegister(src)
owner = null owner = null
return ..() return ..()
@@ -56,6 +71,7 @@
/datum/status_effect/proc/on_apply() //Called whenever the buff is applied; returning FALSE will cause it to autoremove itself. /datum/status_effect/proc/on_apply() //Called whenever the buff is applied; returning FALSE will cause it to autoremove itself.
return TRUE return TRUE
/datum/status_effect/proc/tick() //Called every tick. /datum/status_effect/proc/tick() //Called every tick.
/datum/status_effect/proc/receiveSignal(var/sigtype) //Called when a listener recieves a signal, if the effect is listening.
/datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null /datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null
/datum/status_effect/proc/be_replaced() //Called instead of on_remove when a status effect is replaced by itself or when a status effect with on_remove_on_mob_delete = FALSE has its mob deleted /datum/status_effect/proc/be_replaced() //Called instead of on_remove when a status effect is replaced by itself or when a status effect with on_remove_on_mob_delete = FALSE has its mob deleted
owner.clear_alert(id) owner.clear_alert(id)

View File

@@ -167,3 +167,16 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
if(isrevenant(M) || iseminence(M) || iswizard(M)) if(isrevenant(M) || iseminence(M) || iswizard(M))
return TRUE return TRUE
return FALSE return FALSE
datum/atom_hud/alternate_appearance/basic/onePerson
var/mob/seer
/datum/atom_hud/alternate_appearance/basic/onePerson/mobShouldSee(mob/M)
if(M == seer)
return TRUE
return FALSE
/datum/atom_hud/alternate_appearance/basic/onePerson/New(key, image/I, mob/living/M)
..(key, I, FALSE)
seer = M
add_hud_to(seer)

View File

@@ -358,6 +358,29 @@
pixel_y = rand(-4,4) pixel_y = rand(-4,4)
animate(src, pixel_y = pixel_y + 32, alpha = 0, time = 25) animate(src, pixel_y = pixel_y + 32, alpha = 0, time = 25)
/obj/effect/temp_visual/love_heart
name = "love heart"
icon = 'icons/effects/effects.dmi'
icon_state = "heart"
duration = 25
/obj/effect/temp_visual/love_heart/Initialize(mapload)
. = ..()
pixel_x = rand(-10,10)
pixel_y = rand(-10,10)
animate(src, pixel_y = pixel_y + 32, alpha = 0, time = duration)
/obj/effect/temp_visual/love_heart/invisible
icon_state = null
/obj/effect/temp_visual/love_heart/invisible/Initialize(mapload, mob/seer)
. = ..()
var/image/I = image(icon = 'icons/effects/effects.dmi', icon_state = "heart", layer = ABOVE_MOB_LAYER, loc = src)
add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/onePerson, "heart", I, seer)
I.alpha = 255
I.appearance_flags = RESET_ALPHA
animate(I, alpha = 0, time = duration)
/obj/effect/temp_visual/bleed /obj/effect/temp_visual/bleed
name = "bleed" name = "bleed"
icon = 'icons/effects/bleed.dmi' icon = 'icons/effects/bleed.dmi'

View File

@@ -16,8 +16,17 @@
/datum/antagonist/valentine/on_gain() /datum/antagonist/valentine/on_gain()
forge_objectives() forge_objectives()
if(isliving(owner))
var/mob/living/L = owner
L.apply_status_effect(STATUS_EFFECT_INLOVE, date)
. = ..() . = ..()
/datum/antagonist/valentine/on_removal()
. = ..()
if(isliving(owner))
var/mob/living/L = owner
L.remove_status_effect(STATUS_EFFECT_INLOVE)
/datum/antagonist/valentine/greet() /datum/antagonist/valentine/greet()
to_chat(owner, "<span class='warning'><B>You're on a date with [date.name]! Protect [date.p_them()] at all costs. This takes priority over all other loyalties.</B></span>") to_chat(owner, "<span class='warning'><B>You're on a date with [date.name]! Protect [date.p_them()] at all costs. This takes priority over all other loyalties.</B></span>")

View File

@@ -723,9 +723,8 @@
to_chat(src, "<span class='warning'>You can't do that right now!</span>") to_chat(src, "<span class='warning'>You can't do that right now!</span>")
return FALSE return FALSE
if(!Adjacent(M) && (M.loc != src)) if(!Adjacent(M) && (M.loc != src))
if((be_close == 0) && (dna.check_mutation(TK))) if((be_close == 0) || (dna.check_mutation(TK) && tkMaxRangeCheck(src, M)))
if(tkMaxRangeCheck(src, M)) return TRUE
return TRUE
to_chat(src, "<span class='warning'>You are too far away!</span>") to_chat(src, "<span class='warning'>You are too far away!</span>")
return FALSE return FALSE
return TRUE return TRUE

View File

@@ -583,6 +583,7 @@
return return
changeNext_move(CLICK_CD_RESIST) changeNext_move(CLICK_CD_RESIST)
SendSignal(COMSIG_LIVING_RESIST, src)
//resisting grabs (as if it helps anyone...) //resisting grabs (as if it helps anyone...)
if(!restrained(ignore_grab = 1) && pulledby) if(!restrained(ignore_grab = 1) && pulledby)
visible_message("<span class='danger'>[src] resists against [pulledby]'s grip!</span>") visible_message("<span class='danger'>[src] resists against [pulledby]'s grip!</span>")
@@ -599,14 +600,6 @@
var/obj/C = loc var/obj/C = loc
C.container_resist(src) C.container_resist(src)
else if(IsFrozen())
to_chat(src, "You start breaking out of the ice cube!")
if(do_mob(src, src, 40))
if(IsFrozen())
to_chat(src, "You break out of the ice cube!")
remove_status_effect(/datum/status_effect/freon)
update_canmove()
else if(canmove) else if(canmove)
if(on_fire) if(on_fire)
resist_fire() //stop, drop, and roll resist_fire() //stop, drop, and roll
@@ -907,6 +900,7 @@
new/obj/effect/dummy/fire(src) new/obj/effect/dummy/fire(src)
throw_alert("fire", /obj/screen/alert/fire) throw_alert("fire", /obj/screen/alert/fire)
update_fire() update_fire()
SendSignal(COMSIG_LIVING_IGNITED,src)
return TRUE return TRUE
return FALSE return FALSE
@@ -918,6 +912,7 @@
qdel(F) qdel(F)
clear_alert("fire") clear_alert("fire")
SendSignal(COMSIG_CLEAR_MOOD_EVENT, "on_fire") SendSignal(COMSIG_CLEAR_MOOD_EVENT, "on_fire")
SendSignal(COMSIG_LIVING_EXTINGUISHED, src)
update_fire() update_fire()
/mob/living/proc/adjust_fire_stacks(add_fire_stacks) //Adjusting the amount of fire_stacks we have on person /mob/living/proc/adjust_fire_stacks(add_fire_stacks) //Adjusting the amount of fire_stacks we have on person

View File

@@ -39,7 +39,7 @@ To add a crossbreed:
/obj/item/slimecross/Initialize() /obj/item/slimecross/Initialize()
..() ..()
name = colour + " " + name name = effect + " " + colour + " extract"
var/itemcolor = "#FFFFFF" var/itemcolor = "#FFFFFF"
switch(colour) switch(colour)
if("orange") if("orange")

View File

@@ -564,6 +564,7 @@ datum/status_effect/stabilized/blue/on_remove()
/datum/status_effect/stabilized/cerulean/on_remove() /datum/status_effect/stabilized/cerulean/on_remove()
if(clone) if(clone)
clone.visible_message("<span class='warning'>[clone] dissolves into a puddle of goo!</span>") clone.visible_message("<span class='warning'>[clone] dissolves into a puddle of goo!</span>")
clone.unequip_everything()
qdel(clone) qdel(clone)
/datum/status_effect/stabilized/pyrite /datum/status_effect/stabilized/pyrite
@@ -760,9 +761,16 @@ datum/status_effect/stabilized/blue/on_remove()
var/mob/living/simple_animal/familiar var/mob/living/simple_animal/familiar
/datum/status_effect/stabilized/gold/tick() /datum/status_effect/stabilized/gold/tick()
var/obj/item/slimecross/stabilized/gold/linked = linked_extract
if(!familiar) if(!familiar)
familiar = create_random_mob(get_turf(owner.loc), FRIENDLY_SPAWN) familiar = new linked.mob_type(get_turf(owner.loc))
familiar.del_on_death = TRUE familiar.del_on_death = TRUE
if(linked.saved_mind)
linked.saved_mind.transfer_to(familiar)
familiar.ckey = linked.saved_mind.key
else
if(familiar.mind)
linked.saved_mind = familiar.mind
return ..() return ..()
/datum/status_effect/stabilized/gold/on_remove() /datum/status_effect/stabilized/gold/on_remove()

View File

@@ -386,6 +386,9 @@ Charged extracts:
if(user == M) if(user == M)
to_chat(user, "<span class='warning'>You can't drink the love potion. What are you, a narcissist?</span>") to_chat(user, "<span class='warning'>You can't drink the love potion. What are you, a narcissist?</span>")
return ..() return ..()
if(M.has_status_effect(STATUS_EFFECT_INLOVE))
to_chat(user, "<span class='warning'>[M] is already lovestruck!</span>")
return ..()
M.visible_message("<span class='danger'>[user] starts to feed [M] a love potion!</span>", M.visible_message("<span class='danger'>[user] starts to feed [M] a love potion!</span>",
"<span class='userdanger'>[user] starts to feed you a love potion!</span>") "<span class='userdanger'>[user] starts to feed you a love potion!</span>")
@@ -393,10 +396,11 @@ Charged extracts:
if(!do_after(user, 50, target = M)) if(!do_after(user, 50, target = M))
return return
to_chat(user, "<span class='notice'>You feed [M] the love potion!</span>") to_chat(user, "<span class='notice'>You feed [M] the love potion!</span>")
to_chat(M, "<span class='notice'>You develop feelings for [user], and anyone [p_they(user)] like.</span>") to_chat(M, "<span class='notice'>You develop feelings for [user], and anyone [user.p_they()] like.</span>")
if(!("[REF(user)]" in M.faction) && M.mind) if(M.mind)
M.mind.store_memory("You have strong feelings for [user].") M.mind.store_memory("You are in love with [user].")
M.faction |= "[REF(user)]" M.faction |= "[REF(user)]"
M.apply_status_effect(STATUS_EFFECT_INLOVE, user)
qdel(src) qdel(src)
/obj/item/slimepotion/peacepotion /obj/item/slimepotion/peacepotion

View File

@@ -21,6 +21,7 @@ Recurring extracts:
extract.desc = desc extract.desc = desc
extract.icon = icon extract.icon = icon
extract.icon_state = icon_state extract.icon_state = icon_state
extract.color = color
extract.recurring = TRUE extract.recurring = TRUE
src.forceMove(extract) src.forceMove(extract)
START_PROCESSING(SSobj,src) START_PROCESSING(SSobj,src)

View File

@@ -4,7 +4,7 @@ Self-sustaining extracts:
*/ */
/obj/item/slimecross/selfsustaining /obj/item/slimecross/selfsustaining
name = "self-sustaining extract" name = "self-sustaining extract"
effect = "selfsustaining" effect = "self-sustaining"
icon_state = "selfsustaining" icon_state = "selfsustaining"
var/extract_type = /obj/item/slime_extract var/extract_type = /obj/item/slime_extract
@@ -23,6 +23,7 @@ Self-sustaining extracts:
A.extract = X A.extract = X
A.icon = icon A.icon = icon
A.icon_state = icon_state A.icon_state = icon_state
A.color = color
qdel(src) qdel(src)
/obj/item/autoslime/Initialize() /obj/item/autoslime/Initialize()

View File

@@ -96,6 +96,38 @@ Stabilized extracts:
/obj/item/slimecross/stabilized/gold /obj/item/slimecross/stabilized/gold
colour = "gold" colour = "gold"
var/mob_type
var/datum/mind/saved_mind
/obj/item/slimecross/stabilized/gold/proc/generate_mobtype()
var/static/list/mob_spawn_pets = list()
if(mob_spawn_pets.len <= 0)
for(var/T in typesof(/mob/living/simple_animal))
var/mob/living/simple_animal/SA = T
switch(initial(SA.gold_core_spawnable))
if(FRIENDLY_SPAWN)
mob_spawn_pets += T
mob_type = pick(mob_spawn_pets)
/obj/item/slimecross/stabilized/gold/Initialize()
..()
generate_mobtype()
/obj/item/slimecross/stabilized/gold/attack_self(mob/user)
var/choice = input(user, "Which do you want to reset?", "Familiar Adjustment") as null|anything in list("Familiar Species", "Familiar Sentience")
if(!user.canUseTopic(src, BE_CLOSE))
return
if(isliving(user))
var/mob/living/L = user
if(L.has_status_effect(/datum/status_effect/stabilized/gold))
L.remove_status_effect(/datum/status_effect/stabilized/gold)
START_PROCESSING(SSobj, src)
if(choice == "Familiar Species")
to_chat(user, "<span class='notice'>You squeeze [src], and a shape seems to shift around inside.</span>")
generate_mobtype()
if(choice == "Familiar Sentience")
to_chat(user, "<span class='notice'>You poke [src], and it lets out a glowing pulse.</span>")
saved_mind = null
/obj/item/slimecross/stabilized/oil /obj/item/slimecross/stabilized/oil
colour = "oil" colour = "oil"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 KiB

After

Width:  |  Height:  |  Size: 865 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

@@ -341,6 +341,7 @@
#include "code\datums\components\spooky.dm" #include "code\datums\components\spooky.dm"
#include "code\datums\components\squeek.dm" #include "code\datums\components\squeek.dm"
#include "code\datums\components\stationloving.dm" #include "code\datums\components\stationloving.dm"
#include "code\datums\components\status_effect_listener.dm"
#include "code\datums\components\swarming.dm" #include "code\datums\components\swarming.dm"
#include "code\datums\components\thermite.dm" #include "code\datums\components\thermite.dm"
#include "code\datums\components\wearertargeting.dm" #include "code\datums\components\wearertargeting.dm"