diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index 8ad4628a835..074b9519101 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -14,7 +14,6 @@ var/greet_text /// Type path for the associated job datum. var/role_job = /datum/job/abductor_agent - var/datum/action/cooldown/spell/summonitem/abductor/baton_return_spell /datum/antagonist/abductor/New() // lets get the loading started now, but don't block waiting for it @@ -80,16 +79,11 @@ objectives += team.objectives finalize_abductor() ADD_TRAIT(owner, TRAIT_ABDUCTOR_TRAINING, ABDUCTOR_ANTAGONIST) - baton_return_spell = new(owner) - baton_return_spell.Grant(owner.current) - if(HAS_TRAIT(owner, TRAIT_ABDUCTOR_SCIENTIST_TRAINING)) - baton_return_spell.Remove(owner.current) return ..() /datum/antagonist/abductor/on_removal() owner.special_role = null REMOVE_TRAIT(owner, TRAIT_ABDUCTOR_TRAINING, ABDUCTOR_ANTAGONIST) - baton_return_spell.Remove(owner.current) return ..() /datum/antagonist/abductor/greet() diff --git a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm index b3e4f89a7e0..109f27e8222 100644 --- a/code/modules/antagonists/abductor/equipment/abduction_outfits.dm +++ b/code/modules/antagonists/abductor/equipment/abduction_outfits.dm @@ -24,11 +24,19 @@ for(var/obj/item/abductor/gizmo/G in B.contents) console.AddGizmo(G) -/datum/outfit/abductor/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) - ..() - if(!visualsOnly) - link_to_console(H) +/datum/outfit/abductor/post_equip(mob/living/carbon/human/user, visualsOnly = FALSE) + . = ..() + if(visualsOnly) + return + if(!isnull(user.mind)) + link_to_console(user) + + var/obj/item/melee/baton/abductor/batong = locate() in user + if(!isnull(batong)) + var/datum/action/cooldown/spell/summonitem/abductor/ayy_summon = new(user.mind || user) + ayy_summon.mark_item(batong) + ayy_summon.Grant(user) /datum/outfit/abductor/agent name = "Abductor Agent" @@ -49,11 +57,11 @@ /obj/item/abductor/gizmo = 1 ) -/datum/outfit/abductor/scientist/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) - ..() - if(!visualsOnly) - var/obj/item/implant/abductor/beamplant = new /obj/item/implant/abductor(H) - beamplant.implant(H) +/datum/outfit/abductor/scientist/post_equip(mob/living/carbon/human/user, visualsOnly = FALSE) + . = ..() + if(!visualsOnly && !isnull(user.mind)) + var/obj/item/implant/abductor/beamplant = new /obj/item/implant/abductor(user) + beamplant.implant(user) /datum/outfit/abductor/scientist/onemanteam name = "Abductor Scientist (w/ agent gear)" diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index 0f4eba1f7f4..bcedf1073f2 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -423,6 +423,7 @@ spell_level++ cooldown_time = max(cooldown_time - cooldown_reduction_per_rank, 0.25 SECONDS) // 0 second CD starts to break things. + name = "[get_spell_title()][initial(name)]" build_all_button_icons(UPDATE_BUTTON_NAME) return TRUE @@ -443,13 +444,10 @@ else cooldown_time = max(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time)) + name = "[get_spell_title()][initial(name)]" build_all_button_icons(UPDATE_BUTTON_NAME) return TRUE -/datum/action/cooldown/spell/update_button_name(atom/movable/screen/movable/action_button/button, force) - name = "[get_spell_title()][initial(name)]" - return ..() - /// Gets the title of the spell based on its level. /datum/action/cooldown/spell/proc/get_spell_title() switch(spell_level) diff --git a/code/modules/spells/spell_types/self/summonitem.dm b/code/modules/spells/spell_types/self/summonitem.dm index 5074d6f1d19..62d6b078161 100644 --- a/code/modules/spells/spell_types/self/summonitem.dm +++ b/code/modules/spells/spell_types/self/summonitem.dm @@ -15,22 +15,40 @@ ///The obj marked for recall var/obj/marked_item +/datum/action/cooldown/spell/summonitem/New(Target, original) + . = ..() + AddComponent(/datum/component/action_item_overlay, item_callback = CALLBACK(src, PROC_REF(get_marked))) + +/datum/action/cooldown/spell/summonitem/Destroy() + if(!isnull(marked_item)) + unmark_item() + return ..() + +/// For use in callbacks to get the marked item +/datum/action/cooldown/spell/summonitem/proc/get_marked() + return marked_item + /datum/action/cooldown/spell/summonitem/is_valid_target(atom/cast_on) return isliving(cast_on) /// Set the passed object as our marked item /datum/action/cooldown/spell/summonitem/proc/mark_item(obj/to_mark) - name = "Recall [to_mark]" marked_item = to_mark RegisterSignal(marked_item, COMSIG_QDELETING, PROC_REF(on_marked_item_deleted)) + name = "Recall [marked_item]" + build_all_button_icons() + /// Unset our current marked item /datum/action/cooldown/spell/summonitem/proc/unmark_item() - name = initial(name) UnregisterSignal(marked_item, COMSIG_QDELETING) marked_item = null -/// Signal proc for COMSIG_QDELETING on our marked item, unmarks our item if it's deleted + if(!QDELING(src)) + name = initial(name) + build_all_button_icons() + +/// Signal proc for [COMSIG_QDELETING] on our marked item, unmarks our item if it's deleted /datum/action/cooldown/spell/summonitem/proc/on_marked_item_deleted(datum/source) SIGNAL_HANDLER @@ -50,6 +68,12 @@ try_recall_item(cast_on) +/// Checks if the passed item is a valid item that can be marked / linked to summon. +/datum/action/cooldown/spell/summonitem/proc/can_link_to(obj/item/potential_mark, mob/living/caster) + if(potential_mark.item_flags & ABSTRACT) + return FALSE + return TRUE + /// If we don't have a marked item, attempts to mark the caster's held item. /datum/action/cooldown/spell/summonitem/proc/try_link_item(mob/living/caster) var/obj/item/potential_mark = caster.get_active_held_item() @@ -61,7 +85,7 @@ return FALSE var/link_message = "" - if(potential_mark.item_flags & ABSTRACT) + if(!can_link_to(potential_mark, caster)) return FALSE if(SEND_SIGNAL(potential_mark, COMSIG_ITEM_MARK_RETRIEVAL, src, caster) & COMPONENT_BLOCK_MARK_RETRIEVAL) return FALSE @@ -156,18 +180,29 @@ /datum/action/cooldown/spell/summonitem/abductor name = "Baton Recall" - desc = "Activating this would activate your linked baton emergency teleport protocol and recall it back to your hand, Takes a long time for translocation crystals to be enriched after use. REMINDER: YOU NEED TO LINK YOUR BATON MANUALLY!" - button_icon = 'icons/obj/antags/abductor.dmi' - button_icon_state = "wonderprodStun" + desc = "Activating this will trigger your baton's emergency translocation protocol, \ + recalling it to your hand. Takes a long time for the translocation crystals to reset after use." + sound = 'sound/effects/phasein.ogg' + school = SCHOOL_UNSET cooldown_time = 3.5 MINUTES + spell_requirements = NONE invocation_type = INVOCATION_NONE -/datum/action/cooldown/spell/summonitem/abductor/try_link_item(mob/living/caster) - var/obj/item/potential_mark = caster.get_active_held_item() - if(!istype(potential_mark, /obj/item/melee/baton/abductor)) - to_chat(caster, span_warning("Object is unable to be marked, Ensure that the object you are trying to mark is a baton of our origin")) + antimagic_flags = MAGIC_RESISTANCE_MIND +/datum/action/cooldown/spell/summonitem/abductor/can_link_to(obj/item/potential_mark, mob/living/caster) + . = ..() + if(!.) + return . + + if(!istype(potential_mark, /obj/item/melee/baton/abductor)) + to_chat(caster, span_warning("[potential_mark] has no translocation crystals to link to!")) return FALSE - return ..() + + return TRUE + +/datum/action/cooldown/spell/summonitem/abductor/try_unlink_item(mob/living/caster) + to_chat(caster, span_warning("You can't unlink [marked_item]'s translocation crystals.")) + return FALSE diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index c20a8b2b610..a0ff0e6bb61 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -84,10 +84,12 @@ /// A trait source when adding traits through unit tests #define TRAIT_SOURCE_UNIT_TESTS "unit_tests" +// BEGIN_INCLUDE // SKYRAT EDIT START #include "~skyrat/opposing_force.dm" #include "~skyrat/automapper.dm" //SKYRAT EDIT END +#include "abductor_baton_spell.dm" #include "ablative_hud.dm" #include "achievements.dm" #include "anchored_mobs.dm" diff --git a/code/modules/unit_tests/abductor_baton_spell.dm b/code/modules/unit_tests/abductor_baton_spell.dm new file mode 100644 index 00000000000..83d514de2b2 --- /dev/null +++ b/code/modules/unit_tests/abductor_baton_spell.dm @@ -0,0 +1,19 @@ +/// Tests that abductors get their baton recall spell when being equipped +/datum/unit_test/abductor_baton_spell + +/datum/unit_test/abductor_baton_spell/Run() + // Test abductor agents get a linked "summon item" spell that marks their baton. + var/mob/living/carbon/human/ayy = allocate(/mob/living/carbon/human/consistent) + ayy.equipOutfit(/datum/outfit/abductor/agent) + + var/datum/action/cooldown/spell/summonitem/abductor/summon = locate() in ayy.actions + TEST_ASSERT_NOTNULL(summon, "Abductor agent does not have summon item spell.") + TEST_ASSERT(istype(summon.marked_item, /obj/item/melee/baton/abductor), "Abductor agent's summon item spell did not mark their baton.") + + // Also test abductor solo agents also get the spell. + var/mob/living/carbon/human/ayy_two = allocate(/mob/living/carbon/human/consistent) + ayy_two.equipOutfit(/datum/outfit/abductor/scientist/onemanteam) + + var/datum/action/cooldown/spell/summonitem/abductor/summon_two = locate() in ayy_two.actions + TEST_ASSERT_NOTNULL(summon_two, "Abductor solo agent does not have summon item spell.") + TEST_ASSERT(istype(summon_two.marked_item, /obj/item/melee/baton/abductor), "Abductor solo agent's summon item spell did not mark their baton.")