From 3691d5dddae6ce36840956be30acf0387ec75d4b Mon Sep 17 00:00:00 2001 From: Mykhailo Bykhovtsev Date: Sat, 13 Jul 2019 01:18:26 -0700 Subject: [PATCH] Diona detaching limbs (#6496) Adds ability for Gestalt to detach their limb nymphs and use them for scouting or whatever. Detached Nymph can switch back and forth between controlling Gestalt or nymph Detached nymph can merge back into Gestalt, restoring lost limb instantly. Makes initialization of Diona nymphs based on external organs it has, not a hardcoded number No longer Nymphs spawn on turf and move into Gestalt during initial setup. No longer removing internal limbs would cause spawn of nymph. Only external organs do that now. Timers: Adds new proc in timers that calls the callback and deletes timer afterwards. Used when you need to make active timer execute now. --- code/controllers/subsystems/timer.dm | 27 ++++ .../living/carbon/alien/diona/diona_powers.dm | 9 +- code/modules/mob/living/carbon/diona_base.dm | 55 ++++++++- .../mob/living/carbon/human/diona_gestalt.dm | 115 ++++++++++++++---- code/modules/organs/subtypes/diona.dm | 7 +- html/changelogs/Sindorman-dionascout.yml | 41 +++++++ 6 files changed, 217 insertions(+), 37 deletions(-) create mode 100644 html/changelogs/Sindorman-dionascout.yml diff --git a/code/controllers/subsystems/timer.dm b/code/controllers/subsystems/timer.dm index d973de097a6..377b9358f9a 100644 --- a/code/controllers/subsystems/timer.dm +++ b/code/controllers/subsystems/timer.dm @@ -409,18 +409,45 @@ var/datum/controller/subsystem/timer/SStimer /proc/deltimer(id) if (!id) return FALSE + if (id == TIMER_ID_NULL) CRASH("Tried to delete a null timerid. Use the TIMER_STOPPABLE flag.") + if (!istext(id)) if (istype(id, /datum/timedevent)) qdel(id) return TRUE + var/datum/timedevent/timer = SStimer.timer_id_dict["timerid[id]"] if (timer && !timer.spent) qdel(timer) return TRUE return FALSE +/proc/execute_and_deltimer(id) + if (!id) + return FALSE + + if (id == TIMER_ID_NULL) + CRASH("Tried to delete a null timerid. Use the TIMER_STOPPABLE flag.") + + var/datum/timedevent/timer = null + if (!istext(id) && istype(id, /datum/timedevent)) + timer = id + else + timer = SStimer.timer_id_dict["timerid[id]"] + + if(!timer) + return FALSE + + var/datum/callback/callBack = timer.callBack + + if (timer && !timer.spent) + callBack.Invoke() + timer.spent = TRUE + qdel(timer) + return TRUE + return FALSE #undef BUCKET_LEN #undef BUCKET_POS diff --git a/code/modules/mob/living/carbon/alien/diona/diona_powers.dm b/code/modules/mob/living/carbon/alien/diona/diona_powers.dm index 179c5f99de0..889f9dceffc 100644 --- a/code/modules/mob/living/carbon/alien/diona/diona_powers.dm +++ b/code/modules/mob/living/carbon/alien/diona/diona_powers.dm @@ -59,7 +59,7 @@ to_chat(src, "You feel your being twine with that of \the [H] as you merge with its biomass.") for(var/obj/O in src.contents) drop_from_inventory(O) - loc = H + src.forceMove(H) else to_chat(src, span("warning", "Something went wrong while trying to merge into [H], cancelling.")) return 0 @@ -144,10 +144,6 @@ else return 0 - - - - //Split allows a nymph to peel away from a gestalt and be a lone agent /mob/living/carbon/alien/diona/proc/split() set category = "Abilities" @@ -254,9 +250,6 @@ donor.adjustBruteLoss(4) src.visible_message("[src] sucks some blood from [donor.name]", "You extract a delicious mouthful of blood from [donor.name]!") - - - if (newDNA in sampled_DNA) to_chat(src, "You have already sampled the DNA of this creature before, you can learn nothing new. Move onto something else.") return diff --git a/code/modules/mob/living/carbon/diona_base.dm b/code/modules/mob/living/carbon/diona_base.dm index 018a751e792..cd0aaf4310e 100644 --- a/code/modules/mob/living/carbon/diona_base.dm +++ b/code/modules/mob/living/carbon/diona_base.dm @@ -239,7 +239,7 @@ var/list/diona_banned_languages = list( return FALSE // Continuation of the Diona regen proc, but for human specific actions. -/mob/living/carbon/human/diona_handle_regeneration(var/datum/dionastats/DS) +/mob/living/carbon/human/diona_handle_regeneration(var/datum/dionastats/DS, var/bypass = FALSE) ..() // We cancel with regening organ, as it's meant to stop all other regenerative @@ -280,7 +280,7 @@ var/list/diona_banned_languages = list( //Last up, growing brand new limbs and organs to replace those lost or removed. - if (life_tick % (LIFETICK_INTERVAL_LESS*8) == 0 && (DS.stored_energy > (0.5 * DS.max_energy))) + if ((life_tick % (LIFETICK_INTERVAL_LESS*8) == 0 && (DS.stored_energy > (0.5 * DS.max_energy))) || bypass ) //We will only replace ONE organ or limb each time this procs var/path for (var/i in species.has_limbs) @@ -308,13 +308,15 @@ var/list/diona_banned_languages = list( return DS.stored_energy -= REGROW_ENERGY_REQ adjustNutritionLoss(REGROW_FOOD_REQ) - playsound(src, 'sound/species/diona/gestalt_grow.ogg', 30, 1) visible_message("[src] begins to shift and quiver.", "You begin to shift and quiver, feeling a stirring within your trunk") DS.regening_organ = TRUE to_chat(src, "You are trying to regrow a lost limb, this is a long and complicated process that will take 10 minutes!") - addtimer(CALLBACK(src, .proc/diona_regen_callback, path), 10 MINUTES) + if(!bypass) + DS.regen_limb = addtimer(CALLBACK(src, .proc/diona_regen_callback, path), 10 MINUTES, TIMER_STOPPABLE) + else + diona_regen_callback(path, DS) return @@ -367,6 +369,9 @@ var/list/diona_banned_languages = list( //If we have less than six nymphs, we add one each proc if (topup_nymphs(1)) + if (DS.stored_energy < REGROW_ENERGY_REQ) + return + DS.stored_energy -= REGROW_ENERGY_REQ adjustNutritionLoss(REGROW_FOOD_REQ) to_chat(src, "You feel a stirring inside you as a new nymph is born within your trunk!") @@ -390,6 +395,7 @@ var/list/diona_banned_languages = list( update_dionastats() //Re-find the organs in case they were lost or regained updatehealth() + playsound(src, 'sound/species/diona/gestalt_grow.ogg', 30, 1) //MESSAGE FUNCTIONS /mob/living/carbon/proc/diona_handle_lightmessages(var/datum/dionastats/DS) @@ -515,6 +521,44 @@ var/list/diona_banned_languages = list( else to_chat(src, "You have forgotten the [L.name] language!") +/mob/living/carbon/alien/diona/proc/switch_to_gestalt() + set name = "Switch to Gestalt" + set desc = "Allows you to switch control back to your parent Gestalt." + set category = "Abilities" + + if(!gestalt) + to_chat(src, span("warning", "You have no Gestalt!")) + else if(gestalt.stat == DEAD) + to_chat(src, span("danger", "Your Gestlat is not responding! Something could have happened to it!")) + else + gestalt.key = key + +/mob/living/carbon/alien/diona/proc/merge_back_to_gestalt() + set name = "Merge to Gestalt" + set desc = "Allows you to merge back to your parent Gestalt." + set category = "Abilities" + + for(var/mob/living/carbon/human/H in view(src, 7)) + if(!H.is_diona()) + continue + var/mob/living/carbon/human/diona/C = H + if(C == gestalt) + C.nutrition += REGROW_FOOD_REQ + C.DS.stored_energy += REGROW_ENERGY_REQ + total_radiation += 5 + C.key = src.key + if(DS.regen_limb) + execute_and_deltimer(DS.regen_limb) + else + C.diona_handle_regeneration(C.DS, TRUE) + to_chat(C, span("notice", "Your lost nymph merged back.")) + C.add_nymph() + verbs -= /mob/living/carbon/alien/diona/proc/switch_to_gestalt + C.verbs -= /mob/living/carbon/human/proc/switch_to_nymph + C.DS.nym = null + qdel(src) + break + //DIONASTATS DEFINES //Dionastats is an instanced object that diona will each create and hold a reference to. @@ -541,7 +585,8 @@ var/list/diona_banned_languages = list( var/obj/item/organ/diona/nutrients/nutrient_organ = null //Organ var/LMS = 1 //Lightmessage state. Switching between states gives the user a message var/dionatype //1 = nymph, 2 = worker gestalt - + var/datum/weakref/nym + var/regen_limb /datum/dionastats/Destroy() light_organ = null //Nulling out these references to prevent GC errors diff --git a/code/modules/mob/living/carbon/human/diona_gestalt.dm b/code/modules/mob/living/carbon/human/diona_gestalt.dm index 7ef87a802a0..aaadea41bdf 100644 --- a/code/modules/mob/living/carbon/human/diona_gestalt.dm +++ b/code/modules/mob/living/carbon/human/diona_gestalt.dm @@ -7,9 +7,6 @@ #define COLD_DAMAGE_LEVEL_2 1.5 #define COLD_DAMAGE_LEVEL_3 3 - - -#define NUM_NYMPHS 6 /mob/living/carbon/human var/datum/dionastats/DS @@ -19,6 +16,7 @@ setup_dionastats() verbs += /mob/living/carbon/human/proc/check_light verbs += /mob/living/carbon/human/proc/diona_split_nymph + verbs += /mob/living/carbon/human/proc/diona_detach_nymph verbs += /mob/living/proc/devour spawn(10) @@ -43,31 +41,30 @@ topup_nymphs() -/mob/living/carbon/human/proc/topup_nymphs(var/max = 6) - var/i = 0 +/mob/living/carbon/human/proc/topup_nymphs() var/added = 0 - for(var/mob/living/carbon/alien/diona/D in src) - i++ - D.stat = CONSCIOUS - D.sync_languages(src) + var/list/exclude = list("groin", "l_hand", "r_hand", "l_foot", "r_foot") // becase these are supposed to be whole as their join parts + for(var/thing in organs_by_name) + if(thing in exclude) + continue - if (i < NUM_NYMPHS) - for (i;i < NUM_NYMPHS;i++) - add_nymph() - added++ - if (added >= max) - return added + add_nymph() + added += 1 return added /mob/living/carbon/human/proc/add_nymph() - var/turf/T = get_turf(src) - var/mob/living/carbon/alien/diona/M = new /mob/living/carbon/alien/diona(T) + var/count = 0 + for(var/mob/living/carbon/alien/nymph in contents) + count += 1 + if(count >= 6) + return + var/mob/living/carbon/alien/diona/M = new /mob/living/carbon/alien/diona(src) M.gestalt = src M.stat = CONSCIOUS M.update_verbs() - spawn(1) - M.forceMove(src) + M.sync_languages(src) + return //Environmental Functions //================================ @@ -210,6 +207,84 @@ verbs.Remove(/mob/living/carbon/human/proc/gestalt_set_name) +/mob/living/carbon/human/proc/diona_detach_nymph() + set name = "Detach nymph" + set desc = "Allows you to detach specific nymph, and control it." + set category = "Abilities" + + if(nutrition <= 150) + to_chat(src, span("warning", "You lack nutrition to perform this action!")) + return + if(DS.stored_energy <= 60) + to_chat(src, span("warning", "You lack energy to perform this action!")) + return + // Choose our limb to detach + var/list/exclude = organs_by_name - list("groin", "chest", "l_arm", "r_arm", "l_leg", "r_leg") + var/choice = input(src, "Choose a limb to detach?", "Limb detach") as null|anything in exclude + if(!choice) + return + var/obj/item/organ/external/O = organs_by_name[choice] + if(!O || O.is_stump()) + to_chat(src, span("warning", "Cannot detach that!")) + return + + // Get rid of our limb and replace with stump + var/obj/item/organ/external/stump/stump = new (src, 0, O) + O.removed(null, TRUE) + organs |= stump + stump.update_damages() + O.post_droplimb(src) + // If we got parent organ - drop it too + if(O.parent_organ && O.parent_organ != "chest") + var/obj/item/organ/external/parent = organs_by_name[O.parent_organ] + var/obj/item/organ/external/stump/parent_stump = new (src, 0, parent) + parent.removed(null, TRUE) + organs |= parent_stump + parent_stump.update_damages() + parent.post_droplimb(src) + qdel(parent) + to_chat(src, span("notice", "You detach [O.name] nymph from your body.")) + qdel(O) + + var/mob/living/carbon/alien/diona/M = locate(/mob/living/carbon/alien/diona) in contents + if(!M) + return + M.forceMove(get_turf(src)) + + // Switch control to nymph + M.key = src.key + teleop = TRUE + M.teleop = TRUE + src.key = "@[M.key]" + src.ajourn = 0 + DS.nym = WEAKREF(M) + M.gestalt = src + M.verbs += /mob/living/carbon/alien/diona/proc/merge_back_to_gestalt + M.verbs += /mob/living/carbon/alien/diona/proc/switch_to_gestalt + verbs += /mob/living/carbon/human/proc/switch_to_nymph + + update_dionastats() //Re-find the organs in case they were lost or regained + nutrition -= 150 + DS.stored_energy -= 60 + diona_handle_regeneration(DS) + playsound(src, 'sound/species/diona/gestalt_grow.ogg', 30, 1) + +/mob/living/carbon/human/proc/switch_to_nymph() + set name = "Switch to Nymph" + set desc = "Allows you to switch control back to your detached Nymph." + set category = "Abilities" + + var/mob/living/carbon/alien/diona/nymph = DS.nym.resolve() + + if(!nymph) + to_chat(src, span("warning", "You have no nymph!")) + return + else if(nymph.stat == DEAD) + to_chat(src, span("danger", "Your nymph is not responding! Something could have happened to it!")) + return + else + nymph.key = key + /mob/living/carbon/human/proc/diona_split_into_nymphs() var/turf/T = get_turf(src) var/mob/living/carbon/alien/diona/bestNymph = null @@ -218,8 +293,6 @@ var/nymphs_to_kill_off = 0 - - //We iterate through all the nymphs and find which one is healthiest and not controlled //The gestalt's player will control that nymph diff --git a/code/modules/organs/subtypes/diona.dm b/code/modules/organs/subtypes/diona.dm index 01413c0016c..83fbe21383a 100644 --- a/code/modules/organs/subtypes/diona.dm +++ b/code/modules/organs/subtypes/diona.dm @@ -25,8 +25,6 @@ name = "tendril" cannot_break = 1 - - /obj/item/organ/external/chest/diona name = "core trunk" limb_name = "chest" @@ -215,7 +213,10 @@ ..() if(!istype(H) || !H.organs || !H.organs.len) H.death() - if(prob(50) && spawn_diona_nymph(get_turf(src))) + +/obj/item/organ/external/diona/removed(var/mob/living/user) + ..() + if(spawn_diona_nymph(get_turf(src))) qdel(src) // These are different to the standard diona organs as they have a purpose in other diff --git a/html/changelogs/Sindorman-dionascout.yml b/html/changelogs/Sindorman-dionascout.yml new file mode 100644 index 00000000000..470410e771a --- /dev/null +++ b/html/changelogs/Sindorman-dionascout.yml @@ -0,0 +1,41 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: PoZe + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Diona Gestalt can now detach limbs into a nymph and use it for scouting or whaterver they want. Detaching makes gestalt loose that specific limb. Both Gestalt and nymph get special ability to switch between bodies while detached. Nymph can merge back into gestalt and replace lost limb right away or if limb already regenerated by itself, it will replenish energy and nutrition spent on detaching nymph."