Files
S.P.L.U.R.T-Station-13/modular_splurt/code/datums/traits/trait_actions.dm
BongaTheProto f538e07ca0 Merge pull request #707 from LeDrascol/quirk-fixes-werewolf
Werewolf quirk improvements
2023-02-07 06:56:15 -05:00

960 lines
34 KiB
Plaintext

#define BLOODFLEDGE_DRAIN_NUM 50
#define BLOODFLEDGE_COOLDOWN_BITE 60
//
// Quirk: Hypnotic Gaze
//
/datum/action/innate/Hypnotize
name = "Hypnotize"
desc = "Stare deeply into someone's eyes, drawing them into a hypnotic slumber."
button_icon_state = "Hypno_eye"
icon_icon = 'modular_splurt/icons/mob/actions/lewd_actions/lewd_icons.dmi'
background_icon_state = "bg_alien"
/datum/action/innate/Hypnotize/Activate()
// Define action owner
var/mob/living/carbon/human/action_owner = owner
// Define target
var/grab_target = action_owner.pulling
// Check for target
if(!grab_target)
// Warn the user, then return
to_chat(action_owner, span_warning("You you need to grab someone first!"))
return
// Check for cyborg
if(iscyborg(grab_target))
// Warn the user, then return
to_chat(action_owner, span_warning("You can't hypnotize a cyborg!"))
return
// Check for alien
// Taken from eyedropper check
/*
if(isalien(grab_target))
// Warn the user, then return
to_chat(action_owner, span_warning("[grab_target] doesn\'t seem to have any eyes!"))
return
*/
// Check for carbon human target
if(!ishuman(grab_target))
// Warn the user, then return
to_chat(action_owner, span_warning("That's not a valid creature!"))
return
// Check if target is alive
if(!isliving(grab_target))
// Warn the user, then return
to_chat(action_owner, span_warning("You can't hypnotize the dead!"))
return
// Check for aggressive grab
if(action_owner.grab_state < GRAB_AGGRESSIVE)
// Warn the user, then return
to_chat(action_owner, span_warning("You need a stronger grip before trying this!"))
return
// Define target
var/mob/living/carbon/human/action_target = grab_target
// Check if target has a mind
if(!action_target.mind)
// Warn the user, then return
to_chat(action_owner, span_warning("[grab_target] doesn\'t have a compatible mind!"))
return
/* Unused: Replaced by get_eye_protection
// Check if target's eyes are obscured
// ... by headwear
if((action_target.head && action_target.head.flags_cover & HEADCOVERSEYES))
// Warn the user, then return
to_chat(action_owner, span_warning("[action_target]'s eyes are obscured by [action_target.head]."))
return
// ... by a mask
else if((action_target.wear_mask && action_target.wear_mask.flags_cover & MASKCOVERSEYES))
// Warn the user, then return
to_chat(action_owner, span_warning("[action_target]'s eyes are obscured by [action_target.wear_mask]."))
return
// ... by glasses
else if((action_target.glasses && action_target.glasses.flags_cover & GLASSESCOVERSEYES))
// Warn the user, then return
to_chat(action_owner, span_warning("[action_target]'s eyes are obscured by [action_target.glasses]."))
return
*/
// Check if target has eye protection
if(action_target.get_eye_protection())
// Warn the user, then return
to_chat(action_owner, span_warning("You have difficulty focusing on [action_target]'s eyes due to some form of protection, and are left unable to hypnotize them."))
to_chat(action_target, span_notice("[action_owner] stares intensely at you, but stops after a moment."))
return
// Check if target is blind
if(HAS_TRAIT(action_target, TRAIT_BLIND))
// Warn the user, then return
to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes, but see nothing but emptiness."))
return
// Check for anti-magic
// This does not include TRAIT_HOLY
if(action_target.anti_magic_check())
// Warn the users, then return
to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes. They stare back at you as if nothing had happened."))
to_chat(action_target, span_notice("[action_owner] stares intensely into your eyes for a moment. You sense nothing out of the ordinary from them."))
return
// Check client pref for hypno
if(action_target.client?.prefs.cit_toggles & NEVER_HYPNO)
// Warn the users, then return
to_chat(action_owner, span_warning("You sense that [action_target] would rather not be hypnotized, and decide to respect their wishes."))
to_chat(action_target, span_notice("[action_owner] stares into your eyes with a strange conviction, but turns away after a moment."))
return
// Check for mindshield implant
if(HAS_TRAIT(action_target, TRAIT_MINDSHIELD))
// Warn the users, then return
to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes, but hear a faint buzzing from [action_target.p_their()] head. It seems something is interfering."))
to_chat(action_target, span_notice("[action_owner] stares intensely into your eyes for a moment, before a buzzing sound emits from your head."))
return
// Check for sleep immunity
// This is required for SetSleeping to trigger
if(HAS_TRAIT(action_target, TRAIT_SLEEPIMMUNE))
// Warn the users, then return
to_chat(action_owner, span_warning("You stare deeply into [action_target]'s eyes, and see nothing but unrelenting energy. You won't be able to subdue [action_target.p_them()] in this state!"))
to_chat(action_target, span_notice("[action_owner] stares intensely into your eyes, but sees something unusual about you..."))
return
// Check for sleep
if(action_target.IsSleeping())
// Warn the user, then return
to_chat(action_owner, span_warning("You can't hypnotize [action_target] whilst [action_target.p_theyre()] asleep!"))
return
// Check for combat mode
if(SEND_SIGNAL(action_target, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE))
// Warn the users, then return
to_chat(action_owner, span_warning("[action_target] is acting too defensively! You'll need [action_target.p_them()] to lower [action_target.p_their()] guard first!"))
to_chat(action_target, span_notice("[action_owner] tries to stare into your eyes, but can't get a read on you."))
return
// Display chat messages
to_chat(action_owner, span_notice("You stare deeply into [action_target]'s eyes..."))
to_chat(action_target, span_warning("[action_owner] stares intensely into your eyes..."))
// Try to perform action timer
if(!do_mob(action_owner, action_target, 5 SECONDS))
// Action timer was interrupted
// Warn the user, then return
to_chat(action_owner, span_warning("You lose concentration on [action_target], and fail to hypnotize [action_target.p_them()]!"))
to_chat(action_target, span_notice("[action_owner]'s gaze is broken prematurely, freeing you from any potential effects."))
return
// Define blank response
var/input_consent
// Check for non-consensual setting
if(action_target.client?.prefs.nonconpref != "Yes")
// Non-consensual is NOT enabled
// Prompt target for consent response
input_consent = alert(action_target, "Will you fall into a hypnotic stupor? This will allow [action_owner] to issue hypnotic suggestions.", "Hypnosis", "Yes", "No")
// When consent is denied
if(input_consent == "No")
// Warn the users, then return
to_chat(action_owner, span_warning("[action_target]'s attention breaks, despite the attempt to hypnotize [action_target.p_them()]! [action_target.p_they()] clearly don't want this!"))
to_chat(action_target, span_notice("Your concentration breaks as you realize you have no interest in following [action_owner]'s words!"))
return
// Display local message
action_target.visible_message(span_warning("[action_target] falls into a deep slumber!"), span_danger("Your eyelids gently shut as you fall into a deep slumber. All you can hear is [action_owner]'s voice as you commit to following all of their suggestions."))
// Set sleeping
action_target.SetSleeping(1200)
// Set drowsiness
action_target.drowsyness = max(action_target.drowsyness, 40)
// Prompt action owner for response
var/input_suggestion = input("What would you like to suggest [action_target] do? Leave blank to release [action_target.p_them()] instead.", "Hypnotic suggestion", null, null)
// Check if input text exists
if(!input_suggestion)
// Alert user of no input
to_chat(action_owner, "You decide not to give [action_target] a suggestion.")
// Remove sleep, then return
action_target.SetSleeping(0)
return
// Sanitize input text
input_suggestion = sanitize(input_suggestion)
// Display message to users
to_chat(action_owner, "You whisper your suggestion in a smooth calming voice to [action_target]")
to_chat(action_target, span_hypnophrase("...[input_suggestion]..."))
// Play a sound effect
playsound(action_target, 'sound/magic/domain.ogg', 20, 1)
// Display local message
action_target.visible_message(span_warning("[action_target] wakes up from their deep slumber!"), span_danger("Your eyelids gently open as you see [action_owner]'s face staring back at you."))
// Remove sleep, then return
action_target.SetSleeping(0)
return
//
// Quirk: Hydra Heads
//
/datum/action/innate/hydra
name = "Switch head"
desc = "Switch between each of the heads on your body."
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "art_summon"
/datum/action/innate/hydrareset
name = "Reset speech"
desc = "Go back to speaking as a whole."
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "art_summon"
/datum/action/innate/hydrareset/Activate()
var/mob/living/carbon/human/hydra = owner
hydra.real_name = hydra.name_archive
hydra.visible_message(span_notice("[hydra.name] pushes all three heads forwards; they seem to be talking as a collective."), \
span_notice("You are now talking as [hydra.name_archive]!"), ignored_mobs=owner)
/datum/action/innate/hydra/Activate() //I hate this but its needed
var/mob/living/carbon/human/hydra = owner
var/list/names = splittext(hydra.name_archive,"-")
var/selhead = input("Who would you like to speak as?","Heads:") in names
hydra.real_name = selhead
hydra.visible_message(span_notice("[hydra.name] pulls the rest of their heads back; and puts [selhead]'s forward."), \
span_notice("You are now talking as [selhead]!"), ignored_mobs=owner)
//
// Quirk: Bloodsucker Fledgling / Vampire
//
// Basic action preset
/datum/action/bloodfledge
name = "Broken Bloodfledge Ability"
desc = "You shouldn't be seeing this!"
button_icon_state = "power_torpor"
background_icon_state = "vamp_power_off"
buttontooltipstyle = "cult"
icon_icon = 'icons/mob/actions/bloodsucker.dmi'
button_icon = 'icons/mob/actions/bloodsucker.dmi'
// Action: Bite
/datum/action/bloodfledge/bite
name = "Fledgling Bite"
desc = "Sink your vampiric fangs into the person you are grabbing, and attempt to drink their blood."
button_icon_state = "power_feed"
var/drain_cooldown = 0
/datum/action/bloodfledge/bite/Trigger()
. = ..()
// Check for carbon owner
if(!iscarbon(owner))
return
// Define action owner
var/mob/living/carbon/action_owner = owner
// Check for cooldown
if(drain_cooldown >= world.time)
// Warn the user, then return
to_chat(action_owner, span_notice("That ability isn't ready yet."))
return
// Check for any grabbed target
if(!action_owner.pulling)
// Warn the user, then return
to_chat(action_owner, span_warning("You need a victim first!"))
return
// Limit maximum nutrition
if(action_owner.nutrition >= NUTRITION_LEVEL_FAT)
// Warn the user, then return
to_chat(action_owner, span_notice("You are too full to drain any more."))
return
// Limit maximum potential nutrition
if(action_owner.nutrition + BLOODFLEDGE_DRAIN_NUM >= NUTRITION_LEVEL_FAT)
// Warn the user, then return
to_chat(action_owner, span_notice("You would become too full by draining any more blood."))
return
// Check for muzzle
if(action_owner.is_muzzled())
// Warn the user, then return
to_chat(action_owner, span_notice("You can't bite things while muzzled!"))
return
// Define pulled target
var/pull_target = action_owner.pulling
// Define bite target
var/mob/living/carbon/bite_target
// Check if the target is carbon
if(iscarbon(pull_target))
// Set the bite target
bite_target = pull_target
// Or cocooned carbon
else if(istype(pull_target,/obj/structure/arachnid/cocoon))
// Define if cocoon has a valid target
// This cannot use pull_target
var/possible_cocoon_target = locate(/mob/living/carbon) in action_owner.pulling.contents
// Check defined cocoon target
if(possible_cocoon_target)
// Set the bite target
bite_target = possible_cocoon_target
// Or a blood tomato
else if(istype(pull_target,/obj/item/reagent_containers/food/snacks/grown/tomato/blood))
// Warn the user, then return
to_chat(action_owner, span_danger("You plunge your fangs into [pull_target]! It's not very nutritious."))
return
// This doesn't actually interact with the item
// Or none of the above
else
// Warn the user, then return
to_chat(action_owner, span_warning("You can't drain blood from [pull_target]!"))
return
// Check for anti-magic
if(bite_target.anti_magic_check(FALSE, TRUE, FALSE, 0))
// Warn the user and target, then return
to_chat(bite_target, span_warning("[action_owner] tries to bite you, but stops before touching you!"))
to_chat(action_owner, span_warning("[bite_target] is blessed! You stop just in time to avoid catching fire."))
return
// Check for garlic necklace or garlic in the bloodstream
if(!blood_sucking_checks(bite_target, TRUE, TRUE))
// Warn the user and target, then return
to_chat(bite_target, span_warning("[action_owner] tries to bite you, but is warded off by your Allium Sativum!"))
to_chat(action_owner, span_warning("You sense that [bite_target] is protected by Allium Sativum, and refrain from biting them."))
return
// Define bite target's blood volume
var/target_blood_volume = bite_target.blood_volume
// Check for sufficient blood volume
if(!target_blood_volume)
// Warn the user, then return
to_chat(action_owner, span_warning("There's not enough blood in [bite_target]!"))
return
// Check if total blood would become too low
if((target_blood_volume - BLOODFLEDGE_DRAIN_NUM) <= BLOOD_VOLUME_OKAY)
// Check for aggressive grab
if(action_owner.grab_state < GRAB_AGGRESSIVE)
// Warn the user, then return
to_chat(action_owner, span_warning("You sense that [bite_target] is running low on blood. You'll need a tighter grip on [bite_target.p_them()] to continue."))
return
// Check for pacifist
if(HAS_TRAIT(action_owner, TRAIT_PACIFISM))
// Warn the user, then return
to_chat(action_owner, span_warning("You can't drain any more blood from [bite_target] without hurting [bite_target.p_them()]!"))
return
// Set cooldown and action times
var/time_cooldown = BLOODFLEDGE_COOLDOWN_BITE
var/time_interact = 30
// Check for voracious
if(HAS_TRAIT(action_owner, TRAIT_VORACIOUS))
// Make times twice as fast
time_cooldown *= 0.5
time_interact*= 0.5
// Set cooldown
drain_cooldown = world.time + time_cooldown
// Display local chat message
action_owner.visible_message(span_danger("[action_owner] begins to bite down on [bite_target]'s neck!"))
// Warn bite target
to_chat(bite_target, span_userdanger("[action_owner] has bitten your neck, and is trying to drain your blood!"))
// Play a bite sound effect
playsound(action_owner, 'sound/weapons/bite.ogg', 30, 1, -2)
// Try to perform action timer
if(!do_after(action_owner, time_interact, target = bite_target))
// When failing
// Display a local chat message
action_owner.visible_message(span_danger("[action_owner]'s fangs are prematurely torn from [bite_target]'s neck, spilling [bite_target.p_their()] blood!"))
// Bite target "drops" the blood
// This creates large blood splatter
bite_target.bleed(BLOODFLEDGE_DRAIN_NUM, FALSE)
// Play splatter sound
playsound(get_turf(target), 'sound/effects/splat.ogg', 40, 1)
// Check for masochism
if(!HAS_TRAIT(bite_target, TRAIT_MASO))
// Force bite_target to play the scream emote
bite_target.emote("scream")
// Log the biting action failure
log_combat(action_owner,bite_target,"bloodfledge bitten (interrupted)")
// Return
return
// Check if bite target species has blood
if(NOBLOOD in bite_target.dna.species.species_traits)
// Warn the user and target, then return
to_chat(bite_target, span_warning("[action_owner] tried to drain you, but didn't find any blood!"))
to_chat(action_owner, span_warning("[bite_target] doesn't have any blood to drink!"))
return
// Create blood splatter
bite_target.add_splatter_floor(get_turf(bite_target), TRUE)
// Checks for exotic species blood below
// Variable for species with non-blood blood volumes
var/blood_valid = TRUE
// Variable for gaining blood volume
var/blood_transfer = FALSE
// Name of blood volume to be taken
// Action owner assumes blood until after drinking
var/blood_name = "blood"
// Check bite target for synth blood
if(bite_target.mob_biotypes & MOB_ROBOTIC)
// Mark blood as invalid
blood_valid = FALSE
// Set blood type name
blood_name = "coolant"
// Check if the action owner is also a synth
if (action_owner.mob_biotypes & MOB_ROBOTIC)
// Allow gaining blood from this
blood_transfer = TRUE
// Action owner is not a synth
else
// Warn the user
to_chat(action_owner, span_warning("That didn't taste like blood at all..."))
// Add disgust
action_owner.adjust_disgust(2)
// Cause negative mood
SEND_SIGNAL(action_owner, COMSIG_ADD_MOOD_EVENT, "bloodfledge_drank_synth", /datum/mood_event/drankblood_synth)
// Check if bite target is a slime
if (isslimeperson(bite_target))
// Mark blood as invalid
blood_valid = FALSE
// Set blood type name
blood_name = "slime"
// Check if the action owner is also a slime
if(isslimeperson(action_owner))
// Allow gaining blood from this
blood_transfer = TRUE
// Action owner is not a slime
else
// Warn the user
to_chat(action_owner, span_warning("You feel a sloshing presence inside you, but it dies out after a few moments."))
// Add disgust
action_owner.adjust_disgust(2)
// Cause negative mood
SEND_SIGNAL(action_owner, COMSIG_ADD_MOOD_EVENT, "bloodfledge_drank_slime", /datum/mood_event/drankblood_slime)
// End of species blood checks
// Define user's remaining capacity to absorb blood
var/blood_volume_difference = BLOOD_VOLUME_MAXIMUM - action_owner.blood_volume
var/drained_blood = min(target_blood_volume, BLOODFLEDGE_DRAIN_NUM, blood_volume_difference)
// Remove blood from bite target
bite_target.blood_volume = clamp(target_blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM)
// Perform a blood transfer
// This is done to transfer compatible diseases
// Grants nothing, unless blood transfer variable is set
bite_target.transfer_blood_to(action_owner, (blood_transfer ? drained_blood : 0), TRUE)
// Check if action owner received valid (nourishing) blood
if(blood_valid)
// Add blood reagent to the user
action_owner.reagents.add_reagent(/datum/reagent/blood/, drained_blood)
// Alert the bite target and local user of success
// Yes, this is AFTER the message for non-valid blood
to_chat(bite_target, span_danger("[action_owner] has taken some of your [blood_name]!"))
to_chat(action_owner, span_notice("You've drained some of [bite_target]'s [blood_name]!"))
// Alert the action holder if blood volume limit was exceeded
if(blood_transfer && (action_owner.blood_volume >= BLOOD_VOLUME_MAXIMUM))
to_chat(action_owner, span_warning("You body fails to absorb any more [blood_name]. The remainder has been lost."))
// Play a heartbeat sound effect
// This was changed to match bloodsucker
playsound(action_owner, 'sound/effects/singlebeat.ogg', 30, 1, -2)
// Log the biting action success
log_combat(action_owner,bite_target,"bloodfledge bitten (successfully), transferring [blood_name]")
// Mood events
// Check if bite target is dead or undead
if((bite_target.stat >= DEAD) || (bite_target.mob_biotypes & MOB_UNDEAD))
// Warn the user
to_chat(action_owner, span_warning("The rotten [blood_name] tasted foul."))
// Add disgust
action_owner.adjust_disgust(2)
// Cause negative mood
SEND_SIGNAL(action_owner, COMSIG_ADD_MOOD_EVENT, "bloodfledge_drank_dead", /datum/mood_event/drankblood_dead)
// Check if bite target's blood has been depleted
if(!bite_target.blood_volume)
// Warn the user
to_chat(action_owner, span_warning("You've depleted [bite_target]'s [blood_name] supply!"))
// Cause negative mood
SEND_SIGNAL(action_owner, COMSIG_ADD_MOOD_EVENT, "bloodfledge_drank_killed", /datum/mood_event/drankkilled)
// Check if bite target has cursed blood
if(HAS_TRAIT(bite_target, TRAIT_CURSED_BLOOD))
// Check action owner for cursed blood
var/owner_cursed = HAS_TRAIT(action_owner, TRAIT_CURSED_BLOOD)
// Set chat message based on action owner's trait status
var/warn_message = (owner_cursed ? "You taste the unholy touch of a familiar curse in [bite_target]\'s blood." : "You experience a sensation of intense dread just after drinking from [bite_target]. Something about their blood feels... wrong.")
// Alert user in chat
to_chat(action_owner, span_notice(warn_message))
// Set mood type based on curse status
var/mood_type = (owner_cursed ? /datum/mood_event/drank_cursed_good : /datum/mood_event/drank_cursed_bad)
// Cause mood event
SEND_SIGNAL(action_owner, COMSIG_ADD_MOOD_EVENT, "bloodfledge_drank_cursed_blood", mood_type)
// Action: Revive
/datum/action/bloodfledge/revive
name = "Fledgling Revive"
desc = "Expend all of your remaining energy to escape death."
button_icon_state = "power_strength"
/datum/action/bloodfledge/revive/Trigger()
. = ..()
// Define mob
var/mob/living/carbon/human/action_owner = owner
// Early check for being dead
// Users are most likely to click this while alive
if(action_owner.stat != DEAD)
// Warn user in chat
to_chat(action_owner, "You can't use this ability while alive!")
// Return
return
// Define failure message
var/revive_failed
// Condition: Mob isn't in a closed coffin
if(!istype(action_owner.loc, /obj/structure/closet/crate/coffin))
revive_failed += "\n- You need to be in a closed coffin!"
// Condition: Insufficient nutrition (blood)
if(action_owner.nutrition <= NUTRITION_LEVEL_STARVING)
revive_failed += "\n- You don't have enough blood left!"
// Condition: Can be revived
// This is used by revive(), and must be checked here to prevent false feedback
if(!action_owner.can_be_revived())
revive_failed += "\n- Your body is too weak to sustain life!"
// Condition: Damage limit, brute
if(action_owner.getBruteLoss() >= MAX_REVIVE_BRUTE_DAMAGE)
revive_failed += "\n- Your body is too battered!"
// Condition: Damage limit, burn
if(action_owner.getFireLoss() >= MAX_REVIVE_FIRE_DAMAGE)
revive_failed += "\n- Your body is too badly burned!"
// Condition: Suicide
if(action_owner.suiciding)
revive_failed += "\n- You chose this path."
// Condition: No revivals
if(HAS_TRAIT(action_owner, TRAIT_NOCLONE))
revive_failed += "\n- You only had one chance."
// Condition: Demonic contract
if(action_owner.hellbound)
revive_failed += "\n- The soul pact must be honored."
// Check for failure
if(revive_failed)
// Set combined message
revive_failed = span_warning("You can't revive right now because: [revive_failed]")
// Alert user in chat of failure
to_chat(action_owner, revive_failed)
// Return
return
// Define time dead
// Used for revive policy
var/time_dead = world.time - action_owner.timeofdeath
// Revive the action owner
action_owner.revive()
// Alert the user in chat of success
action_owner.visible_message(span_notice("An ominous energy radiates from the [action_owner.loc]..."), span_warning("You've expended all remaining blood to bring your body back to life!"))
// Play a haunted sound effect
playsound(action_owner, 'sound/hallucinations/growl1.ogg', 30, 1, -2)
// Remove all nutrition (blood)
action_owner.set_nutrition(0)
// Apply daze effect
action_owner.Daze(20)
// Define time limit for revival
// Determines memory loss, using defib time and policies
var/revive_time_limit = CONFIG_GET(number/defib_cmd_time_limit) * 10
// Define revive time threshold
// Late causes memory loss, according to policy
var/time_late = revive_time_limit && (time_dead > revive_time_limit)
// Define policy to use
var/list/policies = CONFIG_GET(keyed_list/policy)
var/time_policy = time_late? policies[POLICYCONFIG_ON_DEFIB_LATE] : policies[POLICYCONFIG_ON_DEFIB_INTACT]
// Check if policy exists
if(time_policy)
// Alert user in chat of policy
to_chat(action_owner, time_policy)
// Log the revival and effective policy
action_owner.log_message("revived using a vampire quirk ability after being dead for [time_dead] deciseconds. Considered [time_late? "late" : "memory-intact"] revival under configured policy limits.", LOG_GAME)
//
// Quirk: Werewolf
//
/datum/action/cooldown/werewolf
name = "Werewolf Ability"
desc = "Do something related to werewolves."
icon_icon = 'modular_splurt/icons/mob/actions/misc_actions.dmi'
button_icon_state = "Transform"
check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS | AB_CHECK_ALIVE
cooldown_time = 5 SECONDS
transparent_when_unavailable = TRUE
/datum/action/cooldown/werewolf/transform
name = "Toggle Werewolf Form"
desc = "Transform in or out of your wolf form."
var/transformed = FALSE
var/species_changed = FALSE
var/werewolf_gender = "Lycan"
var/list/old_features
/datum/action/cooldown/werewolf/transform/Grant()
. = ..()
// Define carbon owner
var/mob/living/carbon/action_owner_carbon = owner
// Define parent quirk
var/datum/quirk/werewolf/quirk_data = locate() in action_owner_carbon.roundstart_quirks
// Check if data was copied
if(!quirk_data)
// Log error and return
log_game("Failed to get species data for werewolf action!")
return
// Define stored features
old_features = quirk_data.old_features.Copy()
// Define action owner
var/mob/living/carbon/human/action_owner = owner
// Set species gendered name
switch(action_owner.gender)
if(MALE)
werewolf_gender = "Wer"
if(FEMALE)
werewolf_gender = "Wīf"
if(PLURAL)
werewolf_gender = "Hie"
if(NEUTER)
werewolf_gender = "Þing"
/datum/action/cooldown/werewolf/transform/Trigger()
. = ..()
// Check if unavailable
// Checks the parent function's return value
if(!.)
// Messages will not display here
return FALSE
// Define action owner
var/mob/living/carbon/human/action_owner = owner
// Check for restraints
if(!CHECK_MOBILITY(action_owner, MOBILITY_USE))
// Warn user, then return
action_owner.visible_message(span_warning("You cannot transform while restrained!"))
return
// Define citadel organs
var/obj/item/organ/genital/penis/organ_penis = action_owner.getorganslot(ORGAN_SLOT_PENIS)
var/obj/item/organ/genital/breasts/organ_breasts = action_owner.getorganslot(ORGAN_SLOT_BREASTS)
var/obj/item/organ/genital/vagina/organ_vagina = action_owner.getorganslot(ORGAN_SLOT_VAGINA)
// Play shake animation
action_owner.shake_animation(2)
// Transform into wolf form
if(!transformed)
// Define current species type
var/datum/species/owner_species = action_owner.dna.species.type
// Check if species has changed
if(old_features["species"] != owner_species)
// Set old species
old_features["species"] = owner_species
// Define species prefix
var/custom_species_prefix
// Check if species is mammal (anthro)
if(ismammal(action_owner))
// Do nothing!
// Check if species is already a mammal sub-type
else if(owner_species in subtypesof(/datum/species/mammal))
// Do nothing!
// Check if species is a jelly
else if(isjellyperson(action_owner))
// Set species prefix
custom_species_prefix = "Jelly "
// Check if species is a jelly subtype
else if(owner_species in subtypesof(/datum/species/jelly))
// Set species prefix
custom_species_prefix = "Slime "
// Species is not a mammal
else
// Change species
action_owner.set_species(/datum/species/mammal, 1)
// Set species changed
species_changed = TRUE
// Set species features
action_owner.dna.custom_species = "[custom_species_prefix][werewolf_gender]wulf"
action_owner.dna.species.mutant_bodyparts["mam_tail"] = "Otusian"
action_owner.dna.species.mutant_bodyparts["legs"] = "Digitigrade"
action_owner.Digitigrade_Leg_Swap(FALSE)
action_owner.dna.species.mutant_bodyparts["mam_snouts"] = "Sergal"
action_owner.dna.features["mam_ears"] = "Jackal"
action_owner.dna.features["mam_tail"] = "Otusian"
action_owner.dna.features["mam_snouts"] = "Sergal"
action_owner.dna.features["legs"] = "Digitigrade"
action_owner.dna.features["insect_fluff"] = "Hyena"
action_owner.update_size(get_size(action_owner) + 0.5)
action_owner.set_bark("bark")
if(old_features["taur"] != "None")
action_owner.dna.features["taur"] = "Canine"
if(!(action_owner.dna.species.species_traits.Find(DIGITIGRADE)))
action_owner.dna.species.species_traits += DIGITIGRADE
action_owner.update_body()
action_owner.update_body_parts()
// Update possible citadel organs
if(organ_breasts)
organ_breasts.color = "#[action_owner.dna.features["mcolor"]]"
organ_breasts.update()
if(organ_penis)
organ_penis.shape = "Knotted"
organ_penis.color = "#ff7c80"
organ_penis.update()
organ_penis.modify_size(6)
if(organ_vagina)
organ_vagina.shape = "Furred"
organ_vagina.color = "#[action_owner.dna.features["mcolor"]]"
organ_vagina.update()
// Un-transform from wolf form
else
// Check if species was already mammal (anthro)
if(!species_changed)
// Do nothing!
// Species was not a mammal
else
// Revert species
action_owner.set_species(old_features["species"], TRUE)
// Clear species changed flag
species_changed = FALSE
// Revert species trait
action_owner.set_bark(old_features["bark"])
action_owner.dna.custom_species = old_features["custom_species"]
action_owner.dna.features["mam_ears"] = old_features["mam_ears"]
action_owner.dna.features["mam_snouts"] = old_features["mam_snouts"]
action_owner.dna.features["mam_tail"] = old_features["mam_tail"]
action_owner.dna.features["legs"] = old_features["legs"]
action_owner.dna.features["insect_fluff"] = old_features["insect_fluff"]
action_owner.dna.species.eye_type = old_features["eye_type"]
if(old_features["taur"] != "None")
action_owner.dna.features["taur"] = old_features["taur"]
if(old_features["legs"] == "Plantigrade")
action_owner.dna.species.species_traits -= DIGITIGRADE
action_owner.Digitigrade_Leg_Swap(TRUE)
action_owner.dna.species.mutant_bodyparts["legs"] = old_features["legs"]
action_owner.update_body()
action_owner.update_body_parts()
action_owner.update_size(get_size(action_owner) - 0.5)
// Revert citadel organs
if(organ_breasts)
organ_breasts.color = "#[old_features["breasts_color"]]"
organ_breasts.update()
if(action_owner.has_penis())
organ_penis.shape = old_features["cock_shape"]
organ_penis.color = "#[old_features["cock_color"]]"
organ_penis.update()
organ_penis.modify_size(-6)
if(action_owner.has_vagina())
organ_vagina.shape = old_features["vag_shape"]
organ_vagina.color = "#[old_features["vag_color"]]"
organ_vagina.update()
organ_vagina.update_size()
// Set transformation message
var/owner_p_their = action_owner.p_their()
var/toggle_message = (!transformed ? "[action_owner] shivers, [owner_p_their] flesh bursting with a sudden growth of thick fur as [owner_p_their] features contort to that of a beast, fully transforming [action_owner.p_them()] into a werewolf!" : "[action_owner] shrinks, [owner_p_their] wolfish features quickly receding.")
// Alert in local chat
action_owner.visible_message(span_danger(toggle_message))
// Toggle transformation state
transformed = !transformed
// Start cooldown
StartCooldown()
// Return success
return TRUE
//
// Quirk: Gargoyle
//
/datum/action/gargoyle/transform
name = "Transform"
desc = "Transform into a statue, regaining energy in the process. Additionally, you will slowly heal while in statue form."
icon_icon = 'icons/mob/actions/actions_changeling.dmi'
button_icon_state = "ling_camouflage"
var/obj/structure/statue/gargoyle/current = null
/datum/action/gargoyle/transform/Trigger()
.=..()
var/mob/living/carbon/human/H = owner
var/datum/quirk/gargoyle/T = locate() in H.roundstart_quirks
if(!T.cooldown)
if(!T.transformed)
if(!isturf(H.loc))
return 0
var/obj/structure/statue/gargoyle/S = new(H.loc, H)
S.name = "statue of [H.name]"
H.bleedsuppress = 1
S.copy_overlays(H)
var/newcolor = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
S.add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY)
current = S
T.transformed = 1
T.cooldown = 30
T.paused = 0
S.dir = H.dir
return 1
else
qdel(current)
T.transformed = 0
T.cooldown = 30
T.paused = 0
H.visible_message(span_warning("[H]'s skin rapidly softens, returning them to normal!"), span_userdanger("Your skin softens, freeing your movement once more!"))
else
to_chat(H, span_warning("You have transformed too recently; you cannot yet transform again!"))
return 0
/datum/action/gargoyle/check
name = "Check"
desc = "Check your current energy levels."
icon_icon = 'icons/mob/actions/actions_clockcult.dmi'
button_icon_state = "Linked Vanguard"
/datum/action/gargoyle/check/Trigger()
.=..()
var/mob/living/carbon/human/H = owner
var/datum/quirk/gargoyle/T = locate() in H.roundstart_quirks
to_chat(H, span_warning("You have [T.energy]/100 energy remaining!"))
/datum/action/gargoyle/pause
name = "Preserve"
desc = "Become near-motionless, thusly conserving your energy until you move from your current tile. Note, you will lose a chunk of energy when you inevitably move from your current position, so you cannot abuse this!"
icon_icon = 'icons/mob/actions/actions_flightsuit.dmi'
button_icon_state = "flightsuit_lock"
/datum/action/gargoyle/pause/Trigger()
.=..()
var/mob/living/carbon/human/H = owner
var/datum/quirk/gargoyle/T = locate() in H.roundstart_quirks
if(!T.paused)
T.paused = 1
T.position = H.loc
to_chat(H, span_warning("You are now conserving your energy; this effect will end the moment you move from your current position!"))
return
else
to_chat(H, span_warning("You are already conserving your energy!"))