Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into tggenetics
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
var/name = "team"
|
||||
var/member_name = "member"
|
||||
var/list/objectives = list() //common objectives, these won't be added or removed automatically, subtypes handle this, this is here for bookkeeping purposes.
|
||||
var/show_roundend_report = TRUE
|
||||
|
||||
/datum/team/New(starting_members)
|
||||
. = ..()
|
||||
@@ -25,6 +26,8 @@
|
||||
|
||||
//Display members/victory/failure/objectives for the team
|
||||
/datum/team/proc/roundend_report()
|
||||
if(!show_roundend_report)
|
||||
return
|
||||
var/list/report = list()
|
||||
|
||||
report += "<span class='header'>[name]:</span>"
|
||||
|
||||
@@ -116,14 +116,13 @@
|
||||
var/mob/living/carbon/human/M = loc
|
||||
M.adjustStaminaLoss(-75)
|
||||
M.SetUnconscious(0)
|
||||
M.SetStun(0)
|
||||
M.SetKnockdown(0)
|
||||
M.SetAllImmobility(0)
|
||||
combat_cooldown = 0
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/clothing/suit/armor/abductor/vest/process()
|
||||
combat_cooldown++
|
||||
if(combat_cooldown==initial(combat_cooldown))
|
||||
if(combat_cooldown == initial(combat_cooldown))
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/clothing/suit/armor/abductor/Destroy()
|
||||
@@ -512,7 +511,7 @@
|
||||
L.lastattackerckey = user.ckey
|
||||
|
||||
L.adjustStaminaLoss(35) //because previously it took 5-6 hits to actually "incapacitate" someone for the purposes of the sleep inducement
|
||||
L.Knockdown(140)
|
||||
L.DefaultCombatKnockdown(140)
|
||||
L.apply_effect(EFFECT_STUTTER, 7)
|
||||
SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK)
|
||||
|
||||
|
||||
@@ -218,11 +218,16 @@
|
||||
|
||||
/mob/living/simple_animal/hostile/blob/blobbernaut/Initialize()
|
||||
. = ..()
|
||||
if(!independent) //no pulling people deep into the blob
|
||||
verbs -= /mob/living/verb/pulled
|
||||
else
|
||||
if(independent)
|
||||
pass_flags &= ~PASSBLOB
|
||||
|
||||
/mob/living/simple_animal/hostile/blob/blobbernaut/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE)
|
||||
if(!independent && ismob(AM))
|
||||
if(!supress_message)
|
||||
to_chat(src, "<span class='warning'>You are unable to grasp people in this form.</span>")
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/blob/blobbernaut/Life()
|
||||
if(..())
|
||||
var/list/blobs_in_area = range(2, src)
|
||||
|
||||
@@ -24,7 +24,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
|
||||
hud_type = /datum/hud/blob_overmind
|
||||
var/obj/structure/blob/core/blob_core = null // The blob overmind's core
|
||||
var/blob_points = 0
|
||||
var/max_blob_points = 100
|
||||
var/max_blob_points = 250
|
||||
var/last_attack = 0
|
||||
var/datum/reagent/blob/blob_reagent_datum = new/datum/reagent/blob()
|
||||
var/list/blob_mobs = list()
|
||||
@@ -157,6 +157,9 @@ GLOBAL_LIST_EMPTY(blob_nodes)
|
||||
BM.overmind = null
|
||||
BM.update_icons()
|
||||
GLOB.overminds -= src
|
||||
blob_mobs = null
|
||||
resource_blobs = null
|
||||
blobs_legit = null
|
||||
|
||||
SSshuttle.clearHostileEnvironment(src)
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
set category = "Blob"
|
||||
set name = "Blob Broadcast"
|
||||
set desc = "Speak with your blob spores and blobbernauts as your mouthpieces."
|
||||
var/speak_text = input(src, "What would you like to say with your minions?", "Blob Broadcast", null) as text
|
||||
var/speak_text = stripped_input(src, "What would you like to say with your minions?", "Blob Broadcast", null)
|
||||
if(!speak_text)
|
||||
return
|
||||
else
|
||||
|
||||
@@ -303,8 +303,7 @@
|
||||
|
||||
/obj/structure/blob/proc/change_to(type, controller)
|
||||
if(!ispath(type))
|
||||
throw EXCEPTION("change_to(): invalid type for blob")
|
||||
return
|
||||
CRASH("change_to(): invalid type for blob")
|
||||
var/obj/structure/blob/B = new type(src.loc, controller)
|
||||
B.creation_action()
|
||||
B.update_icon()
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
bloodsuckerdatum.handle_eat_human_food(food_nutrition)
|
||||
|
||||
|
||||
/datum/antagonist/bloodsucker/proc/handle_eat_human_food(var/food_nutrition) // Called from snacks.dm and drinks.dm
|
||||
/datum/antagonist/bloodsucker/proc/handle_eat_human_food(food_nutrition, puke_blood = TRUE, masquerade_override) // Called from snacks.dm and drinks.dm
|
||||
set waitfor = FALSE
|
||||
if(!owner.current || !iscarbon(owner.current))
|
||||
return
|
||||
@@ -324,14 +324,14 @@
|
||||
C.nutrition -= food_nutrition
|
||||
foodInGut += food_nutrition
|
||||
// Already ate some bad clams? Then we can back out, because we're already sick from it.
|
||||
if (foodInGut != food_nutrition)
|
||||
if(foodInGut != food_nutrition)
|
||||
return
|
||||
// Haven't eaten, but I'm in a Human Disguise.
|
||||
else if (poweron_masquerade)
|
||||
else if(poweron_masquerade && !masquerade_override)
|
||||
to_chat(C, "<span class='notice'>Your stomach turns, but your \"human disguise\" keeps the food down...for now.</span>")
|
||||
// Keep looping until we purge. If we have activated our Human Disguise, we ignore the food. But it'll come up eventually...
|
||||
var/sickphase = 0
|
||||
while (foodInGut)
|
||||
while(foodInGut)
|
||||
sleep(50)
|
||||
C.adjust_disgust(10 * sickphase)
|
||||
// Wait an interval...
|
||||
@@ -340,24 +340,29 @@
|
||||
if(C.stat == DEAD)
|
||||
return
|
||||
// Put up disguise? Then hold off the vomit.
|
||||
if(poweron_masquerade)
|
||||
if(poweron_masquerade && !masquerade_override)
|
||||
if(sickphase > 0)
|
||||
to_chat(C, "<span class='notice'>Your stomach settles temporarily. You regain your composure...for now.</span>")
|
||||
sickphase = 0
|
||||
continue
|
||||
switch(sickphase)
|
||||
if (1)
|
||||
if(1)
|
||||
to_chat(C, "<span class='warning'>You feel unwell. You can taste ash on your tongue.</span>")
|
||||
C.Stun(10)
|
||||
if (2)
|
||||
if(2)
|
||||
to_chat(C, "<span class='warning'>Your stomach turns. Whatever you ate tastes of grave dirt and brimstone.</span>")
|
||||
C.Dizzy(15)
|
||||
C.Stun(13)
|
||||
if (3)
|
||||
if(3)
|
||||
to_chat(C, "<span class='warning'>You purge the food of the living from your viscera! You've never felt worse.</span>")
|
||||
C.vomit(foodInGut * 4, foodInGut * 2, 0) // (var/lost_nutrition = 10, var/blood = 0, var/stun = 1, var/distance = 0, var/message = 1, var/toxic = 0)
|
||||
C.blood_volume = max(0, C.blood_volume - foodInGut * 2)
|
||||
//Puke blood only if puke_blood is true, and loose some blood, else just puke normally.
|
||||
if(puke_blood)
|
||||
C.blood_volume = max(0, C.blood_volume - foodInGut * 2)
|
||||
C.vomit(foodInGut * 4, foodInGut * 2, 0)
|
||||
else
|
||||
C.vomit(foodInGut * 4, FALSE, 0)
|
||||
C.Stun(30)
|
||||
//C.Dizzy(50)
|
||||
foodInGut = 0
|
||||
SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "vampdisgust", /datum/mood_event/bloodsucker_disgust)
|
||||
sickphase ++
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
// Destroy the Solar Arrays
|
||||
/datum/objective/bloodsucker/solars
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
if (SC && SC.lastgen > 0 && SC.connected_panels.len > 0 && SC.connected_tracker)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
*/
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
// Incap?
|
||||
if(must_be_capacitated)
|
||||
var/mob/living/L = owner
|
||||
if (L.incapacitated(TRUE, TRUE) || L.resting && !can_be_immobilized)
|
||||
if (L.incapacitated(TRUE, TRUE) || !CHECK_MOBILITY(L, MOBILITY_STAND) && !can_be_immobilized)
|
||||
if(display_error)
|
||||
to_chat(owner, "<span class='warning'>Not while you're incapacitated!</span>")
|
||||
return FALSE
|
||||
|
||||
@@ -379,20 +379,21 @@
|
||||
protege_objective.generate_objective()
|
||||
add_objective(protege_objective)
|
||||
|
||||
if (rand(0,1) == 0)
|
||||
//if (rand(0,1) == 0)
|
||||
// Heart Thief Objective
|
||||
var/datum/objective/bloodsucker/heartthief/heartthief_objective = new
|
||||
heartthief_objective.owner = owner
|
||||
heartthief_objective.generate_objective()
|
||||
add_objective(heartthief_objective)
|
||||
|
||||
var/datum/objective/bloodsucker/heartthief/heartthief_objective = new
|
||||
heartthief_objective.owner = owner
|
||||
heartthief_objective.generate_objective()
|
||||
add_objective(heartthief_objective)
|
||||
/*
|
||||
else
|
||||
// Solars Objective
|
||||
|
||||
// Solars Objective, doesnt work due to TG updates.
|
||||
var/datum/objective/bloodsucker/solars/solars_objective = new
|
||||
solars_objective.owner = owner
|
||||
solars_objective.generate_objective()
|
||||
add_objective(solars_objective)
|
||||
|
||||
*/
|
||||
// Survive Objective
|
||||
var/datum/objective/bloodsucker/survive/survive_objective = new
|
||||
survive_objective.owner = owner
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
|
||||
|
||||
// organ_internal.dm -- /obj/item/organ
|
||||
|
||||
|
||||
|
||||
// Do I have a stake in my heart?
|
||||
/mob/living/AmStaked()
|
||||
var/obj/item/bodypart/BP = get_bodypart("chest")
|
||||
@@ -13,16 +9,14 @@
|
||||
if (istype(I,/obj/item/stake/))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/proc/AmStaked()
|
||||
return FALSE
|
||||
|
||||
|
||||
/mob/living/proc/StakeCanKillMe()
|
||||
return IsSleeping() || stat >= UNCONSCIOUS || blood_volume <= 0 || HAS_TRAIT(src, TRAIT_DEATHCOMA) // NOTE: You can't go to sleep in a coffin with a stake in you.
|
||||
|
||||
|
||||
///obj/item/weapon/melee/stake
|
||||
/obj/item/stake/
|
||||
/obj/item/stake
|
||||
name = "wooden stake"
|
||||
desc = "A simple wooden stake carved to a sharp point."
|
||||
icon = 'icons/obj/items_and_weapons.dmi'
|
||||
@@ -113,8 +107,7 @@
|
||||
|
||||
// Can this target be staked? If someone stands up before this is complete, it fails. Best used on someone stationary.
|
||||
/mob/living/carbon/proc/can_be_staked()
|
||||
//return resting || IsKnockdown() || IsUnconscious() || (stat && (stat != SOFT_CRIT || pulledby)) || (has_trait(TRAIT_FAKEDEATH)) || resting || IsStun() || IsFrozen() || (pulledby && pulledby.grab_state >= GRAB_NECK)
|
||||
return (resting || lying || IsUnconscious() || pulledby && pulledby.grab_state >= GRAB_NECK)
|
||||
return !CHECK_MOBILITY(src, MOBILITY_STAND)
|
||||
// ABOVE: Taken from update_mobility() in living.dm
|
||||
|
||||
/obj/item/stake/hardened
|
||||
|
||||
@@ -42,25 +42,18 @@
|
||||
/obj/structure/closet/crate
|
||||
var/mob/living/resident // This lets bloodsuckers claim any "closet" as a Coffin, so long as they could get into it and close it. This locks it in place, too.
|
||||
|
||||
/obj/structure/closet/crate/coffin
|
||||
var/pryLidTimer = 250
|
||||
can_weld_shut = FALSE
|
||||
breakout_time = 200
|
||||
|
||||
|
||||
/obj/structure/closet/crate/coffin/blackcoffin
|
||||
name = "black coffin"
|
||||
desc = "For those departed who are not so dear."
|
||||
icon_state = "coffin"
|
||||
icon = 'icons/obj/vamp_obj.dmi'
|
||||
can_weld_shut = FALSE
|
||||
resistance_flags = 0 // Start off with no bonuses.
|
||||
open_sound = 'sound/bloodsucker/coffin_open.ogg'
|
||||
close_sound = 'sound/bloodsucker/coffin_close.ogg'
|
||||
breakout_time = 600
|
||||
pryLidTimer = 400
|
||||
resistance_flags = NONE
|
||||
integrity_failure = 70
|
||||
max_integrity = 100
|
||||
integrity_failure = 0.5
|
||||
armor = list("melee" = 50, "bullet" = 20, "laser" = 30, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
|
||||
|
||||
/obj/structure/closet/crate/coffin/meatcoffin
|
||||
@@ -68,8 +61,6 @@
|
||||
desc = "When you're ready to meat your maker, the steaks can never be too high."
|
||||
icon_state = "meatcoffin"
|
||||
icon = 'icons/obj/vamp_obj.dmi'
|
||||
can_weld_shut = FALSE
|
||||
resistance_flags = 0 // Start off with no bonuses.
|
||||
open_sound = 'sound/effects/footstep/slime1.ogg'
|
||||
close_sound = 'sound/effects/footstep/slime1.ogg'
|
||||
breakout_time = 200
|
||||
@@ -77,24 +68,23 @@
|
||||
resistance_flags = NONE
|
||||
material_drop = /obj/item/reagent_containers/food/snacks/meat/slab
|
||||
material_drop_amount = 3
|
||||
integrity_failure = 40
|
||||
integrity_failure = 0.57
|
||||
armor = list("melee" = 70, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100)
|
||||
|
||||
|
||||
/obj/structure/closet/crate/coffin/metalcoffin
|
||||
name = "metal coffin"
|
||||
desc = "A big metal sardine can inside of another big metal sardine can, in space."
|
||||
icon_state = "metalcoffin"
|
||||
icon = 'icons/obj/vamp_obj.dmi'
|
||||
can_weld_shut = FALSE
|
||||
resistance_flags = FIRE_PROOF | LAVA_PROOF
|
||||
open_sound = 'sound/effects/pressureplate.ogg'
|
||||
close_sound = 'sound/effects/pressureplate.ogg'
|
||||
breakout_time = 300
|
||||
pryLidTimer = 200
|
||||
resistance_flags = NONE
|
||||
material_drop = /obj/item/stack/sheet/metal
|
||||
material_drop_amount = 5
|
||||
integrity_failure = 60
|
||||
max_integrity = 200
|
||||
integrity_failure = 0.25
|
||||
armor = list("melee" = 40, "bullet" = 15, "laser" = 50, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
|
||||
|
||||
//////////////////////////////////////////////
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
buckled_mob.pixel_y = buckled_mob.get_standard_pixel_y_offset(180)
|
||||
src.visible_message(text("<span class='danger'>[buckled_mob][buckled_mob.stat==DEAD?"'s corpse":""] slides off of the rack.</span>"))
|
||||
density = FALSE
|
||||
buckled_mob.AdjustKnockdown(30)
|
||||
buckled_mob.DefaultCombatKnockdown(30)
|
||||
update_icon()
|
||||
useLock = FALSE // Failsafe
|
||||
|
||||
|
||||
@@ -72,8 +72,7 @@
|
||||
if(rand(5 + powerlevel) >= 5)
|
||||
target.visible_message("<span class='danger'>[user] lands a vicious punch, sending [target] away!</span>", \
|
||||
"<span class='userdanger'>[user] has landed a horrifying punch on you, sending you flying!!</span>", null, COMBAT_MESSAGE_RANGE)
|
||||
target.Knockdown(min(5, rand(10, 10 * powerlevel)) )
|
||||
|
||||
target.DefaultCombatKnockdown(min(5, rand(10, 10 * powerlevel)) )
|
||||
// Attack!
|
||||
playsound(get_turf(target), 'sound/weapons/punch4.ogg', 60, 1, -1)
|
||||
user.do_attack_animation(target, ATTACK_EFFECT_SMASH)
|
||||
@@ -145,7 +144,7 @@
|
||||
// Knock Down (if Living)
|
||||
if (isliving(M))
|
||||
var/mob/living/L = M
|
||||
L.Knockdown(pull_power * 10 + 20)
|
||||
L.DefaultCombatKnockdown(pull_power * 10 + 20)
|
||||
// Knock Back (before Knockdown, which probably cancels pull)
|
||||
var/send_dir = get_dir(owner, M)
|
||||
var/turf/T = get_ranged_target_turf(M, send_dir, pull_power)
|
||||
|
||||
@@ -2,40 +2,59 @@
|
||||
|
||||
/datum/action/bloodsucker/cloak
|
||||
name = "Cloak of Darkness"
|
||||
desc = "Blend into the shadows and become invisible to the untrained eye."
|
||||
desc = "Blend into the shadows and become invisible to the untrained eye. Movement is slowed in brightly lit areas."
|
||||
button_icon_state = "power_cloak"
|
||||
bloodcost = 5
|
||||
cooldown = 50
|
||||
bloodsucker_can_buy = TRUE
|
||||
amToggle = TRUE
|
||||
warn_constant_cost = TRUE
|
||||
var/was_running
|
||||
|
||||
var/light_min = 0.2 // If lum is above this, no good.
|
||||
var/moveintent_was_run
|
||||
var/runintent
|
||||
var/walk_threshold = 0.4 // arbitrary number, to be changed. edit in last commit: this is fine after testing on box station for a bit
|
||||
var/lum
|
||||
|
||||
/datum/action/bloodsucker/cloak/CheckCanUse(display_error)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
// Must be Dark
|
||||
var/turf/T = owner.loc
|
||||
if(istype(T) && T.get_lumcount() > light_min)
|
||||
to_chat(owner, "<span class='warning'>This area is not dark enough to blend in</span>")
|
||||
return FALSE
|
||||
|
||||
// must have nobody around to see the cloak
|
||||
var/watchers = viewers(9,get_turf(owner))
|
||||
for(var/mob/living/M in watchers)
|
||||
if(M != owner)
|
||||
to_chat(owner, "<span class='warning'>You may only vanish into the shadows unseen.</span>")
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/action/bloodsucker/cloak/ActivatePower()
|
||||
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
|
||||
var/mob/living/user = owner
|
||||
was_running = (user.m_intent == MOVE_INTENT_RUN)
|
||||
if(was_running)
|
||||
user.toggle_move_intent()
|
||||
ADD_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
|
||||
|
||||
moveintent_was_run = (user.m_intent == MOVE_INTENT_RUN)
|
||||
|
||||
while(bloodsuckerdatum && ContinueActive(user))
|
||||
// Pay Blood Toll (if awake)
|
||||
owner.alpha = max(20, owner.alpha - min(75, 10 + 5 * level_current))
|
||||
owner.alpha = max(35, owner.alpha - min(75, 10 + 5 * level_current))
|
||||
bloodsuckerdatum.AddBloodVolume(-0.2)
|
||||
sleep(5) // Check every few ticks that we haven't disabled this power
|
||||
|
||||
runintent = (user.m_intent == MOVE_INTENT_RUN)
|
||||
var/turf/T = get_turf(user)
|
||||
lum = T.get_lumcount()
|
||||
|
||||
if(istype(owner.loc))
|
||||
if(lum > walk_threshold)
|
||||
if(runintent)
|
||||
user.toggle_move_intent()
|
||||
ADD_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
|
||||
|
||||
if(lum < walk_threshold)
|
||||
if(!runintent)
|
||||
user.toggle_move_intent()
|
||||
REMOVE_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
|
||||
|
||||
sleep(5) // Check every few ticks
|
||||
|
||||
/datum/action/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target)
|
||||
if (!..())
|
||||
@@ -43,15 +62,14 @@
|
||||
if(user.stat == !CONSCIOUS) // Must be CONSCIOUS
|
||||
to_chat(owner, "<span class='warning'>Your cloak failed due to you falling unconcious! </span>")
|
||||
return FALSE
|
||||
var/turf/T = owner.loc // Must be DARK
|
||||
if(istype(T) && T.get_lumcount() > light_min)
|
||||
to_chat(owner, "<span class='warning'>Your cloak failed due to there being too much light!</span>")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/action/bloodsucker/cloak/DeactivatePower(mob/living/user = owner, mob/living/target)
|
||||
..()
|
||||
REMOVE_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
|
||||
user.alpha = 255
|
||||
if(was_running && user.m_intent != MOVE_INTENT_RUN)
|
||||
|
||||
runintent = (user.m_intent == MOVE_INTENT_RUN)
|
||||
|
||||
if(!runintent && moveintent_was_run)
|
||||
user.toggle_move_intent()
|
||||
|
||||
@@ -306,7 +306,7 @@
|
||||
// Bloodsuckers not affected by "the Kiss" of another vampire
|
||||
if(!target.mind || !target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
|
||||
target.Unconscious(50,0)
|
||||
target.Knockdown(40 + 5 * level_current,1)
|
||||
target.DefaultCombatKnockdown(40 + 5 * level_current,1)
|
||||
// NOTE: THis is based on level of power!
|
||||
if(ishuman(target))
|
||||
target.adjustStaminaLoss(5, forced = TRUE)// Base Stamina Damage
|
||||
@@ -321,4 +321,4 @@
|
||||
// My mouth is no longer full
|
||||
REMOVE_TRAIT(owner, TRAIT_MUTE, "bloodsucker_feed")
|
||||
// Let me move immediately
|
||||
user.update_canmove()
|
||||
user.update_mobility()
|
||||
|
||||
@@ -100,8 +100,8 @@
|
||||
var/mob/living/simple_animal/SA = pick(/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse, /mob/living/simple_animal/hostile/retaliate/bat) //prob(300) /mob/living/simple_animal/mouse,
|
||||
new SA (owner.loc)
|
||||
// TELEPORT: Move to Coffin & Close it!
|
||||
user.set_resting(TRUE, TRUE, FALSE)
|
||||
do_teleport(owner, bloodsuckerdatum.coffin, no_effects = TRUE, forced = TRUE, channel = TELEPORT_CHANNEL_QUANTUM)
|
||||
user.resting = TRUE
|
||||
user.Stun(30,1)
|
||||
// CLOSE LID: If fail, force me in.
|
||||
if(!bloodsuckerdatum.coffin.close(owner))
|
||||
|
||||
@@ -76,16 +76,17 @@
|
||||
sleep(speed)
|
||||
UnregisterSignal(owner, COMSIG_MOVABLE_MOVED)
|
||||
hit = null
|
||||
user.update_canmove()
|
||||
user.update_mobility()
|
||||
|
||||
/datum/action/bloodsucker/targeted/haste/DeactivatePower(mob/living/user = owner, mob/living/target)
|
||||
..() // activate = FALSE
|
||||
user.update_canmove()
|
||||
user.update_mobility()
|
||||
|
||||
/datum/action/bloodsucker/targeted/haste/proc/on_move()
|
||||
for(var/mob/living/L in dview(1, get_turf(owner)))
|
||||
if(!hit[L] && (L != owner))
|
||||
hit[L] = TRUE
|
||||
playsound(L, "sound/weapons/punch[rand(1,4)].ogg", 15, 1, -1)
|
||||
L.Knockdown(10 + level_current * 5, override_hardstun = 0.1)
|
||||
L.DefaultCombatKnockdown(10 + level_current * 5)
|
||||
L.Paralyze(0.1)
|
||||
L.spin(10, 1)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
cooldown = 120
|
||||
target_range = 3
|
||||
power_activates_immediately = TRUE
|
||||
message_Trigger = ""//"Whom will you subvert to your will?"
|
||||
message_Trigger = "Whom will you ensnare within your grasp?"
|
||||
must_be_capacitated = TRUE
|
||||
bloodsucker_can_buy = TRUE
|
||||
|
||||
@@ -52,32 +52,37 @@
|
||||
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
|
||||
var/mob/living/carbon/target = A
|
||||
var/turf/T = get_turf(target)
|
||||
var/mob/living/L = owner
|
||||
// Clear Vars
|
||||
owner.pulling = null
|
||||
// Will we Knock them Down?
|
||||
var/do_knockdown = !is_A_facing_B(target,owner) || owner.alpha <= 0 || istype(owner.loc, /obj/structure/closet)
|
||||
// CAUSES: Target has their back to me, I'm invisible, or I'm in a Closet
|
||||
// Step One: Heatseek toward Target's Turf
|
||||
|
||||
walk_towards(owner, T, 0.1, 10) // NOTE: this runs in the background! to cancel it, you need to use walk(owner.current,0), or give them a new path.
|
||||
addtimer(CALLBACK(owner, .proc/_walk, 0), 2 SECONDS)
|
||||
if(get_turf(owner) != T && !(isliving(target) && target.Adjacent(owner)) && owner.incapacitated() && owner.resting)
|
||||
var/send_dir = get_dir(owner, T)
|
||||
new /datum/forced_movement(owner, get_ranged_target_turf(owner, send_dir, 1), 1, FALSE)
|
||||
owner.spin(10)
|
||||
// Step Two: Check if I'm at/adjectent to Target's CURRENT turf (not original...that was just a destination)
|
||||
sleep(1)
|
||||
if(target.Adjacent(owner))
|
||||
// LEVEL 2: If behind target, mute or unconscious!
|
||||
if(do_knockdown) // && level_current >= 1)
|
||||
target.Knockdown(15 + 10 * level_current,1)
|
||||
target.adjustStaminaLoss(40 + 10 * level_current)
|
||||
// Cancel Walk (we were close enough to contact them)
|
||||
walk(owner, 0)
|
||||
target.Stun(10,1) //Without this the victim can just walk away
|
||||
target.grabbedby(owner) // Taken from mutations.dm under changelings
|
||||
target.grippedby(owner, instant = TRUE) //instant aggro grab
|
||||
addtimer(CALLBACK(GLOBAL_PROC, .proc/_walk, owner, 0), 2 SECONDS)
|
||||
target.playsound_local(get_turf(owner), 'sound/bloodsucker/lunge_warn.ogg', 60, FALSE, pressure_affected = FALSE) // target-only telegraphing
|
||||
owner.playsound_local(owner, 'sound/bloodsucker/lunge_warn.ogg', 60, FALSE, pressure_affected = FALSE) // audio feedback to the user
|
||||
if(do_mob(owner, owner, 7, TRUE, TRUE))
|
||||
walk_towards(owner, T, 0.1, 10) // yes i know i shouldn't use this but i don't know how to work in anything better
|
||||
if(get_turf(owner) != T && !(isliving(target) && target.Adjacent(owner)) && owner.incapacitated() && !CHECK_MOBILITY(L, MOBILITY_STAND))
|
||||
var/send_dir = get_dir(owner, T)
|
||||
new /datum/forced_movement(owner, get_ranged_target_turf(owner, send_dir, 1), 1, FALSE)
|
||||
owner.spin(10)
|
||||
// Step Two: Check if I'm at/adjectent to Target's CURRENT turf (not original...that was just a destination)
|
||||
for(var/i in 1 to 6)
|
||||
if (target.Adjacent(owner))
|
||||
// LEVEL 2: If behind target, mute or unconscious!
|
||||
if(do_knockdown) // && level_current >= 1)
|
||||
target.Knockdown(15 + 10 * level_current,1)
|
||||
target.adjustStaminaLoss(40 + 10 * level_current)
|
||||
// Cancel Walk (we were close enough to contact them)
|
||||
walk(owner, 0)
|
||||
target.Stun(10,1) //Without this the victim can just walk away
|
||||
target.grabbedby(owner) // Taken from mutations.dm under changelings
|
||||
target.grippedby(owner, instant = TRUE) //instant aggro grab
|
||||
break
|
||||
sleep(3)
|
||||
|
||||
/datum/action/bloodsucker/targeted/lunge/DeactivatePower(mob/living/user = owner, mob/living/target)
|
||||
..() // activate = FALSE
|
||||
user.update_canmove()
|
||||
user.update_mobility()
|
||||
|
||||
@@ -11,24 +11,25 @@
|
||||
button_icon_state = "power_mez"
|
||||
bloodcost = 30
|
||||
cooldown = 300
|
||||
target_range = 1
|
||||
power_activates_immediately = FALSE
|
||||
target_range = 2
|
||||
power_activates_immediately = TRUE
|
||||
message_Trigger = "Whom will you subvert to your will?"
|
||||
must_be_capacitated = TRUE
|
||||
bloodsucker_can_buy = TRUE
|
||||
var/success
|
||||
|
||||
/datum/action/bloodsucker/targeted/mesmerize/CheckCanUse(display_error)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
if (!owner.getorganslot(ORGAN_SLOT_EYES))
|
||||
if(!owner.getorganslot(ORGAN_SLOT_EYES))
|
||||
if (display_error)
|
||||
to_chat(owner, "<span class='warning'>You have no eyes with which to mesmerize.</span>")
|
||||
return FALSE
|
||||
// Check: Eyes covered?
|
||||
var/mob/living/L = owner
|
||||
if (istype(L) && L.is_eyes_covered() || !isturf(owner.loc))
|
||||
if (display_error)
|
||||
if(istype(L) && L.is_eyes_covered() || !isturf(owner.loc))
|
||||
if(display_error)
|
||||
to_chat(owner, "<span class='warning'>Your eyes are concealed from sight.</span>")
|
||||
return FALSE
|
||||
return TRUE
|
||||
@@ -38,74 +39,99 @@
|
||||
|
||||
/datum/action/bloodsucker/targeted/mesmerize/CheckCanTarget(atom/A,display_error)
|
||||
// Check: Self
|
||||
if (A == owner)
|
||||
if(A == owner)
|
||||
return FALSE
|
||||
var/mob/living/carbon/target = A // We already know it's carbon due to CheckValidTarget()
|
||||
|
||||
// Bloodsucker
|
||||
if (target.mind && target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
|
||||
if(target.mind && target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
|
||||
if (display_error)
|
||||
to_chat(owner, "<span class='warning'>Bloodsuckers are immune to [src].</span>")
|
||||
return FALSE
|
||||
// Dead/Unconscious
|
||||
if (target.stat > CONSCIOUS)
|
||||
if(target.stat > CONSCIOUS)
|
||||
if (display_error)
|
||||
to_chat(owner, "<span class='warning'>Your victim is not [(target.stat == DEAD || HAS_TRAIT(target, TRAIT_FAKEDEATH))?"alive":"conscious"].</span>")
|
||||
return FALSE
|
||||
// Check: Target has eyes?
|
||||
if (!target.getorganslot(ORGAN_SLOT_EYES))
|
||||
if(!target.getorganslot(ORGAN_SLOT_EYES))
|
||||
if (display_error)
|
||||
to_chat(owner, "<span class='warning'>They have no eyes!</span>")
|
||||
return FALSE
|
||||
// Check: Target blind?
|
||||
if (target.eye_blind > 0)
|
||||
if(target.eye_blind > 0)
|
||||
if (display_error)
|
||||
to_chat(owner, "<span class='warning'>Your victim's eyes are glazed over. They cannot perceive you.</span>")
|
||||
return FALSE
|
||||
// Check: Target See Me? (behind wall)
|
||||
if (!(owner in view(target_range, get_turf(target))))
|
||||
if(!(target in view(target_range, get_turf(owner))))
|
||||
// Sub-Check: GET CLOSER
|
||||
//if (!(owner in range(target_range, get_turf(target)))
|
||||
// if (display_error)
|
||||
// to_chat(owner, "<span class='warning'>You're too far from your victim.</span>")
|
||||
if (display_error)
|
||||
if(display_error)
|
||||
to_chat(owner, "<span class='warning'>You're too far outside your victim's view.</span>")
|
||||
return FALSE
|
||||
|
||||
if(target.has_status_effect(STATUS_EFFECT_MESMERIZE)) // ignores facing once the windup has started
|
||||
return TRUE
|
||||
|
||||
// Check: Facing target?
|
||||
if (!is_A_facing_B(owner,target)) // in unsorted.dm
|
||||
if(!is_A_facing_B(owner,target)) // in unsorted.dm
|
||||
if (display_error)
|
||||
to_chat(owner, "<span class='warning'>You must be facing your victim.</span>")
|
||||
return FALSE
|
||||
// Check: Target facing me?
|
||||
if (!target.resting && !is_A_facing_B(target,owner))
|
||||
if (display_error)
|
||||
if (CHECK_MOBILITY(target, MOBILITY_STAND) && !is_A_facing_B(target,owner))
|
||||
if(display_error)
|
||||
to_chat(owner, "<span class='warning'>Your victim must be facing you to see into your eyes.</span>")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/action/bloodsucker/targeted/mesmerize/proc/ContinueTarget(atom/A)
|
||||
var/mob/living/carbon/target = A
|
||||
var/mob/living/user = owner
|
||||
|
||||
var/cancontinue=CheckCanTarget(target)
|
||||
if(!cancontinue)
|
||||
success = FALSE
|
||||
target.remove_status_effect(STATUS_EFFECT_MESMERIZE)
|
||||
user.remove_status_effect(STATUS_EFFECT_MESMERIZE)
|
||||
DeactivatePower()
|
||||
DeactivateRangedAbility()
|
||||
StartCooldown()
|
||||
to_chat(user, "<span class='warning'>[target] has escaped your gaze!</span>")
|
||||
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
/datum/action/bloodsucker/targeted/mesmerize/FireTargetedPower(atom/A)
|
||||
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
|
||||
var/mob/living/carbon/target = A
|
||||
var/mob/living/user = owner
|
||||
|
||||
if(istype(target))
|
||||
target.Stun(40) //Utterly useless without this, its okay since there are so many checks to go through
|
||||
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, 45) //So you cant rotate with combat mode, plus fancy status alert
|
||||
|
||||
if(do_mob(user, target, 40, 0, TRUE, extra_checks = CALLBACK(src, .proc/ContinueActive, user, target)))
|
||||
PowerActivatedSuccessfully() // PAY COST! BEGIN COOLDOWN!
|
||||
var/power_time = 90 + level_current * 12
|
||||
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, power_time + 80)
|
||||
to_chat(user, "<span class='notice'>[target] is fixed in place by your hypnotic gaze.</span>")
|
||||
target.Stun(power_time)
|
||||
target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // target.changeNext_move(power_time) // check click.dm
|
||||
target.notransform = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
|
||||
spawn(power_time)
|
||||
if(istype(target))
|
||||
target.notransform = FALSE
|
||||
// They Woke Up! (Notice if within view)
|
||||
if(istype(user) && target.stat == CONSCIOUS && (target in view(10, get_turf(user))) )
|
||||
to_chat(user, "<span class='warning'>[target] has snapped out of their trance.</span>")
|
||||
success = TRUE
|
||||
var/power_time = 138 + level_current * 12
|
||||
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, 30)
|
||||
user.apply_status_effect(STATUS_EFFECT_MESMERIZE, 30)
|
||||
|
||||
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/ContinueTarget)
|
||||
|
||||
// 3 second windup
|
||||
sleep(30)
|
||||
if(success)
|
||||
PowerActivatedSuccessfully() // blood & cooldown only altered if power activated successfully - less "fuck you"-y
|
||||
target.face_atom(user)
|
||||
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, power_time) // pretty much purely cosmetic
|
||||
target.Stun(power_time)
|
||||
to_chat(user, "<span class='notice'>[target] is fixed in place by your hypnotic gaze.</span>")
|
||||
target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // target.changeNext_move(power_time) // check click.dm
|
||||
target.notransform = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
|
||||
spawn(power_time)
|
||||
if(istype(target) && success)
|
||||
target.notransform = FALSE
|
||||
// They Woke Up! (Notice if within view)
|
||||
if(istype(user) && target.stat == CONSCIOUS && (target in view(10, get_turf(user))) )
|
||||
to_chat(user, "<span class='warning'>[target] has snapped out of their trance.</span>")
|
||||
|
||||
/datum/action/bloodsucker/targeted/mesmerize/ContinueActive(mob/living/user, mob/living/target)
|
||||
return ..() && CheckCanUse() && CheckCanTarget(target)
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
H.socks = random_socks(H.gender)
|
||||
//H.eye_color = random_eye_color()
|
||||
REMOVE_TRAIT(H, TRAIT_DISFIGURED, null) //
|
||||
H.dna.features = random_features()
|
||||
H.dna.features = random_features(H.dna.species?.id)
|
||||
|
||||
// Apply Appearance
|
||||
H.update_body() // Outfit and underware, also body.
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
if(istype(our_target, /datum/cellular_emporium))
|
||||
cellular_emporium = our_target
|
||||
else
|
||||
throw EXCEPTION("cellular_emporium action created with non emporium")
|
||||
CRASH("cellular_emporium action created with non emporium")
|
||||
|
||||
/datum/action/innate/cellular_emporium/Activate()
|
||||
cellular_emporium.ui_interact(owner)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
user.tod = STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)
|
||||
user.fakedeath("changeling") //play dead
|
||||
user.update_stat()
|
||||
user.update_canmove()
|
||||
|
||||
addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE)
|
||||
return TRUE
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
H.confused += 3
|
||||
for(var/mob/living/silicon/S in range(2,user))
|
||||
to_chat(S, "<span class='userdanger'>Your sensors are disabled by a shower of blood!</span>")
|
||||
S.Knockdown(60)
|
||||
S.DefaultCombatKnockdown(60)
|
||||
var/turf = get_turf(user)
|
||||
user.gib()
|
||||
. = TRUE
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
if(issilicon(M))
|
||||
SEND_SOUND(M, sound('sound/weapons/flash.ogg'))
|
||||
M.Knockdown(rand(100,200))
|
||||
M.DefaultCombatKnockdown(rand(100,200))
|
||||
|
||||
for(var/obj/machinery/light/L in range(4, user))
|
||||
L.on = 1
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
changeling.chem_recharge_slowdown -= 0.5
|
||||
if(stacks >= 20)
|
||||
to_chat(user, "<span class='danger'>We collapse in exhaustion.</span>")
|
||||
user.Knockdown(60)
|
||||
user.DefaultCombatKnockdown(60)
|
||||
user.emote("gasp")
|
||||
|
||||
INVOKE_ASYNC(src, .proc/muscle_loop, user)
|
||||
@@ -40,7 +40,7 @@
|
||||
if(user.stat != CONSCIOUS || user.staminaloss >= 90)
|
||||
active = !active
|
||||
to_chat(user, "<span class='notice'>Our muscles relax without the energy to strengthen them.</span>")
|
||||
user.Knockdown(40)
|
||||
user.DefaultCombatKnockdown(40)
|
||||
user.remove_movespeed_modifier(MOVESPEED_ID_CHANGELING_MUSCLES)
|
||||
changeling.chem_recharge_slowdown -= 0.5
|
||||
break
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
if(iscultist(L)) //No longer stuns cultists, instead sets them on fire and burns them
|
||||
to_chat(L, "<span class='heavy_brass'>\"Watch your step, wretch.\"</span>")
|
||||
L.adjustFireLoss(10)
|
||||
L.Knockdown(20, FALSE)
|
||||
L.DefaultCombatKnockdown(20, FALSE)
|
||||
L.adjust_fire_stacks(5) //Burn!
|
||||
L.IgniteMob()
|
||||
else
|
||||
@@ -155,7 +155,7 @@
|
||||
if(brutedamage || burndamage)
|
||||
L.adjustBruteLoss(-(brutedamage * 0.25))
|
||||
L.adjustFireLoss(-(burndamage * 0.25))
|
||||
L.Knockdown(50) //Completely defenseless for five seconds - mainly to give them time to read over the information they've just been presented with
|
||||
L.DefaultCombatKnockdown(50) //Completely defenseless for five seconds - mainly to give them time to read over the information they've just been presented with
|
||||
if(iscarbon(L))
|
||||
var/mob/living/carbon/C = L
|
||||
C.silent += 5
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
if(!iscultist(L))
|
||||
L.visible_message("<span class='warning'>[L]'s eyes blaze with brilliant light!</span>", \
|
||||
"<span class='userdanger'>Your vision suddenly screams with white-hot light!</span>")
|
||||
L.Knockdown(15, TRUE, FALSE, 15)
|
||||
L.DefaultCombatKnockdown(15, TRUE, FALSE, 15)
|
||||
L.apply_status_effect(STATUS_EFFECT_KINDLE)
|
||||
L.flash_act(1, 1)
|
||||
if(issilicon(target))
|
||||
|
||||
@@ -57,15 +57,15 @@
|
||||
else if(!..())
|
||||
if(!L.anti_magic_check())
|
||||
if(issilicon(L))
|
||||
L.Knockdown(100)
|
||||
L.DefaultCombatKnockdown(100)
|
||||
else if(iscultist(L))
|
||||
L.confused += CLAMP(10 - L.confused, 0, 5) // Spearthrow now confuses enemy cultists + just deals extra damage / sets on fire instead of hardstunning + damage
|
||||
to_chat(L, "<span class ='userdanger'>[src] crashes into you with burning force, sending you reeling!</span>")
|
||||
L.adjust_fire_stacks(2)
|
||||
L.Knockdown(1)
|
||||
L.DefaultCombatKnockdown(1)
|
||||
L.IgniteMob()
|
||||
else
|
||||
L.Knockdown(40)
|
||||
L.DefaultCombatKnockdown(40)
|
||||
GLOB.clockwork_vitality += L.adjustFireLoss(bonus_burn * 3) //normally a total of 40 damage, 70 with ratvar
|
||||
break_spear(T)
|
||||
else
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
var/datum/status_effect/belligerent/B = C.apply_status_effect(STATUS_EFFECT_BELLIGERENT)
|
||||
if(!QDELETED(B))
|
||||
B.duration = world.time + 30
|
||||
C.Knockdown(5) //knocks down for half a second if affected
|
||||
C.DefaultCombatKnockdown(5) //knocks down for half a second if affected
|
||||
sleep(!GLOB.ratvar_approaches ? 16 : 10)
|
||||
name = "judicial blast"
|
||||
layer = ABOVE_ALL_MOB_LAYER
|
||||
@@ -196,7 +196,7 @@
|
||||
L.visible_message("<span class='warning'>Strange energy flows into [L]'s [I.name]!</span>", \
|
||||
"<span class='userdanger'>Your [I.name] shields you from [src]!</span>")
|
||||
continue
|
||||
L.Knockdown(15) //knocks down briefly when exploding
|
||||
L.DefaultCombatKnockdown(15) //knocks down briefly when exploding
|
||||
if(!iscultist(L))
|
||||
L.visible_message("<span class='warning'>[L] is struck by a judicial explosion!</span>", \
|
||||
"<span class='userdanger'>[!issilicon(L) ? "An unseen force slams you into the ground!" : "ERROR: Motor servos disabled by external source!"]</span>")
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
L.confused = min(L.confused + 15, 50)
|
||||
L.dizziness = min(L.dizziness + 15, 50)
|
||||
if(L.confused >= 25)
|
||||
L.Knockdown(FLOOR(L.confused * 0.8, 1))
|
||||
L.DefaultCombatKnockdown(FLOOR(L.confused * 0.8, 1))
|
||||
take_damage(max_integrity)
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
if(buckled_mobs && LAZYLEN(buckled_mobs))
|
||||
var/mob/living/L = buckled_mobs[1]
|
||||
if(iscarbon(L))
|
||||
L.Knockdown(100)
|
||||
L.DefaultCombatKnockdown(100)
|
||||
L.visible_message("<span class='warning'>[L] is maimed as the skewer shatters while still in [L.p_their()] body!</span>")
|
||||
L.adjustBruteLoss(15)
|
||||
unbuckle_mob(L)
|
||||
@@ -117,6 +117,6 @@
|
||||
return
|
||||
skewee.visible_message("<span class='danger'>[skewee] comes free of [src] with a squelching pop!</span>", \
|
||||
"<span class='boldannounce'>You come free of [src]!</span>")
|
||||
skewee.Knockdown(30)
|
||||
skewee.DefaultCombatKnockdown(30)
|
||||
playsound(skewee, 'sound/misc/desceration-03.ogg', 50, TRUE)
|
||||
unbuckle_mob(skewee)
|
||||
|
||||
@@ -446,7 +446,7 @@
|
||||
var/atom/throw_target = get_edge_target_turf(L, user.dir)
|
||||
L.throw_at(throw_target, 7, 1, user)
|
||||
else if(!iscultist(L))
|
||||
L.Knockdown(160)
|
||||
L.DefaultCombatKnockdown(160)
|
||||
L.adjustStaminaLoss(140) //Ensures hard stamcrit
|
||||
L.flash_act(1,1)
|
||||
if(issilicon(target))
|
||||
|
||||
@@ -46,10 +46,11 @@
|
||||
/obj/item/melee/cultblade/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/butchering, 40, 100)
|
||||
AddElement(/datum/element/sword_point)
|
||||
|
||||
/obj/item/melee/cultblade/attack(mob/living/target, mob/living/carbon/human/user)
|
||||
if(!iscultist(user))
|
||||
user.Knockdown(100)
|
||||
user.DefaultCombatKnockdown(100)
|
||||
user.dropItemToGround(src, TRUE)
|
||||
user.visible_message("<span class='warning'>A powerful force shoves [user] away from [target]!</span>", \
|
||||
"<span class='cultlarge'>\"You shouldn't play with sharp things. You'll poke someone's eye out.\"</span>")
|
||||
@@ -148,7 +149,7 @@
|
||||
user.emote("scream")
|
||||
user.apply_damage(30, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
|
||||
user.dropItemToGround(src, TRUE)
|
||||
user.Knockdown(50)
|
||||
user.DefaultCombatKnockdown(50)
|
||||
return
|
||||
force = initial(force)
|
||||
jaunt.Grant(user, src)
|
||||
@@ -405,7 +406,7 @@
|
||||
to_chat(user, "<span class='warning'>An overwhelming sense of nausea overpowers you!</span>")
|
||||
user.dropItemToGround(src, TRUE)
|
||||
user.Dizzy(30)
|
||||
user.Knockdown(100)
|
||||
user.DefaultCombatKnockdown(100)
|
||||
else
|
||||
to_chat(user, "<span class='cultlarge'>\"Trying to use things you don't own is bad, you know.\"</span>")
|
||||
to_chat(user, "<span class='userdanger'>The armor squeezes at your body!</span>")
|
||||
@@ -457,7 +458,7 @@
|
||||
to_chat(user, "<span class='warning'>An overwhelming sense of nausea overpowers you!</span>")
|
||||
user.dropItemToGround(src, TRUE)
|
||||
user.Dizzy(30)
|
||||
user.Knockdown(100)
|
||||
user.DefaultCombatKnockdown(100)
|
||||
else
|
||||
to_chat(user, "<span class='cultlarge'>\"Trying to use things you don't own is bad, you know.\"</span>")
|
||||
to_chat(user, "<span class='userdanger'>The robes squeeze at your body!</span>")
|
||||
@@ -478,7 +479,7 @@
|
||||
to_chat(user, "<span class='cultlarge'>\"You want to be blind, do you?\"</span>")
|
||||
user.dropItemToGround(src, TRUE)
|
||||
user.Dizzy(30)
|
||||
user.Knockdown(100)
|
||||
user.DefaultCombatKnockdown(100)
|
||||
user.blind_eyes(30)
|
||||
|
||||
/obj/item/reagent_containers/glass/beaker/unholywater
|
||||
@@ -499,7 +500,7 @@
|
||||
/obj/item/shuttle_curse/attack_self(mob/living/user)
|
||||
if(!iscultist(user))
|
||||
user.dropItemToGround(src, TRUE)
|
||||
user.Knockdown(100)
|
||||
user.DefaultCombatKnockdown(100)
|
||||
to_chat(user, "<span class='warning'>A powerful force shoves you away from [src]!</span>")
|
||||
return
|
||||
if(curselimit > 1)
|
||||
@@ -705,10 +706,10 @@
|
||||
if(is_servant_of_ratvar(L))
|
||||
to_chat(L, "<span class='cultlarge'>\"Kneel for me, scum\"</span>")
|
||||
L.confused += CLAMP(10 - L.confused, 0, 5) //confuses and lightly knockdowns + damages hostile cultists instead of hardstunning like before
|
||||
L.Knockdown(15)
|
||||
L.DefaultCombatKnockdown(15)
|
||||
L.adjustBruteLoss(10)
|
||||
else
|
||||
L.Knockdown(50)
|
||||
L.DefaultCombatKnockdown(50)
|
||||
break_spear(T)
|
||||
else
|
||||
..()
|
||||
@@ -843,7 +844,7 @@
|
||||
INVOKE_ASYNC(src, .proc/pewpew, user, params)
|
||||
var/obj/structure/emergency_shield/invoker/N = new(user.loc)
|
||||
if(do_after(user, 90, target = user))
|
||||
user.Knockdown(40)
|
||||
user.DefaultCombatKnockdown(40)
|
||||
to_chat(user, "<span class='cult italic'>You have exhausted the power of this spell!</span>")
|
||||
firing = FALSE
|
||||
if(N)
|
||||
@@ -908,7 +909,7 @@
|
||||
else
|
||||
var/mob/living/L = target
|
||||
if(L.density)
|
||||
L.Knockdown(20)
|
||||
L.DefaultCombatKnockdown(20)
|
||||
L.adjustBruteLoss(45)
|
||||
playsound(L, 'sound/hallucinations/wail.ogg', 50, 1)
|
||||
L.emote("scream")
|
||||
@@ -944,7 +945,7 @@
|
||||
T.visible_message("<span class='warning'>The sheer force from [P] shatters the mirror shield!</span>")
|
||||
new /obj/effect/temp_visual/cult/sparks(T)
|
||||
playsound(T, 'sound/effects/glassbr3.ogg', 100)
|
||||
owner.Knockdown(25)
|
||||
owner.DefaultCombatKnockdown(25)
|
||||
qdel(src)
|
||||
return FALSE
|
||||
if(P.is_reflectable)
|
||||
@@ -1001,9 +1002,9 @@
|
||||
else if(!..())
|
||||
if(!L.anti_magic_check())
|
||||
if(is_servant_of_ratvar(L))
|
||||
L.Knockdown(60)
|
||||
L.DefaultCombatKnockdown(60)
|
||||
else
|
||||
L.Knockdown(30)
|
||||
L.DefaultCombatKnockdown(30)
|
||||
if(D.thrower)
|
||||
for(var/mob/living/Next in orange(2, T))
|
||||
if(!Next.density || iscultist(Next))
|
||||
|
||||
@@ -219,7 +219,7 @@ structure_check() searches for nearby cultist structures required for the invoca
|
||||
L.visible_message("<span class='warning'>[L]'s eyes glow a defiant yellow!</span>", \
|
||||
"<span class='cultlarge'>\"Stop resisting. You <i>will</i> be mi-\"</span>\n\
|
||||
<span class='large_brass'>\"Give up and you will feel pain unlike anything you've ever felt!\"</span>")
|
||||
L.Knockdown(80)
|
||||
L.DefaultCombatKnockdown(80)
|
||||
else if(is_convertable)
|
||||
do_convert(L, invokers)
|
||||
else
|
||||
@@ -908,7 +908,7 @@ structure_check() searches for nearby cultist structures required for the invoca
|
||||
if(affecting.key)
|
||||
affecting.visible_message("<span class='warning'>[affecting] slowly relaxes, the glow around [affecting.p_them()] dimming.</span>", \
|
||||
"<span class='danger'>You are re-united with your physical form. [src] releases its hold over you.</span>")
|
||||
affecting.Knockdown(40)
|
||||
affecting.DefaultCombatKnockdown(40)
|
||||
break
|
||||
if(affecting.health <= 10)
|
||||
to_chat(G, "<span class='cultitalic'>Your body can no longer sustain the connection!</span>")
|
||||
@@ -970,7 +970,7 @@ structure_check() searches for nearby cultist structures required for the invoca
|
||||
playsound(T, 'sound/magic/enter_blood.ogg', 100, 1)
|
||||
visible_message("<span class='warning'>A colossal shockwave of energy bursts from the rune, disintegrating it in the process!</span>")
|
||||
for(var/mob/living/L in range(src, 3))
|
||||
L.Knockdown(30)
|
||||
L.DefaultCombatKnockdown(30)
|
||||
empulse(T, 0.42*(intensity), 1)
|
||||
var/list/images = list()
|
||||
var/zmatch = T.z
|
||||
|
||||
@@ -477,7 +477,7 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
|
||||
if(SOULVALUE >= ARCH_THRESHOLD && ascendable)
|
||||
A.convert_to_archdevil()
|
||||
else
|
||||
throw EXCEPTION("Unable to find a blobstart landmark for hellish resurrection")
|
||||
CRASH("Unable to find a blobstart landmark for hellish resurrection")
|
||||
|
||||
|
||||
/datum/antagonist/devil/proc/update_hud()
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
if(BANE_HARVEST)
|
||||
if(istype(weapon, /obj/item/reagent_containers/food/snacks/grown/))
|
||||
visible_message("<span class='warning'>The spirits of the harvest aid in the exorcism.</span>", "<span class='notice'>The harvest spirits are harming you.</span>")
|
||||
Knockdown(40)
|
||||
DefaultCombatKnockdown(40)
|
||||
qdel(weapon)
|
||||
return 2
|
||||
return 1
|
||||
@@ -70,16 +70,10 @@
|
||||
O.explanation_text = "Protect \the [M.current.real_name], the [M.assigned_role], from harm."
|
||||
objectives += O
|
||||
if(4) //flavor
|
||||
if(helping_station)
|
||||
var/datum/objective/flavor/ninja_helping/O = new /datum/objective/flavor/ninja_helping
|
||||
O.owner = owner
|
||||
O.forge_objective()
|
||||
objectives += O
|
||||
else
|
||||
var/datum/objective/flavor/ninja_syndie/O = new /datum/objective/flavor/ninja_helping
|
||||
O.owner = owner
|
||||
O.forge_objective()
|
||||
objectives += O
|
||||
var/datum/objective/flavor/O = helping_station ? new /datum/objective/flavor/ninja_helping : new /datum/objective/flavor/ninja_syndie
|
||||
O.owner = owner
|
||||
O.forge_objective()
|
||||
objectives += O
|
||||
else
|
||||
break
|
||||
var/datum/objective/O = new /datum/objective/survive()
|
||||
|
||||
@@ -178,23 +178,22 @@
|
||||
else
|
||||
return NUKE_OFF_UNLOCKED
|
||||
|
||||
/obj/machinery/nuclearbomb/update_icon()
|
||||
if(deconstruction_state == NUKESTATE_INTACT)
|
||||
switch(get_nuke_state())
|
||||
if(NUKE_OFF_LOCKED, NUKE_OFF_UNLOCKED)
|
||||
icon_state = "nuclearbomb_base"
|
||||
update_icon_interior()
|
||||
update_icon_lights()
|
||||
if(NUKE_ON_TIMING)
|
||||
cut_overlays()
|
||||
icon_state = "nuclearbomb_timing"
|
||||
if(NUKE_ON_EXPLODING)
|
||||
cut_overlays()
|
||||
icon_state = "nuclearbomb_exploding"
|
||||
else
|
||||
/obj/machinery/nuclearbomb/update_icon_state()
|
||||
if(deconstruction_state != NUKESTATE_INTACT)
|
||||
icon_state = "nuclearbomb_base"
|
||||
update_icon_interior()
|
||||
update_icon_lights()
|
||||
return
|
||||
switch(get_nuke_state())
|
||||
if(NUKE_OFF_LOCKED, NUKE_OFF_UNLOCKED)
|
||||
icon_state = "nuclearbomb_base"
|
||||
if(NUKE_ON_TIMING)
|
||||
icon_state = "nuclearbomb_timing"
|
||||
if(NUKE_ON_EXPLODING)
|
||||
icon_state = "nuclearbomb_exploding"
|
||||
|
||||
/obj/machinery/nuclearbomb/update_overlays()
|
||||
. = ..()
|
||||
update_icon_interior()
|
||||
update_icon_lights()
|
||||
|
||||
/obj/machinery/nuclearbomb/proc/update_icon_interior()
|
||||
cut_overlay(interior)
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
return 0
|
||||
|
||||
/obj/item/IntegrateAmount() //returns the amount of resources gained when eating this item
|
||||
if(custom_materials[getmaterialref(/datum/material/iron)] || custom_materials[getmaterialref(/datum/material/glass)])
|
||||
if(custom_materials[SSmaterials.GetMaterialRef(/datum/material/iron)] || custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)])
|
||||
return 1
|
||||
return ..()
|
||||
|
||||
@@ -586,7 +586,7 @@
|
||||
playsound(loc,'sound/effects/snap.ogg',50, 1, -1)
|
||||
L.electrocute_act(0, src, 1, 1, 1)
|
||||
if(iscyborg(L))
|
||||
L.Knockdown(100)
|
||||
L.DefaultCombatKnockdown(100)
|
||||
qdel(src)
|
||||
..()
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
var/should_give_codewords = TRUE
|
||||
var/should_equip = TRUE
|
||||
var/traitor_kind = TRAITOR_HUMAN //Set on initial assignment
|
||||
var/datum/contractor_hub/contractor_hub
|
||||
hijack_speed = 0.5 //10 seconds per hijack stage by default
|
||||
|
||||
/datum/antagonist/traitor/on_gain()
|
||||
@@ -413,6 +414,9 @@
|
||||
|
||||
var/special_role_text = lowertext(name)
|
||||
|
||||
if(contractor_hub)
|
||||
result += contractor_round_end()
|
||||
|
||||
if(traitorwin)
|
||||
result += "<span class='greentext'>The [special_role_text] was successful!</span>"
|
||||
else
|
||||
@@ -421,12 +425,44 @@
|
||||
|
||||
return result.Join("<br>")
|
||||
|
||||
/// Proc detailing contract kit buys/completed contracts/additional info
|
||||
/datum/antagonist/traitor/proc/contractor_round_end()
|
||||
var result = ""
|
||||
var total_spent_rep = 0
|
||||
|
||||
var/completed_contracts = 0
|
||||
var/tc_total = contractor_hub.contract_TC_payed_out + contractor_hub.contract_TC_to_redeem
|
||||
for(var/datum/syndicate_contract/contract in contractor_hub.assigned_contracts)
|
||||
if(contract.status == CONTRACT_STATUS_COMPLETE)
|
||||
completed_contracts++
|
||||
|
||||
var/contractor_item_icons = "" // Icons of purchases
|
||||
var/contractor_support_unit = "" // Set if they had a support unit - and shows appended to their contracts completed
|
||||
|
||||
for(var/datum/contractor_item/contractor_purchase in contractor_hub.purchased_items) // Get all the icons/total cost for all our items bought
|
||||
contractor_item_icons += "<span class='tooltip_container'>\[ <i class=\"fas [contractor_purchase.item_icon]\"></i><span class='tooltip_hover'><b>[contractor_purchase.name] - [contractor_purchase.cost] Rep</b><br><br>[contractor_purchase.desc]</span> \]</span>"
|
||||
total_spent_rep += contractor_purchase.cost
|
||||
if(istype(contractor_purchase, /datum/contractor_item/contractor_partner)) // Special case for reinforcements, we want to show their ckey and name on round end.
|
||||
var/datum/contractor_item/contractor_partner/partner = contractor_purchase
|
||||
contractor_support_unit += "<br><b>[partner.partner_mind.key]</b> played <b>[partner.partner_mind.current.name]</b>, their contractor support unit."
|
||||
if (contractor_hub.purchased_items.len)
|
||||
result += "<br>(used [total_spent_rep] Rep)"
|
||||
result += contractor_item_icons
|
||||
result += "<br>"
|
||||
if(completed_contracts > 0)
|
||||
var/pluralCheck = "contract"
|
||||
if(completed_contracts > 1)
|
||||
pluralCheck = "contracts"
|
||||
result += "Completed <span class='greentext'>[completed_contracts]</span> [pluralCheck] for a total of \
|
||||
<span class='greentext'>[tc_total] TC</span>!<br>"
|
||||
return result
|
||||
|
||||
/datum/antagonist/traitor/roundend_report_footer()
|
||||
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
|
||||
var/responses = jointext(GLOB.syndicate_code_response, ", ")
|
||||
|
||||
var message = "<br><b>The code phrases were:</b> <span class='bluetext'>[phrases]</span><br>\
|
||||
<b>The code responses were:</b> <span class='redtext'>[responses]</span><br>"
|
||||
<b>The code responses were:</b> <span class='redtext'>[responses]</span><br>"
|
||||
|
||||
return message
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// Support unit gets it's own very basic antag datum for admin logging.
|
||||
/datum/antagonist/traitor/contractor_support
|
||||
name = "Contractor Support Unit"
|
||||
antag_moodlet = /datum/mood_event/focused
|
||||
show_in_roundend = FALSE /// We're already adding them in to the contractor's roundend.
|
||||
give_objectives = TRUE /// We give them their own custom objective.
|
||||
show_in_antagpanel = FALSE /// Not a proper/full antag.
|
||||
should_equip = FALSE /// Don't give them an uplink.
|
||||
var/datum/team/contractor_team/contractor_team
|
||||
|
||||
/datum/team/contractor_team // Team for storing both the contractor and their support unit - only really for the HUD and admin logging.
|
||||
show_roundend_report = FALSE
|
||||
|
||||
/datum/antagonist/traitor/contractor_support/forge_traitor_objectives()
|
||||
var/datum/objective/generic_objective = new
|
||||
generic_objective.name = "Follow Contractor's Orders"
|
||||
generic_objective.explanation_text = "Follow your orders. Assist agents in this mission area."
|
||||
generic_objective.completed = TRUE
|
||||
add_objective(generic_objective)
|
||||
|
||||
/datum/contractor_hub
|
||||
var/contract_rep = 0
|
||||
var/list/hub_items = list()
|
||||
var/list/purchased_items = list()
|
||||
var/static/list/contractor_items = typecacheof(/datum/contractor_item/, TRUE)
|
||||
var/datum/syndicate_contract/current_contract
|
||||
var/list/datum/syndicate_contract/assigned_contracts = list()
|
||||
var/list/assigned_targets = list() // used as a blacklist to make sure we're not assigning targets already assigned
|
||||
var/contract_TC_payed_out = 0 // Keeping track for roundend reporting
|
||||
var/contract_TC_to_redeem = 0 // Used internally and roundend reporting - what TC we have available to cashout.
|
||||
|
||||
/datum/contractor_hub/proc/create_hub_items()
|
||||
for(var/path in contractor_items)
|
||||
var/datum/contractor_item/contractor_item = new path
|
||||
hub_items.Add(contractor_item)
|
||||
|
||||
/datum/contractor_hub/proc/create_contracts(datum/mind/owner) // 6 initial contracts
|
||||
var/list/to_generate = list(
|
||||
CONTRACT_PAYOUT_LARGE,
|
||||
CONTRACT_PAYOUT_MEDIUM,
|
||||
CONTRACT_PAYOUT_SMALL,
|
||||
CONTRACT_PAYOUT_SMALL,
|
||||
CONTRACT_PAYOUT_SMALL,
|
||||
CONTRACT_PAYOUT_SMALL
|
||||
)
|
||||
|
||||
var/lowest_TC_threshold = 30 // We don't want the sum of all the payouts to be under this amount
|
||||
var/total = 0
|
||||
var/lowest_paying_sum = 0
|
||||
var/datum/syndicate_contract/lowest_paying_contract
|
||||
|
||||
to_generate = shuffle(to_generate) // Randomise order, so we don't have contracts always in payout order.
|
||||
var/start_index = 1 // Support contract generation happening multiple times
|
||||
if(assigned_contracts.len != 0)
|
||||
start_index = assigned_contracts.len + 1
|
||||
|
||||
for(var/i = 1; i <= to_generate.len; i++) // Generate contracts, and find the lowest paying.
|
||||
var/datum/syndicate_contract/contract_to_add = new(owner, assigned_targets, to_generate[i])
|
||||
var/contract_payout_total = contract_to_add.contract.payout + contract_to_add.contract.payout_bonus
|
||||
assigned_targets.Add(contract_to_add.contract.target)
|
||||
if(!lowest_paying_contract || (contract_payout_total < lowest_paying_sum))
|
||||
lowest_paying_sum = contract_payout_total
|
||||
lowest_paying_contract = contract_to_add
|
||||
total += contract_payout_total
|
||||
contract_to_add.id = start_index
|
||||
assigned_contracts.Add(contract_to_add)
|
||||
start_index++
|
||||
if(total < lowest_TC_threshold) // If the threshold for TC payouts isn't reached, boost the lowest paying contract
|
||||
lowest_paying_contract.contract.payout_bonus += (lowest_TC_threshold - total)
|
||||
|
||||
/datum/contractor_item
|
||||
var/name // Name of item
|
||||
var/desc // description of item
|
||||
var/item // item path, no item path means the purchase needs it's own handle_purchase()
|
||||
var/item_icon = "fa-broadcast-tower" // fontawesome icon to use inside the hub - https://fontawesome.com/icons/
|
||||
var/limited = -1 // Any number above 0 for how many times it can be bought in a round for a single traitor. -1 is unlimited.
|
||||
var/cost // Cost of the item in contract rep.
|
||||
|
||||
/datum/contractor_item/contract_reroll
|
||||
name = "Contract Reroll"
|
||||
desc = "Request a reroll of your current contract list. Will generate a new target, payment, and dropoff for the contracts you currently have available."
|
||||
item_icon = "fa-dice"
|
||||
limited = 2
|
||||
cost = 0
|
||||
|
||||
/datum/contractor_item/contract_reroll/handle_purchase(var/datum/contractor_hub/hub)
|
||||
. = ..()
|
||||
if (.)
|
||||
var/list/new_target_list = list() // We're not regenerating already completed/aborted/extracting contracts, but we don't want to repeat their targets.
|
||||
for(var/datum/syndicate_contract/contract_check in hub.assigned_contracts)
|
||||
if (contract_check.status != CONTRACT_STATUS_ACTIVE && contract_check.status != CONTRACT_STATUS_INACTIVE)
|
||||
if (contract_check.contract.target)
|
||||
new_target_list.Add(contract_check.contract.target)
|
||||
continue
|
||||
for(var/datum/syndicate_contract/rerolling_contract in hub.assigned_contracts) // Reroll contracts without duplicates
|
||||
if (rerolling_contract.status != CONTRACT_STATUS_ACTIVE && rerolling_contract.status != CONTRACT_STATUS_INACTIVE)
|
||||
continue
|
||||
rerolling_contract.generate(new_target_list)
|
||||
new_target_list.Add(rerolling_contract.contract.target)
|
||||
hub.assigned_targets = new_target_list // Set our target list with the new set we've generated.
|
||||
|
||||
/datum/contractor_item/contractor_pinpointer
|
||||
name = "Contractor Pinpointer"
|
||||
desc = "A pinpointer that finds targets even without active suit sensors. Due to taking advantage of an exploit within the system, it can't pinpoint to the same accuracy as the traditional models. Becomes permanently locked to the user that first activates it."
|
||||
item = /obj/item/pinpointer/crew/contractor
|
||||
item_icon = "fa-search-location"
|
||||
limited = 2
|
||||
cost = 1
|
||||
|
||||
/datum/contractor_item/fulton_extraction_kit
|
||||
name = "Fulton Extraction Kit"
|
||||
desc = "For getting your target across the station to those difficult dropoffs. Place the beacon somewhere secure, and link the pack. Activating the pack on your target in space will send them over to the beacon - make sure they're not just going to run away though!"
|
||||
item = /obj/item/storage/box/contractor/fulton_extraction
|
||||
item_icon = "fa-parachute-box"
|
||||
limited = 1
|
||||
cost = 1
|
||||
|
||||
/datum/contractor_item/contractor_partner
|
||||
name = "Reinforcements"
|
||||
desc = "Upon purchase we'll contact available units in the area. Should there be an agent free, we'll send them down to assist you immediately. If no units are free, we give a full refund."
|
||||
item_icon = "fa-user-friends"
|
||||
limited = 1
|
||||
cost = 2
|
||||
var/datum/mind/partner_mind = null
|
||||
|
||||
/datum/contractor_item/contractor_partner/handle_purchase(var/datum/contractor_hub/hub, mob/living/user)
|
||||
. = ..()
|
||||
if (.)
|
||||
to_chat(user, "<span class='notice'>The uplink vibrates quietly, connecting to nearby agents...</span>")
|
||||
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the Contractor Support Unit for [user.real_name]?", ROLE_PAI, null, FALSE, 100, POLL_IGNORE_CONTRACTOR_SUPPORT)
|
||||
if(LAZYLEN(candidates))
|
||||
var/mob/dead/observer/C = pick(candidates)
|
||||
spawn_contractor_partner(user, C.key)
|
||||
else
|
||||
to_chat(user, "<span class='notice'>No available agents at this time, please try again later.</span>")
|
||||
limited += 1 // refund and add the limit back.
|
||||
hub.contract_rep += cost
|
||||
hub.purchased_items -= src
|
||||
|
||||
/datum/outfit/contractor_partner
|
||||
name = "Contractor Support Unit"
|
||||
uniform = /obj/item/clothing/under/chameleon
|
||||
suit = /obj/item/clothing/suit/chameleon
|
||||
back = /obj/item/storage/backpack
|
||||
belt = /obj/item/pda/chameleon
|
||||
mask = /obj/item/clothing/mask/cigarette/syndicate
|
||||
shoes = /obj/item/clothing/shoes/chameleon/noslip
|
||||
ears = /obj/item/radio/headset/chameleon
|
||||
id = /obj/item/card/id/syndicate
|
||||
r_hand = /obj/item/storage/toolbox/syndicate
|
||||
backpack_contents = list(/obj/item/storage/box/survival, /obj/item/implanter/uplink, /obj/item/clothing/mask/chameleon,
|
||||
/obj/item/storage/fancy/cigarettes/cigpack_syndicate, /obj/item/lighter)
|
||||
|
||||
/datum/outfit/contractor_partner/post_equip(mob/living/carbon/human/H, visualsOnly)
|
||||
. = ..()
|
||||
var/obj/item/clothing/mask/cigarette/syndicate/cig = H.get_item_by_slot(SLOT_WEAR_MASK)
|
||||
cig.light() // pre-light their cig for extra badass
|
||||
|
||||
/datum/contractor_item/contractor_partner/proc/spawn_contractor_partner(mob/living/user, key)
|
||||
var/mob/living/carbon/human/partner = new()
|
||||
var/datum/outfit/contractor_partner/partner_outfit = new()
|
||||
partner_outfit.equip(partner)
|
||||
var/obj/structure/closet/supplypod/arrival_pod = new()
|
||||
arrival_pod.style = STYLE_SYNDICATE
|
||||
arrival_pod.explosionSize = list(0,0,0,1)
|
||||
arrival_pod.bluespace = TRUE
|
||||
var/turf/free_location = find_obstruction_free_location(2, user)
|
||||
if (!free_location) // We really want to send them - if we can't find a nice location just land it on top of them.
|
||||
free_location = get_turf(user)
|
||||
partner.forceMove(arrival_pod)
|
||||
partner.ckey = key
|
||||
partner_mind = partner.mind // We give a reference to the mind that'll be the support unit
|
||||
partner_mind.make_Contractor_Support()
|
||||
to_chat(partner_mind.current, "\n<span class='alertwarning'>[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.</span>")
|
||||
to_chat(partner_mind.current, "<span class='alertwarning'>Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.</span>\n\n")
|
||||
new /obj/effect/abstract/DPtarget(free_location, arrival_pod)
|
||||
|
||||
/datum/contractor_item/blackout
|
||||
name = "Blackout"
|
||||
desc = "Request Syndicate Command to distrupt the station's powernet. Disables power across the station for a short duration."
|
||||
item_icon = "fa-bolt"
|
||||
limited = 2
|
||||
cost = 3
|
||||
|
||||
/datum/contractor_item/blackout/handle_purchase(var/datum/contractor_hub/hub)
|
||||
. = ..()
|
||||
if (.)
|
||||
power_fail(35, 50)
|
||||
priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", "poweroff")
|
||||
|
||||
// Subtract cost, and spawn if it's an item.
|
||||
/datum/contractor_item/proc/handle_purchase(var/datum/contractor_hub/hub, mob/living/user)
|
||||
if (hub.contract_rep >= cost)
|
||||
hub.contract_rep -= cost
|
||||
else
|
||||
return FALSE
|
||||
if (limited >= 1)
|
||||
limited -= 1
|
||||
else if (limited == 0)
|
||||
return FALSE
|
||||
hub.purchased_items.Add(src)
|
||||
if (item && ispath(item))
|
||||
var/atom/item_to_create = new item(get_turf(user))
|
||||
|
||||
if(user.put_in_hands(item_to_create))
|
||||
to_chat(user, "<span class='notice'>Your purchase materializes into your hands!</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>Your purchase materializes onto the floor.</span>")
|
||||
return item_to_create
|
||||
return TRUE
|
||||
|
||||
/obj/item/pinpointer/crew/contractor
|
||||
name = "contractor pinpointer"
|
||||
desc = "A handheld tracking device that locks onto certain signals. Ignores suit sensors, but is much less accurate."
|
||||
icon_state = "pinpointer_syndicate"
|
||||
minimum_range = 25
|
||||
has_owner = TRUE
|
||||
ignore_suit_sensor_level = TRUE
|
||||
|
||||
/obj/item/storage/box/contractor/fulton_extraction
|
||||
name = "Fulton Extraction Kit"
|
||||
icon_state = "syndiebox"
|
||||
illustration = "writing_syndie"
|
||||
|
||||
/obj/item/storage/box/contractor/fulton_extraction/PopulateContents()
|
||||
new /obj/item/extraction_pack(src)
|
||||
new /obj/item/fulton_core(src)
|
||||
@@ -0,0 +1,150 @@
|
||||
/datum/syndicate_contract
|
||||
var/id = 0
|
||||
var/status = CONTRACT_STATUS_INACTIVE
|
||||
var/datum/objective/contract/contract = new()
|
||||
var/target_rank
|
||||
var/ransom = 0
|
||||
var/payout_type = null
|
||||
var/list/victim_belongings = list()
|
||||
|
||||
/datum/syndicate_contract/New(contract_owner, blacklist, type=CONTRACT_PAYOUT_SMALL)
|
||||
contract.owner = contract_owner
|
||||
payout_type = type
|
||||
generate(blacklist)
|
||||
|
||||
/datum/syndicate_contract/proc/generate(blacklist)
|
||||
contract.find_target(null, blacklist)
|
||||
var/datum/data/record/record = find_record("name", contract.target.name, GLOB.data_core.general)
|
||||
if(record)
|
||||
target_rank = record.fields["rank"]
|
||||
else
|
||||
target_rank = "Unknown"
|
||||
if (payout_type == CONTRACT_PAYOUT_LARGE)
|
||||
contract.payout_bonus = rand(9,13)
|
||||
else if(payout_type == CONTRACT_PAYOUT_MEDIUM)
|
||||
contract.payout_bonus = rand(6,8)
|
||||
else
|
||||
contract.payout_bonus = rand(2,4)
|
||||
contract.payout = rand(0, 2)
|
||||
contract.generate_dropoff()
|
||||
ransom = 100 * rand(18, 45)
|
||||
|
||||
/datum/syndicate_contract/proc/handle_extraction(var/mob/living/user)
|
||||
if (contract.target && contract.dropoff_check(user, contract.target.current))
|
||||
var/turf/free_location = find_obstruction_free_location(3, user, contract.dropoff)
|
||||
if(free_location) // We've got a valid location, launch.
|
||||
launch_extraction_pod(free_location)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
// Launch the pod to collect our victim.
|
||||
/datum/syndicate_contract/proc/launch_extraction_pod(turf/empty_pod_turf)
|
||||
var/obj/structure/closet/supplypod/extractionpod/empty_pod = new()
|
||||
RegisterSignal(empty_pod, COMSIG_ATOM_ENTERED, .proc/enter_check)
|
||||
empty_pod.stay_after_drop = TRUE
|
||||
empty_pod.reversing = TRUE
|
||||
empty_pod.explosionSize = list(0,0,0,1)
|
||||
empty_pod.leavingSound = 'sound/effects/podwoosh.ogg'
|
||||
new /obj/effect/abstract/DPtarget(empty_pod_turf, empty_pod)
|
||||
|
||||
/datum/syndicate_contract/proc/enter_check(datum/source, sent_mob)
|
||||
if(istype(source, /obj/structure/closet/supplypod/extractionpod))
|
||||
if(isliving(sent_mob))
|
||||
var/mob/living/M = sent_mob
|
||||
var/datum/antagonist/traitor/traitor_data = contract.owner.has_antag_datum(/datum/antagonist/traitor)
|
||||
if(M == contract.target.current)
|
||||
traitor_data.contractor_hub.contract_TC_to_redeem += contract.payout
|
||||
if(M.stat != DEAD)
|
||||
traitor_data.contractor_hub.contract_TC_to_redeem += contract.payout_bonus
|
||||
status = CONTRACT_STATUS_COMPLETE
|
||||
if(traitor_data.contractor_hub.current_contract == src)
|
||||
traitor_data.contractor_hub.current_contract = null
|
||||
traitor_data.contractor_hub.contract_rep += 2
|
||||
else
|
||||
status = CONTRACT_STATUS_ABORTED // Sending a target that wasn't even yours is as good as just aborting it
|
||||
if(traitor_data.contractor_hub.current_contract == src)
|
||||
traitor_data.contractor_hub.current_contract = null
|
||||
if(iscarbon(M))
|
||||
for(var/obj/item/W in M)
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(W == H.w_uniform || W == H.shoes)
|
||||
continue //So all they're left with are shoes and uniform.
|
||||
M.transferItemToLoc(W)
|
||||
victim_belongings.Add(W)
|
||||
var/obj/structure/closet/supplypod/extractionpod/pod = source
|
||||
pod.send_up(pod) // Handle the pod returning
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/target = M // After we remove items, at least give them what they need to live.
|
||||
target.dna.species.give_important_for_life(target)
|
||||
handleVictimExperience(M) // After pod is sent we start the victim narrative/heal.
|
||||
var/points_to_check = SSshuttle.points // This is slightly delayed because of the sleep calls above to handle the narrative. We don't want to tell the station instantly.
|
||||
if(points_to_check >= ransom)
|
||||
SSshuttle.points -= ransom
|
||||
else
|
||||
SSshuttle.points -= points_to_check
|
||||
priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. \
|
||||
As is policy we've taken a portion of the station's funds to offset the overall cost.", null, "attention", null, "Nanotrasen Asset Protection")
|
||||
|
||||
/datum/syndicate_contract/proc/handleVictimExperience(var/mob/living/M) // They're off to holding - handle the return timer and give some text about what's going on.
|
||||
addtimer(CALLBACK(src, .proc/returnVictim, M), (60 * 10) * 4) // Ship 'em back - dead or alive... 4 minutes wait.
|
||||
if(M.stat != DEAD) //Even if they weren't the target, we're still treating them the same.
|
||||
M.reagents.add_reagent(/datum/reagent/medicine/omnizine, 20) // Heal them up - gets them out of crit/soft crit.
|
||||
M.flash_act()
|
||||
M.confused += 10
|
||||
M.blur_eyes(5)
|
||||
to_chat(M, "<span class='warning'>You feel strange...</span>")
|
||||
sleep(60)
|
||||
to_chat(M, "<span class='warning'>That pod did something to you...</span>")
|
||||
M.Dizzy(35)
|
||||
sleep(65)
|
||||
to_chat(M, "<span class='warning'>Your head pounds... It feels like it's going to burst out your skull!</span>")
|
||||
M.flash_act()
|
||||
M.confused += 20
|
||||
M.blur_eyes(3)
|
||||
sleep(30)
|
||||
to_chat(M, "<span class='warning'>Your head pounds...</span>")
|
||||
sleep(100)
|
||||
M.flash_act()
|
||||
M.Unconscious(200)
|
||||
to_chat(M, "<span class='reallybig hypnophrase'>A million voices echo in your head... <i>\"Your mind held many valuable secrets - \
|
||||
we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \
|
||||
so it's only a matter of time before we ship you back...\"</i></span>")
|
||||
M.blur_eyes(10)
|
||||
M.Dizzy(15)
|
||||
M.confused += 20
|
||||
|
||||
/datum/syndicate_contract/proc/returnVictim(var/mob/living/M) // We're returning the victim
|
||||
var/list/possible_drop_loc = list()
|
||||
for(var/turf/possible_drop in contract.dropoff.contents)
|
||||
if(!is_blocked_turf(possible_drop))
|
||||
possible_drop_loc.Add(possible_drop)
|
||||
if(possible_drop_loc.len > 0)
|
||||
var/pod_rand_loc = rand(1, possible_drop_loc.len)
|
||||
var/obj/structure/closet/supplypod/return_pod = new()
|
||||
return_pod.bluespace = TRUE
|
||||
return_pod.explosionSize = list(0,0,0,0)
|
||||
return_pod.style = STYLE_SYNDICATE
|
||||
do_sparks(8, FALSE, M)
|
||||
M.visible_message("<span class='notice'>[M] vanishes...</span>")
|
||||
for(var/obj/item/W in M)
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(W == H.w_uniform || W == H.shoes)
|
||||
continue //So all they're left with are shoes and uniform.
|
||||
M.dropItemToGround(W)
|
||||
for(var/obj/item/W in victim_belongings)
|
||||
W.forceMove(return_pod)
|
||||
M.forceMove(return_pod)
|
||||
M.flash_act()
|
||||
M.blur_eyes(30)
|
||||
M.Dizzy(35)
|
||||
M.confused += 20
|
||||
new /obj/effect/abstract/DPtarget(possible_drop_loc[pod_rand_loc], return_pod)
|
||||
else
|
||||
to_chat(M, "<span class='reallybig hypnophrase'>A million voices echo in your head... <i>\"Seems where you got sent here from won't \
|
||||
be able to handle our pod... You will die here instead.\"</i></span>")
|
||||
if(iscarbon(M))
|
||||
var/mob/living/carbon/C = M
|
||||
if(C.can_heartattack())
|
||||
C.set_heartattack(TRUE)
|
||||
@@ -261,7 +261,7 @@
|
||||
GiveHint(target)
|
||||
else if(is_pointed(I))
|
||||
to_chat(target, "<span class='userdanger'>You feel a stabbing pain in [parse_zone(user.zone_selected)]!</span>")
|
||||
target.Knockdown(40)
|
||||
target.DefaultCombatKnockdown(40)
|
||||
GiveHint(target)
|
||||
else if(istype(I, /obj/item/bikehorn))
|
||||
to_chat(target, "<span class='userdanger'>HONK</span>")
|
||||
@@ -377,7 +377,10 @@
|
||||
/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user)
|
||||
user.invisibility = initial(user.invisibility)
|
||||
user.status_flags &= ~GODMODE
|
||||
user.canmove = TRUE
|
||||
REMOVE_TRAIT(user, TRAIT_MOBILITY_NOMOVE, src)
|
||||
REMOVE_TRAIT(user, TRAIT_MOBILITY_NOUSE, src)
|
||||
REMOVE_TRAIT(user, TRAIT_MOBILITY_NOPICKUP, src)
|
||||
user.update_mobility()
|
||||
|
||||
/obj/item/warpwhistle/attack_self(mob/living/carbon/user)
|
||||
if(!istype(user) || on_cooldown)
|
||||
@@ -390,7 +393,10 @@
|
||||
on_cooldown = TRUE
|
||||
last_user = user
|
||||
playsound(T,'sound/magic/warpwhistle.ogg', 200, 1)
|
||||
user.canmove = FALSE
|
||||
ADD_TRAIT(user, TRAIT_MOBILITY_NOMOVE, src)
|
||||
ADD_TRAIT(user, TRAIT_MOBILITY_NOUSE, src)
|
||||
ADD_TRAIT(user, TRAIT_MOBILITY_NOPICKUP, src)
|
||||
user.update_mobility()
|
||||
new /obj/effect/temp_visual/tornado(T)
|
||||
sleep(20)
|
||||
if(interrupted(user))
|
||||
@@ -412,7 +418,6 @@
|
||||
return
|
||||
if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout)
|
||||
do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC)
|
||||
user.canmove = 0
|
||||
T = potential_T
|
||||
break
|
||||
breakout += 1
|
||||
|
||||
@@ -83,8 +83,8 @@
|
||||
/obj/item/soulstone/proc/release_shades(mob/user)
|
||||
for(var/mob/living/simple_animal/shade/A in src)
|
||||
A.status_flags &= ~GODMODE
|
||||
A.canmove = TRUE
|
||||
A.forceMove(get_turf(user))
|
||||
A.mobility_flags = MOBILITY_FLAGS_DEFAULT
|
||||
A.cancel_camera()
|
||||
icon_state = "soulstone"
|
||||
name = initial(name)
|
||||
@@ -173,7 +173,7 @@
|
||||
else
|
||||
T.forceMove(src) //put shade in stone
|
||||
T.status_flags |= GODMODE
|
||||
T.canmove = FALSE
|
||||
T.mobility_flags = NONE
|
||||
T.health = T.maxHealth
|
||||
icon_state = "soulstone2"
|
||||
name = "soulstone: Shade of [T.real_name]"
|
||||
@@ -240,8 +240,8 @@
|
||||
T.dust_animation()
|
||||
QDEL_IN(T, 5)
|
||||
var/mob/living/simple_animal/shade/S = new /mob/living/simple_animal/shade(src)
|
||||
S.status_flags |= GODMODE //So they won't die inside the stone somehow
|
||||
S.canmove = FALSE//Can't move out of the soul stone
|
||||
S.status_flags |= GODMODE //So they won't die inside the stone somehow
|
||||
S.mobility_flags = NONE //Can't move out of the soul stone
|
||||
S.name = "Shade of [T.real_name]"
|
||||
S.real_name = "Shade of [T.real_name]"
|
||||
T.transfer_ckey(S)
|
||||
|
||||
Reference in New Issue
Block a user