Skip to content

Commit

Permalink
Add de-evolve cooldown per caste & De-evolve fixes (#7633)
Browse files Browse the repository at this point in the history
# About the pull request

This PR adds a 5 minute cooldown to go to the same caste again after a
de-evolution.

It also fixes various oversights between the self de-evolve vs the queen
de-evolve (they really ought to be unified, but there's also various
subtle differences between them). Also fixes the Mature Larva name never
getting applied.

# Explain why it's good for the game

Normally I would not expect a player needing to de-evolve and re-evolve
to the same caste, but there are edge cases where this can be used to
reset conditions so it should be discouraged.

# Testing Photographs and Procedure
<details>
<summary>Screenshots & Videos</summary>

https://youtu.be/ScnsdwFa4yo

After fixes/refactoring:
https://youtu.be/LxlmKzN9QkU

</details>


# Changelog
:cl: Drathek
add: Added 5 minute cooldown when de-evolving to evolve into the same
caste
fix: Fixed queen de-evolve deleting her own organ rather than target
xeno's
fix: Fixed queen de-evolve not transferring built_structures list
fix: Fixed regular de-evolve not stopping stat tracking on old xeno
fix: Fixed de-evolve verb getting restored if it was taken away
(Wouldn't without admin intervention currently)
fix: Fixed mature larva name never getting applied when full evo
threshold
del: De-evolving to larva is now never bloodied (bloody used to only
depend on evolution points to determine bloody status)
/:cl:
  • Loading branch information
Drulikar authored Nov 21, 2024
1 parent ba1c072 commit 813e85c
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 83 deletions.
7 changes: 7 additions & 0 deletions code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,10 @@

/// used in /datum/component/status_effect/cleanse()
#define COMSIG_XENO_DEBUFF_CLEANSE "xeno_debuff_cleanse"

/// From /mob/living/carbon/xenomorph/verb/Deevolve()
#define COMSIG_XENO_DEEVOLVE "xeno_deevolve"

/// From /mob/living/carbon/xenomorph/proc/do_evolve(): (castepick)
#define COMSIG_XENO_TRY_EVOLVE "xeno_try_evolve"
#define COMPONENT_OVERRIDE_EVOLVE (1<<0)
7 changes: 6 additions & 1 deletion code/__DEFINES/xeno.dm
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@
#define XENO_SHIELD_SOURCE_CUMULATIVE_GENERIC 10

//XENO CASTES
#define XENO_CASTE_LARVA "Bloody Larva"
#define XENO_CASTE_LARVA "Larva"
#define XENO_CASTE_PREDALIEN_LARVA "Predalien Larva"
#define XENO_CASTE_FACEHUGGER "Facehugger"
#define XENO_CASTE_LESSER_DRONE "Lesser Drone"
Expand Down Expand Up @@ -767,3 +767,8 @@

#define JOIN_AS_FACEHUGGER_DELAY (3 MINUTES)
#define JOIN_AS_LESSER_DRONE_DELAY (30 SECONDS)

// larva states
#define LARVA_STATE_BLOODY 0
#define LARVA_STATE_NORMAL 1
#define LARVA_STATE_MATURE 2
5 changes: 5 additions & 0 deletions code/__HELPERS/unsorted.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
return TRUE

#define VARSET_LIST_CALLBACK(target, var_name, var_value) CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(___callbackvarset), ##target, ##var_name, ##var_value)
#define VARSET_LIST_REMOVE_CALLBACK(target, var_value) CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(___callbackvarsetlistremove), ##target, ##var_value)
//dupe code because dm can't handle 3 level deep macros
#define VARSET_CALLBACK(datum, var, var_value) CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(___callbackvarset), ##datum, NAMEOF(##datum, ##var), ##var_value)
/// Same as VARSET_CALLBACK, but uses a weakref to the datum.
Expand All @@ -1474,6 +1475,10 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
else
datum.vars[var_name] = var_value

/proc/___callbackvarsetlistremove(list, var_value)
if(length(list))
list -= var_value

//don't question just accept
/proc/pass(...)
return
Expand Down
38 changes: 38 additions & 0 deletions code/datums/components/deevolve_cooldown.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#define XENO_DEEVOLVE_COOLDOWN 5 MINUTES

/**
* A component that should be on all xenos that should be prevented from de-evolution manipulation.
*/
/datum/component/deevolve_cooldown
/// The xeno that we are bound to
var/mob/living/carbon/xenomorph/parent_xeno
/// Assoc list of caste define to timerid of recent de-evolves this xeno has performed that are still on cooldown
var/list/deevolves_on_cooldown = list()

/datum/component/deevolve_cooldown/Initialize(mob/living/carbon/xenomorph/old_xeno)
parent_xeno = parent
if(!istype(parent_xeno))
return COMPONENT_INCOMPATIBLE
var/datum/component/deevolve_cooldown/old_component = old_xeno?.GetComponent(/datum/component/deevolve_cooldown)
if(old_component)
deevolves_on_cooldown = old_component.deevolves_on_cooldown

/datum/component/deevolve_cooldown/RegisterWithParent()
RegisterSignal(parent_xeno, COMSIG_XENO_DEEVOLVE, PROC_REF(on_deevolve))
RegisterSignal(parent_xeno, COMSIG_XENO_TRY_EVOLVE, PROC_REF(on_try_evolve))

/datum/component/deevolve_cooldown/UnregisterFromParent()
UnregisterSignal(parent_xeno, list(COMSIG_XENO_DEEVOLVE, COMSIG_XENO_TRY_EVOLVE))

/// Signal handler for COMSIG_XENO_DEEVOLVE to add the current xeno as a de-evolution
/datum/component/deevolve_cooldown/proc/on_deevolve()
deevolves_on_cooldown[parent_xeno.caste_type] = addtimer(VARSET_LIST_REMOVE_CALLBACK(deevolves_on_cooldown, "[parent_xeno.caste_type]"), XENO_DEEVOLVE_COOLDOWN, TIMER_STOPPABLE)
return

/// Signal handler for COMSIG_XENO_TRY_EVOLVE to determine is the evolution is allowed
/datum/component/deevolve_cooldown/proc/on_try_evolve(mob/living/carbon/xenomorph/old_xeno, castepick)
var/on_cooldown_timer = deevolves_on_cooldown[castepick]
if(on_cooldown_timer)
to_chat(old_xeno, SPAN_WARNING("We cannot evolve into this caste again yet! ([DisplayTimeText(timeleft(on_cooldown_timer), 1)] remaining)"))
return COMPONENT_OVERRIDE_EVOLVE
return FALSE
68 changes: 29 additions & 39 deletions code/modules/mob/living/carbon/xenomorph/Evolution.dm
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
if(!castepick) //Changed my mind
return

if(SEND_SIGNAL(src, COMSIG_XENO_TRY_EVOLVE, castepick) & COMPONENT_OVERRIDE_EVOLVE)
return // Message will be handled by component

var/datum/caste_datum/caste_datum = GLOB.xeno_datum_list[castepick]
if(caste_datum && caste_datum.minimum_evolve_time > ROUND_TIME)
to_chat(src, SPAN_WARNING("The Hive cannot support this caste yet! ([floor((caste_datum.minimum_evolve_time - ROUND_TIME) / 10)] seconds remaining)"))
Expand Down Expand Up @@ -248,31 +251,26 @@

return TRUE

// The queen de-evo, but on yourself. Only usable once
// The queen de-evo, but on yourself.
/mob/living/carbon/xenomorph/verb/Deevolve()
set name = "De-Evolve"
set desc = "De-evolve into a lesser form."
set category = "Alien"

if(!check_state())
return

if(is_ventcrawling)
to_chat(src, SPAN_XENOWARNING("You can't deevolve here."))
return

if(!isturf(loc))
to_chat(src, SPAN_XENOWARNING("You can't deevolve here."))
return

if(health < maxHealth)
to_chat(src, SPAN_XENOWARNING("We are too weak to deevolve, we must regain our health first."))
return

if(length(caste.deevolves_to) < 1)
to_chat(src, SPAN_XENOWARNING("We can't deevolve any further."))
return

if(lock_evolve)
if(banished)
to_chat(src, SPAN_WARNING("We are banished and cannot reach the hivemind."))
Expand All @@ -282,7 +280,6 @@


var/newcaste

if(length(caste.deevolves_to) == 1)
newcaste = caste.deevolves_to[1]
else if(length(caste.deevolves_to) > 1)
Expand All @@ -291,55 +288,33 @@
if(!newcaste)
return

var/confirm = tgui_alert(src, "Are you sure you want to de-evolve from [caste.caste_type] to [newcaste]?", "Deevolution", list("Yes", "No"))
var/confirm = tgui_alert(src, "Are you sure you want to de-evolve from [caste.caste_type] to [newcaste]? You won't be able to return to it for a time.", "Deevolution", list("Yes", "No"))
if(confirm != "Yes")
return

if(!check_state())
return

if(is_ventcrawling)
return

if(!isturf(loc))
return

if(health <= 0)
return

if(lock_evolve)
to_chat(src, SPAN_WARNING("You are banished and cannot reach the hivemind."))
return FALSE
return


SEND_SIGNAL(src, COMSIG_XENO_DEEVOLVE)

var/xeno_type
var/level_to_switch_to = get_vision_level()
switch(newcaste)
if("Larva")
xeno_type = /mob/living/carbon/xenomorph/larva
if(XENO_CASTE_RUNNER)
xeno_type = /mob/living/carbon/xenomorph/runner
if(XENO_CASTE_DRONE)
xeno_type = /mob/living/carbon/xenomorph/drone
if(XENO_CASTE_SENTINEL)
xeno_type = /mob/living/carbon/xenomorph/sentinel
if(XENO_CASTE_SPITTER)
xeno_type = /mob/living/carbon/xenomorph/spitter
if(XENO_CASTE_LURKER)
xeno_type = /mob/living/carbon/xenomorph/lurker
if(XENO_CASTE_WARRIOR)
xeno_type = /mob/living/carbon/xenomorph/warrior
if(XENO_CASTE_DEFENDER)
xeno_type = /mob/living/carbon/xenomorph/defender
if(XENO_CASTE_BURROWER)
xeno_type = /mob/living/carbon/xenomorph/burrower
var/obj/item/organ/xeno/organ = locate() in src
if(!isnull(organ))
qdel(organ)
var/mob/living/carbon/xenomorph/new_xeno = new xeno_type(get_turf(src), src)

new_xeno.built_structures = built_structures.Copy()
var/level_to_switch_to = get_vision_level()
var/xeno_type = GLOB.RoleAuthority.get_caste_by_text(newcaste)

built_structures = null
var/mob/living/carbon/xenomorph/new_xeno = new xeno_type(get_turf(src), src)

if(!istype(new_xeno))
//Something went horribly wrong!
Expand All @@ -348,7 +323,11 @@
qdel(new_xeno)
return

new_xeno.built_structures = built_structures.Copy()
built_structures = null

log_game("EVOLVE: [key_name(src)] de-evolved into [new_xeno].")

if(mind)
mind.transfer_to(new_xeno)
else
Expand All @@ -359,19 +338,30 @@
new_xeno.client.pixel_y = 0

//Regenerate the new mob's name now that our player is inside
if(newcaste == XENO_CASTE_LARVA)
var/mob/living/carbon/xenomorph/larva/new_larva = new_xeno
new_larva.larva_state = LARVA_STATE_NORMAL
new_larva.update_icons()
new_xeno.generate_name()
if(new_xeno.client)
new_xeno.set_lighting_alpha(level_to_switch_to)

// If the player has lost the Deevolve verb before, don't allow them to do it again
if(!(/mob/living/carbon/xenomorph/verb/Deevolve in verbs))
remove_verb(new_xeno, /mob/living/carbon/xenomorph/verb/Deevolve)

new_xeno.visible_message(SPAN_XENODANGER("A [new_xeno.caste.caste_type] emerges from the husk of \the [src]."), \
SPAN_XENODANGER("We regress into our previous form."))

transfer_observers_to(new_xeno)

if(GLOB.round_statistics && !new_xeno.statistic_exempt)
GLOB.round_statistics.track_new_participant(faction, -1) //so an evolved xeno doesn't count as two.
SSround_recording.recorder.stop_tracking(src)
SSround_recording.recorder.track_player(new_xeno)

src.transfer_observers_to(new_xeno)

qdel(src)
return

/mob/living/carbon/xenomorph/proc/can_evolve(castepick, potential_queens)
var/selected_caste = GLOB.xeno_datum_list[castepick]?.type
Expand Down
1 change: 1 addition & 0 deletions code/modules/mob/living/carbon/xenomorph/Xenomorph.dm
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
vis_contents += wound_icon_holder

///Handle transferring things from the old Xeno if we have one in the case of evolve, devolve etc.
AddComponent(/datum/component/deevolve_cooldown, old_xeno)
if(old_xeno)
src.nicknumber = old_xeno.nicknumber
src.life_kills_total = old_xeno.life_kills_total
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,35 @@
if(target_xeno.hivenumber != user_xeno.hivenumber)
to_chat(user_xeno, SPAN_XENOWARNING("[target_xeno] doesn't belong to your hive!"))
return

if(target_xeno.is_ventcrawling)
to_chat(user_xeno, SPAN_XENOWARNING("[target_xeno] can't be deevolved here."))
return

if(!isturf(target_xeno.loc))
to_chat(user_xeno, SPAN_XENOWARNING("[target_xeno] can't be deevolved here."))
return

if(target_xeno.health <= 0)
to_chat(user_xeno, SPAN_XENOWARNING("[target_xeno] is too weak to be deevolved."))
return

if(length(target_xeno.caste.deevolves_to) < 1)
to_chat(user_xeno, SPAN_XENOWARNING("[target_xeno] can't be deevolved."))
return

if(target_xeno.banished)
to_chat(user_xeno, SPAN_XENOWARNING("[target_xeno] is banished and can't be deevolved."))
return


var/newcaste

if(length(target_xeno.caste.deevolves_to) == 1)
newcaste = target_xeno.caste.deevolves_to[1]
else if(length(target_xeno.caste.deevolves_to) > 1)
newcaste = tgui_input_list(user_xeno, "Choose a caste you want to de-evolve [target_xeno] to.", "De-evolve", target_xeno.caste.deevolves_to, theme="hive_status")

if(!newcaste)
return

if(newcaste == "Larva")
if(newcaste == XENO_CASTE_LARVA)
to_chat(user_xeno, SPAN_XENOWARNING("You cannot deevolve xenomorphs to larva."))
return

if (user_xeno.observed_xeno != target_xeno)
if(user_xeno.observed_xeno != target_xeno)
return

var/confirm = tgui_alert(user_xeno, "Are you sure you want to deevolve [target_xeno] from [target_xeno.caste.caste_type] to [newcaste]?", "Deevolution", list("Yes", "No"))
Expand All @@ -63,33 +55,21 @@
to_chat(user_xeno, SPAN_XENOWARNING("You must provide a reason for deevolving [target_xeno]."))
return

if (!check_and_use_plasma_owner(plasma_cost))
if(!check_and_use_plasma_owner(plasma_cost))
return


to_chat(target_xeno, SPAN_XENOWARNING("The queen is deevolving you for the following reason: [reason]"))

var/xeno_type
var/level_to_switch_to = target_xeno.get_vision_level()
switch(newcaste)
if(XENO_CASTE_RUNNER)
xeno_type = /mob/living/carbon/xenomorph/runner
if(XENO_CASTE_DRONE)
xeno_type = /mob/living/carbon/xenomorph/drone
if(XENO_CASTE_SENTINEL)
xeno_type = /mob/living/carbon/xenomorph/sentinel
if(XENO_CASTE_SPITTER)
xeno_type = /mob/living/carbon/xenomorph/spitter
if(XENO_CASTE_LURKER)
xeno_type = /mob/living/carbon/xenomorph/lurker
if(XENO_CASTE_WARRIOR)
xeno_type = /mob/living/carbon/xenomorph/warrior
if(XENO_CASTE_DEFENDER)
xeno_type = /mob/living/carbon/xenomorph/defender
if(XENO_CASTE_BURROWER)
xeno_type = /mob/living/carbon/xenomorph/burrower
var/obj/item/organ/xeno/organ = locate() in src
SEND_SIGNAL(target_xeno, COMSIG_XENO_DEEVOLVE)

var/obj/item/organ/xeno/organ = locate() in target_xeno
if(!isnull(organ))
qdel(organ)

var/level_to_switch_to = target_xeno.get_vision_level()
var/xeno_type = GLOB.RoleAuthority.get_caste_by_text(newcaste)

//From there, the new xeno exists, hopefully
var/mob/living/carbon/xenomorph/new_xeno = new xeno_type(get_turf(target_xeno), target_xeno)

Expand All @@ -100,6 +80,9 @@
qdel(new_xeno)
return

new_xeno.built_structures = target_xeno.built_structures.Copy()
target_xeno.built_structures = null

if(target_xeno.mind)
target_xeno.mind.transfer_to(new_xeno)
else
Expand All @@ -113,7 +96,8 @@
new_xeno.generate_name()
if(new_xeno.client)
new_xeno.set_lighting_alpha(level_to_switch_to)
// If the player has self-deevolved before, don't allow them to do it again

// If the player has lost the Deevolve verb before, don't allow them to do it again
if(!(/mob/living/carbon/xenomorph/verb/Deevolve in target_xeno.verbs))
remove_verb(new_xeno, /mob/living/carbon/xenomorph/verb/Deevolve)

Expand All @@ -132,6 +116,7 @@
GLOB.round_statistics.track_new_participant(target_xeno.faction, -1) //so an evolved xeno doesn't count as two.
SSround_recording.recorder.stop_tracking(target_xeno)
SSround_recording.recorder.track_player(new_xeno)

qdel(target_xeno)
return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
speed = XENO_SPEED_TIER_6

evolves_to = list(XENO_CASTE_WARRIOR)
deevolves_to = list("Larva")
deevolves_to = list(XENO_CASTE_LARVA)
can_vent_crawl = 0

available_strains = list(/datum/xeno_strain/steel_crest)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/living/carbon/xenomorph/castes/Drone.dm
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

caste_desc = "A builder of hives. Only drones may evolve into Queens."
evolves_to = list(XENO_CASTE_QUEEN, XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD) //Add more here separated by commas
deevolves_to = list("Larva")
deevolves_to = list(XENO_CASTE_LARVA)
can_hold_facehuggers = 1
can_hold_eggs = CAN_HOLD_TWO_HANDS
acid_level = 1
Expand Down
Loading

0 comments on commit 813e85c

Please sign in to comment.