diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 5b8074a4658..d3655b164f5 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -124,6 +124,17 @@
#define WEAPON_MEDIUM 2
#define WEAPON_HEAVY 3
+//His Grace.
+#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep.
+#define HIS_GRACE_PECKISH 20 //Slightly hungry.
+#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20.
+#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped.
+#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30.
+#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive.
+#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets.
+
+#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill.
+
#define EXPLODE_NONE 0 //Don't even ask me why we need this.
#define EXPLODE_DEVASTATE 1
#define EXPLODE_HEAVY 2
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index ce91d2ccf31..aa4b044ed0f 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -13,6 +13,8 @@
// BUFFS //
///////////
+#define STATUS_EFFECT_HISGRACE /datum/status_effect/his_grace //His Grace.
+
#define STATUS_EFFECT_SHADOW_MEND /datum/status_effect/shadow_mend //Quick, powerful heal that deals damage afterwards. Heals 15 brute/burn every second for 3 seconds.
#define STATUS_EFFECT_VOID_PRICE /datum/status_effect/void_price //The price of healing yourself with void energy. Deals 3 brute damage every 3 seconds for 30 seconds.
#define STATUS_EFFECT_EXERCISED /datum/status_effect/exercised //Prevents heart disease
@@ -27,7 +29,6 @@
//#define STATUS_EFFECT_POWERREGEN /datum/status_effect/cyborg_power_regen //Regenerates power on a given cyborg over time
-//#define STATUS_EFFECT_HISGRACE /datum/status_effect/his_grace //His Grace.
//#define STATUS_EFFECT_WISH_GRANTERS_GIFT /datum/status_effect/wish_granters_gift //If you're currently resurrecting with the Wish Granter
@@ -53,7 +54,7 @@
//#define MAX_MANIA_SEVERITY 100 //how high the mania severity can go
//#define MANIA_DAMAGE_TO_CONVERT 90 //how much damage is required before it'll convert affected targets
-//#define STATUS_EFFECT_HISWRATH /datum/status_effect/his_wrath //His Wrath.
+#define STATUS_EFFECT_HISWRATH /datum/status_effect/his_wrath //His Wrath.
#define STATUS_EFFECT_SUMMONEDGHOST /datum/status_effect/cultghost //is a cult ghost: can see dead people, can't manifest more ghosts
diff --git a/code/__HELPERS/AnimationLibrary.dm b/code/__HELPERS/AnimationLibrary.dm
index 1f0c3dd9ab1..738783ed826 100644
--- a/code/__HELPERS/AnimationLibrary.dm
+++ b/code/__HELPERS/AnimationLibrary.dm
@@ -188,3 +188,21 @@
animate(A, transform = matrix(punchstr, MATRIX_ROTATE), pixel_y = 16, time = 2, color = "#eeeeee", easing = BOUNCE_EASING)
animate(transform = matrix(-punchstr, MATRIX_ROTATE), pixel_y = original_y, time = 2, color = "#ffffff", easing = BOUNCE_EASING)
animate(transform = null, time = 3, easing = BOUNCE_EASING)
+
+/proc/animate_rumble(atom/A)
+ var/static/list/transforms
+ if(!transforms)
+ var/matrix/M1 = matrix()
+ var/matrix/M2 = matrix()
+ var/matrix/M3 = matrix()
+ var/matrix/M4 = matrix()
+ M1.Translate(-1, 0)
+ M2.Translate(0, 1)
+ M3.Translate(1, 0)
+ M4.Translate(0, -1)
+ transforms = list(M1, M2, M3, M4)
+
+ animate(A, transform = transforms[1], time = 0.2, loop = -1)
+ animate(transform = transforms[2], time = 0.1)
+ animate(transform = transforms[3], time = 0.2)
+ animate(transform = transforms[4], time = 0.3)
diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm
index 0520ab6b077..414a16636ec 100644
--- a/code/__HELPERS/matrices.dm
+++ b/code/__HELPERS/matrices.dm
@@ -3,7 +3,7 @@
Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
-/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3)
+/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE)
if(!segments)
return
var/segment = 360/segments
@@ -19,7 +19,10 @@
speed /= segments
- animate(src, transform = matrices[1], time = speed, loops)
+ if(parallel)
+ animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL)
+ else
+ animate(src, transform = matrices[1], time = speed, loops)
for(var/i in 2 to segments) //2 because 1 is covered above
animate(transform = matrices[i], time = speed)
//doesn't have an object argument because this is "Stacking" with the animate call above
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 8b4cea27947..688db849a33 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -464,12 +464,15 @@ Turf and target are seperate in case you want to teleport some distance from a t
if(GLOB.stealthminID[P] == txt)
return P
-// Returns the atom sitting on the turf.
-// For example, using this on a disk, which is in a bag, on a mob, will return the mob because it's on the turf.
-/proc/get_atom_on_turf(atom/movable/M)
+//Returns the atom sitting on the turf.
+//For example, using this on a disk, which is in a bag, on a mob, will return the mob because it's on the turf.
+//Optional arg 'type' to stop once it reaches a specific type instead of a turf.
+/proc/get_atom_on_turf(atom/movable/M, stop_type)
var/atom/loc = M
- while(loc && loc.loc && !istype(loc.loc, /turf/))
+ while(loc?.loc && !isturf(loc.loc))
loc = loc.loc
+ if(stop_type && istype(loc, stop_type))
+ break
return loc
/*
@@ -1536,7 +1539,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
shift.Translate(0,radius)
transform = shift
- SpinAnimation(rotation_speed, -1, clockwise, rotation_segments)
+ SpinAnimation(rotation_speed, -1, clockwise, rotation_segments, parallel = FALSE)
while(orbiting && orbiting == A && A.loc)
var/targetloc = get_turf(A)
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 0494e14d647..54735988b9c 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -1,5 +1,58 @@
//Largely beneficial effects go here, even if they have drawbacks. An example is provided in Shadow Mend.
+/datum/status_effect/his_grace
+ id = "his_grace"
+ duration = -1
+ tick_interval = 4
+ alert_type = /obj/screen/alert/status_effect/his_grace
+ var/bloodlust = 0
+
+/obj/screen/alert/status_effect/his_grace
+ name = "His Grace"
+ desc = "His Grace hungers, and you must feed Him."
+ icon_state = "his_grace"
+ alerttooltipstyle = "hisgrace"
+
+/obj/screen/alert/status_effect/his_grace/MouseEntered(location, control, params)
+ desc = initial(desc)
+ var/datum/status_effect/his_grace/HG = attached_effect
+ desc += "
Current Bloodthirst: [HG.bloodlust]\
+
Becomes undroppable at [HIS_GRACE_FAMISHED]\
+
Will consume you at [HIS_GRACE_CONSUME_OWNER]"
+ ..()
+
+/datum/status_effect/his_grace/on_apply()
+ add_attack_logs(owner, owner, "gained His Grace's stun immunity", ATKLOG_ALL)
+ owner.add_stun_absorption("hisgrace", INFINITY, 3, null, "His Grace protects you from the stun!")
+ return ..()
+
+/datum/status_effect/his_grace/tick()
+ bloodlust = 0
+ var/graces = 0
+ var/list/held_items = list()
+ held_items += owner.l_hand
+ held_items += owner.r_hand
+ for(var/obj/item/his_grace/HG in held_items)
+ if(HG.bloodthirst > bloodlust)
+ bloodlust = HG.bloodthirst
+ if(HG.awakened)
+ graces++
+ if(!graces)
+ owner.apply_status_effect(STATUS_EFFECT_HISWRATH)
+ qdel(src)
+ return
+ var/grace_heal = bloodlust * 0.05
+ owner.adjustBruteLoss(-grace_heal)
+ owner.adjustFireLoss(-grace_heal)
+ owner.adjustToxLoss(-grace_heal)
+ owner.adjustOxyLoss(-(grace_heal * 2))
+ owner.adjustCloneLoss(-grace_heal)
+
+/datum/status_effect/his_grace/on_remove()
+ add_attack_logs(owner, owner, "lost His Grace's stun immunity", ATKLOG_ALL)
+ if(islist(owner.stun_absorption) && owner.stun_absorption["hisgrace"])
+ owner.stun_absorption -= "hisgrace"
+
/datum/status_effect/shadow_mend
id = "shadow_mend"
duration = 30
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index d8b5e4d9cbe..498bb042497 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -1,5 +1,28 @@
//OTHER DEBUFFS
+/datum/status_effect/his_wrath //does minor damage over time unless holding His Grace
+ id = "his_wrath"
+ duration = -1
+ tick_interval = 4
+ alert_type = /obj/screen/alert/status_effect/his_wrath
+
+/obj/screen/alert/status_effect/his_wrath
+ name = "His Wrath"
+ desc = "You fled from His Grace instead of feeding Him, and now you suffer."
+ icon_state = "his_grace"
+ alerttooltipstyle = "hisgrace"
+
+/datum/status_effect/his_wrath/tick()
+ var/list/held_items = list()
+ held_items += owner.l_hand
+ held_items += owner.r_hand
+ for(var/obj/item/his_grace/HG in held_items)
+ qdel(src)
+ return
+ owner.adjustBruteLoss(0.1)
+ owner.adjustFireLoss(0.1)
+ owner.adjustToxLoss(0.2)
+
/datum/status_effect/cultghost //is a cult ghost and can't use manifest runes
id = "cult_ghost"
duration = -1
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index b2eb0ba224d..f8f2fe512f6 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -257,10 +257,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
job = list("Chaplain")
/datum/uplink_item/jobspecific/artistic_toolbox
- name = "Artistic Toolbox"
- desc = "An accursed toolbox that grants its followers extreme power at the cost of requiring repeated sacrifices to it. If sacrifices are not provided, it will turn on its follower."
- reference = "HGAT"
- item = /obj/item/storage/toolbox/green/memetic
+ name = "His Grace"
+ desc = "An incredibly dangerous weapon recovered from a station overcome by the grey tide. Once activated, He will thirst for blood and must be used to kill to sate that thirst. \
+ His Grace grants gradual regeneration and complete stun immunity to His wielder, but be wary: if He gets too hungry, He will become impossible to drop and eventually kill you if not fed. \
+ However, if left alone for long enough, He will fall back to slumber. \
+ To activate His Grace, simply unlatch Him."
+ reference = "HG"
+ item = /obj/item/his_grace
cost = 20
job = list("Chaplain")
surplus = 0 //No lucky chances from the crate; if you get this, this is ALL you're getting
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 7232239d6f5..bac3f4c3349 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -420,6 +420,7 @@
if(spin && !no_spin && !no_spin_thrown)
SpinAnimation(5, 1)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin)
SSthrowing.processing[src] = TT
TT.tick()
diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm
new file mode 100644
index 00000000000..e86d82fef36
--- /dev/null
+++ b/code/game/objects/items/his_grace.dm
@@ -0,0 +1,263 @@
+//His Grace is a very special weapon granted only to traitor chaplains.
+//When awakened, He thirsts for blood and begins ticking a "bloodthirst" counter.
+//The wielder of His Grace is immune to stuns and gradually heals.
+//If the wielder fails to feed His Grace in time, He will devour them and become incredibly aggressive.
+//Leaving His Grace alone for some time will reset His thirst and put Him to sleep.
+//Using His Grace effectively requires extreme speed and care.
+
+/obj/item/his_grace
+ name = "artistic toolbox"
+ desc = "A toolbox painted bright green. Looking at it makes you feel uneasy."
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "green"
+ item_state = "artistic_toolbox"
+ lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi'
+ w_class = WEIGHT_CLASS_GIGANTIC
+ force = 12
+ attack_verb = list("robusted")
+ hitsound = 'sound/weapons/smash.ogg'
+ drop_sound = 'sound/items/handling/toolbox_drop.ogg'
+ pickup_sound = 'sound/items/handling/toolbox_pickup.ogg'
+ var/awakened = FALSE
+ var/bloodthirst = HIS_GRACE_SATIATED
+ var/prev_bloodthirst = HIS_GRACE_SATIATED
+ var/force_bonus = 0
+ var/ascended = FALSE
+ var/victims_needed = 25
+ var/ascend_bonus = 15
+
+/obj/item/his_grace/Initialize(mapload)
+ . = ..()
+ START_PROCESSING(SSprocessing, src)
+ GLOB.poi_list |= src
+ RegisterSignal(src, COMSIG_MOVABLE_POST_THROW, .proc/move_gracefully)
+ update_icon()
+
+/obj/item/his_grace/Destroy()
+ STOP_PROCESSING(SSprocessing, src)
+ for(var/mob/living/L in src)
+ L.forceMove(get_turf(src))
+ GLOB.poi_list -= src
+ return ..()
+
+/obj/item/his_grace/update_icon()
+ icon_state = ascended ? "gold" : "green"
+ item_state = ascended ? "toolbox_gold" : "artistic_toolbox"
+ cut_overlays()
+ if(ascended)
+ add_overlay("triple_latch")
+ else if(awakened)
+ add_overlay("single_latch_open")
+ else
+ add_overlay("single_latch")
+
+/obj/item/his_grace/attack_self(mob/living/user)
+ if(!awakened)
+ INVOKE_ASYNC(src, .proc/awaken, user)
+
+/obj/item/his_grace/attack(mob/living/M, mob/user)
+ if(awakened && M.stat)
+ consume(M)
+ else
+ ..()
+
+/obj/item/his_grace/can_be_pulled(user, grab_state, force, show_message = FALSE) //you can't pull his grace
+ return FALSE
+
+/obj/item/his_grace/examine(mob/user)
+ . = ..()
+ if(awakened)
+ switch(bloodthirst)
+ if(HIS_GRACE_SATIATED to HIS_GRACE_PECKISH)
+ . += "[src] isn't very hungry. Not yet."
+ if(HIS_GRACE_PECKISH to HIS_GRACE_HUNGRY)
+ . += "[src] would like a snack."
+ if(HIS_GRACE_HUNGRY to HIS_GRACE_FAMISHED)
+ . += "[src] is quite hungry now."
+ if(HIS_GRACE_FAMISHED to HIS_GRACE_STARVING)
+ . += "[src] is openly salivating at the sight of you. Be careful."
+ if(HIS_GRACE_STARVING to HIS_GRACE_CONSUME_OWNER)
+ . += "You walk a fine line. [src] is very close to devouring you."
+ if(HIS_GRACE_CONSUME_OWNER to HIS_GRACE_FALL_ASLEEP)
+ . += "[src] is shaking violently and staring directly at you."
+ else
+ . += "[src] is latched closed."
+
+/obj/item/his_grace/relaymove(mob/living/user, direction) //Allows changelings, etc. to climb out of Him after they revive, provided He isn't active
+ if(!awakened)
+ user.forceMove(get_turf(src))
+ user.visible_message("[user] scrambles out of [src]!", "You climb out of [src]!")
+
+/obj/item/his_grace/process()
+ if(!bloodthirst)
+ drowse()
+ return
+ if(bloodthirst < HIS_GRACE_CONSUME_OWNER && !ascended)
+ adjust_bloodthirst(1 + FLOOR(length(contents) * 0.5, 1)) //Maybe adjust this?
+ else
+ adjust_bloodthirst(1) //don't cool off rapidly once we're at the point where His Grace consumes all.
+ var/mob/living/master = get_atom_on_turf(src, /mob/living)
+ var/list/held_items = list()
+ if(istype(master))
+ held_items += master.l_hand
+ held_items += master.r_hand
+ if(istype(master) && (src in held_items))
+ switch(bloodthirst)
+ if(HIS_GRACE_CONSUME_OWNER to HIS_GRACE_FALL_ASLEEP)
+ master.visible_message("[src] turns on [master]!", "[src] turns on you!")
+ do_attack_animation(master, null, src)
+ master.emote("scream")
+ master.remove_status_effect(STATUS_EFFECT_HISGRACE)
+ flags &= ~NODROP
+ master.Weaken(3)
+ master.adjustBruteLoss(1000)
+ playsound(master, 'sound/effects/splat.ogg', 100, FALSE)
+ else
+ master.apply_status_effect(STATUS_EFFECT_HISGRACE)
+ return
+ forceMove(get_turf(src)) //no you can't put His Grace in a locker you just have to deal with Him
+ if(bloodthirst < HIS_GRACE_CONSUME_OWNER)
+ return
+ if(bloodthirst >= HIS_GRACE_FALL_ASLEEP)
+ drowse()
+ return
+ var/list/targets = list()
+ for(var/mob/living/L in oview(2, src))
+ targets += L
+ if(!length(targets))
+ return
+ var/mob/living/L = pick(targets)
+ step_to(src, L)
+ if(Adjacent(L))
+ if(!L.stat)
+ L.visible_message("[src] lunges at [L]!", "[src] lunges at you!")
+ do_attack_animation(L, null, src)
+ playsound(L, 'sound/weapons/smash.ogg', 50, TRUE)
+ playsound(L, 'sound/misc/desceration-01.ogg', 50, TRUE)
+ L.adjustBruteLoss(force)
+ adjust_bloodthirst(-5) //Don't stop attacking they're right there!
+ else
+ consume(L)
+
+/obj/item/his_grace/proc/awaken(mob/user) //Good morning, Mr. Grace.
+ if(awakened)
+ return
+ awakened = TRUE
+ user.visible_message("[src] begins to rattle. He thirsts.", "You flick [src]'s latch up. You hope this is a good idea.")
+ name = "His Grace"
+ desc = "A bloodthirsty artifact created by a profane rite."
+ gender = MALE
+ adjust_bloodthirst(1)
+ force_bonus = HIS_GRACE_FORCE_BONUS * length(contents)
+ playsound(user, 'sound/effects/pope_entry.ogg', 100)
+ update_icon()
+ move_gracefully()
+
+/obj/item/his_grace/proc/move_gracefully()
+ SIGNAL_HANDLER
+
+ if(!awakened)
+ return
+ animate_rumble(src)
+
+/obj/item/his_grace/proc/drowse() //Good night, Mr. Grace.
+ if(!awakened || ascended)
+ return
+ var/turf/T = get_turf(src)
+ T.visible_message("[src] slowly stops rattling and falls still, His latch snapping shut.")
+ playsound(loc, 'sound/weapons/batonextend.ogg', 100, TRUE)
+ name = initial(name)
+ desc = initial(desc)
+ animate(src, transform = matrix())
+ gender = initial(gender)
+ force = initial(force)
+ force_bonus = initial(force_bonus)
+ awakened = FALSE
+ bloodthirst = 0
+ update_icon()
+
+/obj/item/his_grace/proc/consume(mob/living/meal) //Here's your dinner, Mr. Grace.
+ if(!meal)
+ return
+ var/victims = 0
+ meal.visible_message("[src] swings open and devours [meal]!", "[src] consumes you!")
+ meal.adjustBruteLoss(300)
+ playsound(meal, 'sound/misc/desceration-02.ogg', 75, TRUE)
+ playsound(src, 'sound/items/eatfood.ogg', 100, TRUE)
+ meal.forceMove(src)
+ force_bonus += HIS_GRACE_FORCE_BONUS
+ prev_bloodthirst = bloodthirst
+ if(prev_bloodthirst < HIS_GRACE_CONSUME_OWNER)
+ bloodthirst = max(length(contents), 1) //Never fully sated, and His hunger will only grow.
+ else
+ bloodthirst = HIS_GRACE_CONSUME_OWNER
+ for(var/mob/living/C in contents)
+ if(C.mind)
+ victims++
+ if(victims >= victims_needed)
+ ascend()
+ update_stats()
+
+/obj/item/his_grace/proc/adjust_bloodthirst(amt)
+ prev_bloodthirst = bloodthirst
+ if(prev_bloodthirst < HIS_GRACE_CONSUME_OWNER && !ascended)
+ bloodthirst = clamp(bloodthirst + amt, HIS_GRACE_SATIATED, HIS_GRACE_CONSUME_OWNER)
+ else if(!ascended)
+ bloodthirst = clamp(bloodthirst + amt, HIS_GRACE_CONSUME_OWNER, HIS_GRACE_FALL_ASLEEP)
+ update_stats()
+
+/obj/item/his_grace/proc/update_stats()
+ flags &= ~NODROP
+ var/mob/living/master = get_atom_on_turf(src, /mob/living)
+ switch(bloodthirst)
+ if(HIS_GRACE_CONSUME_OWNER to HIS_GRACE_FALL_ASLEEP)
+ if(HIS_GRACE_CONSUME_OWNER > prev_bloodthirst)
+ master.visible_message("[src] enters a frenzy!")
+ if(HIS_GRACE_STARVING to HIS_GRACE_CONSUME_OWNER)
+ flags |= NODROP
+ if(HIS_GRACE_STARVING > prev_bloodthirst)
+ master.visible_message("[src] is starving!", "[src]'s bloodlust overcomes you. [src] must be fed, or you will become His meal.\
+ [force_bonus < 15 ? " And still, His power grows.":""]")
+ force_bonus = max(force_bonus, 15)
+ if(HIS_GRACE_FAMISHED to HIS_GRACE_STARVING)
+ flags |= NODROP
+ if(HIS_GRACE_FAMISHED > prev_bloodthirst)
+ master.visible_message("[src] is very hungry!", "Spines sink into your hand. [src] must feed immediately.\
+ [force_bonus < 10 ? " His power grows.":""]")
+ force_bonus = max(force_bonus, 10)
+ if(prev_bloodthirst >= HIS_GRACE_STARVING)
+ master.visible_message("[src] is now only very hungry!", "Your bloodlust recedes.")
+ if(HIS_GRACE_HUNGRY to HIS_GRACE_FAMISHED)
+ if(HIS_GRACE_HUNGRY > prev_bloodthirst)
+ master.visible_message("[src] is getting hungry.", "You feel [src]'s hunger within you.\
+ [force_bonus < 5 ? " His power grows.":""]")
+ force_bonus = max(force_bonus, 5)
+ if(prev_bloodthirst >= HIS_GRACE_FAMISHED)
+ master.visible_message("[src] is now only somewhat hungry.", "[src]'s hunger recedes a little...")
+ if(HIS_GRACE_PECKISH to HIS_GRACE_HUNGRY)
+ if(HIS_GRACE_PECKISH > prev_bloodthirst)
+ master.visible_message("[src] is feeling snackish.", "[src] begins to hunger.")
+ if(prev_bloodthirst >= HIS_GRACE_HUNGRY)
+ master.visible_message("[src] is now only a little peckish.", "[src]'s hunger recedes somewhat...")
+ if(HIS_GRACE_SATIATED to HIS_GRACE_PECKISH)
+ if(prev_bloodthirst >= HIS_GRACE_PECKISH)
+ master.visible_message("[src] is satiated.", "[src]'s hunger recedes...")
+ force = initial(force) + force_bonus
+
+/obj/item/his_grace/proc/ascend()
+ if(ascended)
+ return
+ force_bonus += ascend_bonus
+ desc = "A legendary toolbox and a distant artifact from The Age of Three Powers. On its three latches engraved are the words \"The Sun\", \"The Moon\", and \"The Stars\". The entire toolbox has the words \"The World\" engraved into its sides."
+ icon_state = "his_grace_ascended"
+ item_state = "toolbox_gold"
+ ascended = TRUE
+ update_icon()
+ playsound(src, 'sound/effects/his_grace_ascend.ogg', 100)
+ var/mob/living/carbon/human/master = loc
+ if(istype(master))
+ master.visible_message("Gods will be watching.")
+ name = "[master]'s mythical toolbox of three powers"
+ master.update_inv_l_hand()
+ master.update_inv_r_hand()
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 4b7ec1b350b..f2e006e24c8 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -1066,6 +1066,50 @@
w_class = WEIGHT_CLASS_SMALL
resistance_flags = FLAMMABLE
+/obj/item/toy/windup_toolbox
+ name = "windup toolbox"
+ desc = "A replica toolbox that rumbles when you turn the key."
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "green"
+ item_state = "artistic_toolbox"
+ lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi'
+ hitsound = 'sound/weapons/smash.ogg'
+ drop_sound = 'sound/items/handling/toolbox_drop.ogg'
+ pickup_sound = 'sound/items/handling/toolbox_pickup.ogg'
+ attack_verb = list("robusted")
+ var/active = FALSE
+
+/obj/item/toy/windup_toolbox/Initialize(mapload)
+ . = ..()
+ update_icon()
+
+/obj/item/toy/windup_toolbox/update_icon()
+ ..()
+ cut_overlays()
+ if(active)
+ add_overlay("single_latch_open")
+ else
+ add_overlay("single_latch")
+
+/obj/item/toy/windup_toolbox/attack_self(mob/user)
+ if(!active)
+ to_chat(user, "You wind up [src], it begins to rumble.")
+ active = TRUE
+ update_icon()
+ playsound(src, 'sound/effects/pope_entry.ogg', 100)
+ animate_rumble(src)
+ addtimer(CALLBACK(src, .proc/stopRumble), 60 SECONDS)
+ else
+ to_chat(user, "[src] is already active!")
+
+/obj/item/toy/windup_toolbox/proc/stopRumble()
+ active = FALSE
+ update_icon()
+ visible_message("[src] slowly stops rattling and falls still, its latch snapping shut.") //subtle difference
+ playsound(loc, 'sound/weapons/batonextend.ogg', 100, TRUE)
+ animate(src, transform = matrix())
+
/*
* Toy/fake flash
*/
diff --git a/code/game/objects/items/weapons/storage/artistic_toolbox.dm b/code/game/objects/items/weapons/storage/artistic_toolbox.dm
deleted file mode 100644
index 3fbaa1d468d..00000000000
--- a/code/game/objects/items/weapons/storage/artistic_toolbox.dm
+++ /dev/null
@@ -1,223 +0,0 @@
-/obj/item/storage/toolbox/green
- name = "artistic toolbox"
- desc = "A metal container designed to hold various tools. This variety holds art supplies."
- icon_state = "green"
- item_state = "artistic_toolbox"
-
-/obj/item/storage/toolbox/green/memetic
- name = "artistic toolbox"
- desc = "His Grace."
- force = 5
- throwforce = 10
- origin_tech = "combat=4;engineering=4;syndicate=2"
- actions_types = list(/datum/action/item_action/toggle)
- var/list/servantlinks = list()
- var/hunger = 0
- var/hunger_message_level = 0
- var/mob/living/carbon/human/original_owner = null
- var/activated = FALSE
-
-/obj/item/storage/toolbox/green/memetic/ui_action_click(mob/user)
- if(user.HasDisease(new /datum/disease/memetic_madness(0)))
- var/obj/item/storage/toolbox/green/memetic/M = user.get_active_hand()
- if(istype(M))
- to_chat(user, "His Grace [flags & NODROP ? "releases from" : "binds to"] your hand!")
- flags ^= NODROP
- else if(!activated && loc == user)
- if(link_user(user))
- to_chat(user, "Call to His Grace again if you wish it bound to your hand!")
- else
- to_chat(user, "You can't seem to understand what this does.")
-
-
-/obj/item/storage/toolbox/green/memetic/attack_hand(mob/living/carbon/user)
- if(!activated && loc == user)
- link_user(user)
- return
- ..()
-
-/obj/item/storage/toolbox/green/memetic/proc/link_user(mob/living/carbon/user)
- if(ishuman(user) && !user.HasDisease(new /datum/disease/memetic_madness(0)))
- activated = TRUE
- user.ForceContractDisease(new /datum/disease/memetic_madness(0))
- for(var/datum/disease/memetic_madness/DD in user.viruses)
- DD.progenitor = src
- servantlinks.Add(DD)
- break
- force += 4
- throwforce += 4
- SEND_SOUND(user, sound('sound/goonstation/effects/screech.ogg'))
- shake_camera(user, 20, 1)
- var/acount = 0
- var/amax = rand(10, 15)
- var/up_and_down
- var/asize = 1
- while(acount <= amax)
- up_and_down += "a"
- if(acount > (amax * 0.5))
- asize--
- else
- asize++
- acount++
- to_chat(user, "[up_and_down]")
- to_chat(user, "His Grace accepts thee, spread His will! All who look close to the Enlightened may share His gifts.")
- original_owner = user
- return TRUE
- return FALSE
-
-/obj/item/storage/toolbox/green/memetic/attackby(obj/item/I, mob/user)
- if(activated)
- if(istype(I, /obj/item/grab))
- var/obj/item/grab/G = I
- var/mob/living/victim = G.affecting
- if(!user.HasDisease(new /datum/disease/memetic_madness(0)))
- to_chat(user, "You can't seem to find the latch to open this.")
- return
- if(!victim)
- return
- if(!victim.stat && !victim.restrained() && !victim.IsWeakened())
- to_chat(user, "They're moving too much to feed to His Grace!")
- return
- user.visible_message("[user] is trying to feed [victim] to [src]!")
- if(!do_mob(user, victim, 30))
- return
-
- user.visible_message("[user] has fed [victim] to [src]!")
-
- consume(victim)
- qdel(G)
-
- to_chat(user, "You have done well...")
- force += 5
- throwforce += 5
- return
-
- return ..()
-
-/obj/item/storage/toolbox/green/memetic/proc/consume(mob/living/L)
- if(!L)
- return
- hunger = 0
- hunger_message_level = 0
- playsound(loc, 'sound/goonstation/misc/burp_alien.ogg', 50, 0)
-
- if(L != original_owner)
- var/list/equipped_items = L.get_equipped_items(TRUE)
- if(L.l_hand)
- equipped_items += L.l_hand
- if(L.r_hand)
- equipped_items += L.r_hand
- if(equipped_items.len)
- var/obj/item/storage/box/B = new(src)
- B.name = "Box-'[L.real_name]'"
- for(var/obj/item/SI in equipped_items)
- L.unEquip(SI, TRUE)
- SI.forceMove(B)
- equipped_items.Cut()
-
- L.forceMove(src)
-
- L.emote("scream")
- L.death()
- L.ghostize()
- if(L == original_owner)
- L.unEquip(src, TRUE)
- qdel(L)
- var/obj/item/storage/toolbox/green/fake_toolbox = new(get_turf(src))
- fake_toolbox.desc = "It looks a lot duller than it used to."
- qdel(src)
- else
- qdel(L)
-
-/obj/item/storage/toolbox/green/memetic/Destroy()
- for(var/datum/disease/memetic_madness/D in servantlinks)
- D.cure()
-
- servantlinks.Cut()
- servantlinks = null
- original_owner = null
- visible_message("[src] screams!")
- playsound(loc, 'sound/goonstation/effects/screech.ogg', 100, 1)
- return ..()
-
-/datum/disease/memetic_madness
- name = "Memetic Kill Agent"
- max_stages = 4
- stage_prob = 8
- spread_text = "Non-Contagious"
- spread_flags = SPECIAL
- cure_text = "Unknown"
- viable_mobtypes = list(/mob/living/carbon/human)
- severity = BIOHAZARD
- disease_flags = CAN_CARRY
- spread_flags = NON_CONTAGIOUS
- virus_heal_resistant = TRUE
- var/obj/item/storage/toolbox/green/memetic/progenitor = null
-
-/datum/disease/memetic_madness/Destroy()
- if(progenitor)
- progenitor.servantlinks.Remove(src)
- progenitor = null
- if(affected_mob)
- affected_mob.status_flags |= CANSTUN | CANWEAKEN | CANPARALYSE
- return ..()
-
-/datum/disease/memetic_madness/stage_act()
- ..()
- if(!progenitor) //if someone admin spawns this, cure it right away; this should only ever be given directly from the toolbox itself.
- cure()
- return
- if(progenitor in affected_mob.contents)
- affected_mob.adjustOxyLoss(-5)
- affected_mob.adjustBruteLoss(-12)
- affected_mob.adjustFireLoss(-12)
- affected_mob.adjustToxLoss(-5)
- affected_mob.setStaminaLoss(0)
- var/status = CANSTUN | CANWEAKEN | CANPARALYSE
- affected_mob.status_flags &= ~status
- affected_mob.AdjustDizzy(-10)
- affected_mob.AdjustDrowsy(-10)
- affected_mob.SetSleeping(0)
- affected_mob.SetSlowed(0)
- affected_mob.SetConfused(0)
- stage = 1
- switch(progenitor.hunger)
- if(10 to 60)
- if(progenitor.hunger_message_level < 1)
- progenitor.hunger_message_level = 1
- to_chat(affected_mob, "Feed Me the unclean ones...They will be purified...")
- if(61 to 120)
- if(progenitor.hunger_message_level < 2)
- progenitor.hunger_message_level = 2
- to_chat(affected_mob, "I hunger for the flesh of the impure...")
- if(121 to 210)
- if(prob(10) && progenitor.hunger_message_level < 3)
- progenitor.hunger_message_level = 3
- to_chat(affected_mob, "The hunger of your Master grows with every passing moment. Feed Me at once.")
- if(211 to 399)
- if(progenitor.hunger_message_level < 4)
- progenitor.hunger_message_level = 4
- to_chat(affected_mob, "His Grace starves in your hands. Feed Me the unclean or suffer.")
- if(400 to INFINITY)
- affected_mob.visible_message("[progenitor] consumes [affected_mob] whole!")
- progenitor.consume(affected_mob)
- return
-
- progenitor.hunger += min(max((progenitor.force / 10), 1), 10)
-
- else
- affected_mob.status_flags |= CANSTUN | CANWEAKEN | CANPARALYSE
-
- if(stage == 4)
- if(get_dist(get_turf(progenitor), get_turf(affected_mob)) <= 7)
- stage = 1
- return
- if(prob(4))
- to_chat(affected_mob, "You are too far from His Grace...")
- affected_mob.adjustToxLoss(5)
- else if(prob(6))
- to_chat(affected_mob, "You feel weak.")
- affected_mob.adjustBruteLoss(5)
-
- if(ismob(progenitor.loc))
- progenitor.hunger++
diff --git a/code/game/objects/items/weapons/storage/toolbox.dm b/code/game/objects/items/weapons/storage/toolbox.dm
index db3e97b4154..6e52acb93df 100644
--- a/code/game/objects/items/weapons/storage/toolbox.dm
+++ b/code/game/objects/items/weapons/storage/toolbox.dm
@@ -132,3 +132,24 @@
new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor)
new /obj/item/wirecutters(src)
new /obj/item/multitool(src)
+
+/obj/item/storage/toolbox/artistic
+ name = "artistic toolbox"
+ desc = "A toolbox painted bright green. Why anyone would store art supplies in a toolbox is beyond you, but it has plenty of extra space."
+ icon_state = "green"
+ item_state = "artistic_toolbox"
+ w_class = WEIGHT_CLASS_GIGANTIC //Holds more than a regular toolbox!
+ max_combined_w_class = 20
+ storage_slots = 10
+
+/obj/item/storage/toolbox/artistic/populate_contents()
+ new /obj/item/storage/fancy/crayons(src)
+ new /obj/item/crowbar(src)
+ new /obj/item/stack/cable_coil(src)
+ new /obj/item/stack/cable_coil/yellow(src)
+ new /obj/item/stack/cable_coil/blue(src)
+ new /obj/item/stack/cable_coil/green(src)
+ new /obj/item/stack/cable_coil/pink(src)
+ new /obj/item/stack/cable_coil/orange(src)
+ new /obj/item/stack/cable_coil/cyan(src)
+ new /obj/item/stack/cable_coil/white(src)
diff --git a/code/modules/arcade/prize_datums.dm b/code/modules/arcade/prize_datums.dm
index 8ffc59825cb..8be89339134 100644
--- a/code/modules/arcade/prize_datums.dm
+++ b/code/modules/arcade/prize_datums.dm
@@ -251,6 +251,12 @@ GLOBAL_DATUM_INIT(global_prizes, /datum/prizes, new())
typepath = /obj/item/toy/foamblade
cost = 100
+/datum/prize_item/wind_up_toolbox
+ name = "Wind Up Toolbox"
+ desc = "A replica toolbox that rumbles when you turn the key."
+ typepath = /obj/item/toy/windup_toolbox
+ cost = 100
+
/datum/prize_item/redbutton
name = "Shiny Red Button"
desc = "PRESS IT!"
diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm
index 615718bb834..ca0a4df5f69 100644
--- a/code/modules/mining/equipment/survival_pod.dm
+++ b/code/modules/mining/equipment/survival_pod.dm
@@ -323,7 +323,7 @@
/obj/item/lava_staff,
/obj/item/katana/energy,
/obj/item/hierophant_club,
- /obj/item/storage/toolbox/green/memetic,
+ /obj/item/his_grace,
/obj/item/gun/projectile/automatic/l6_saw,
/obj/item/gun/magic/staff/chaos,
/obj/item/gun/magic/staff/spellblade,
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index beca603f4cc..5abf3b185b5 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -214,7 +214,7 @@
var/list/allowed_toolbox = list(/obj/item/storage/toolbox/emergency, //which toolboxes can be made into floorbots
/obj/item/storage/toolbox/electrical,
/obj/item/storage/toolbox/mechanical,
- /obj/item/storage/toolbox/green,
+ /obj/item/storage/toolbox/artistic,
/obj/item/storage/toolbox/syndicate,
/obj/item/storage/toolbox/fakesyndi)
@@ -223,8 +223,6 @@
return
if(!is_type_in_list(src, allowed_toolbox))
return
- if(type == /obj/item/storage/toolbox/green/memetic)
- return
if(contents.len >= 1)
to_chat(user, "They won't fit in, as there is already stuff inside.")
return
@@ -242,7 +240,7 @@
B.toolbox_color = "or"
if(/obj/item/storage/toolbox/electrical)
B.toolbox_color = "y"
- if(/obj/item/storage/toolbox/green)
+ if(/obj/item/storage/toolbox/artistic)
B.toolbox_color = "g"
if(/obj/item/storage/toolbox/syndicate)
B.toolbox_color = "s"
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
index 5be97564787..352eae47ead 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
@@ -69,7 +69,7 @@ Difficulty: Medium
ranged_cooldown = world.time + ranged_cooldown_time
else
visible_message("[src] charges!")
- SpinAnimation(speed = 20, loops = 5)
+ SpinAnimation(speed = 20, loops = 5, parallel = FALSE)
ranged = 0
retreat_distance = 0
minimum_distance = 0
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index fab75c91507..c7a11c933b9 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -365,9 +365,6 @@
// STUN
/mob/living/Stun(amount, updating = 1, force = 0)
- if(status_flags & CANSTUN || force)
- if(absorb_stun(amount, force))
- return FALSE
return SetStunned(max(stunned, amount), updating, force)
/mob/living/SetStunned(amount, updating = 1, force = 0) //if you REALLY need to set stun to a set amount without the whole "can't go below current stunned"
@@ -377,6 +374,8 @@
. = STATUS_UPDATE_NONE
if(status_flags & CANSTUN || force)
+ if(absorb_stun(amount, force))
+ return STATUS_UPDATE_NONE
stunned = max(amount, 0)
if(updating)
update_canmove()
@@ -405,9 +404,6 @@
// WEAKEN
/mob/living/Weaken(amount, updating = 1, force = 0)
- if(status_flags & CANWEAKEN || force)
- if(absorb_stun(amount, force))
- return FALSE
return SetWeakened(max(weakened, amount), updating, force)
/mob/living/SetWeakened(amount, updating = 1, force = 0)
@@ -416,6 +412,8 @@
updating = FALSE
. = STATUS_UPDATE_NONE
if(status_flags & CANWEAKEN || force)
+ if(absorb_stun(amount, force))
+ return STATUS_UPDATE_NONE
weakened = max(amount, 0)
if(updating)
update_canmove() //updates lying, canmove and icons
diff --git a/code/modules/pda/PDA.dm b/code/modules/pda/PDA.dm
old mode 100644
new mode 100755
diff --git a/goon/browserassets/css/browserOutput-dark.css b/goon/browserassets/css/browserOutput-dark.css
index db879454cc4..bbee5d2625d 100644
--- a/goon/browserassets/css/browserOutput-dark.css
+++ b/goon/browserassets/css/browserOutput-dark.css
@@ -365,6 +365,7 @@ h1.alert, h2.alert {color: #FFF;}
.greentext {color: #00FF00; font-size: 150%;}
.redtext {color: #FF0000; font-size: 150%;}
.bold {font-weight: bold;}
+.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
.center {text-align: center;}
.red {color: #FF0000;}
.purple {color: #9031C4;}
diff --git a/goon/browserassets/css/browserOutput.css b/goon/browserassets/css/browserOutput.css
index f2de7748945..cc6ed7fdcb8 100644
--- a/goon/browserassets/css/browserOutput.css
+++ b/goon/browserassets/css/browserOutput.css
@@ -362,6 +362,7 @@ h1.alert, h2.alert {color: #000000;}
.greentext {color: #00FF00; font-size: 150%;}
.redtext {color: #FF0000; font-size: 150%;}
.bold {font-weight: bold;}
+.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
.center {text-align: center;}
.red {color: #FF0000;}
.purple {color: #5e2d79;}
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index 367a42a6c79..9ef222c0bff 100644
Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index 45adf5cc6e9..6ac7c19a6a3 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 7c5da49fd52..5c415a06568 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -851,6 +851,7 @@
#include "code\game\objects\items\documents.dm"
#include "code\game\objects\items\flag.dm"
#include "code\game\objects\items\hand_item.dm"
+#include "code\game\objects\items\his_grace.dm"
#include "code\game\objects\items\latexballoon.dm"
#include "code\game\objects\items\misc.dm"
#include "code\game\objects\items\mixing_bowl.dm"
@@ -1023,7 +1024,6 @@
#include "code\game\objects\items\weapons\implants\implantuplink.dm"
#include "code\game\objects\items\weapons\melee\energy.dm"
#include "code\game\objects\items\weapons\melee\misc.dm"
-#include "code\game\objects\items\weapons\storage\artistic_toolbox.dm"
#include "code\game\objects\items\weapons\storage\backpack.dm"
#include "code\game\objects\items\weapons\storage\bags.dm"
#include "code\game\objects\items\weapons\storage\belt.dm"
diff --git a/sound/effects/his_grace_ascend.ogg b/sound/effects/his_grace_ascend.ogg
new file mode 100644
index 00000000000..61866f2afc9
Binary files /dev/null and b/sound/effects/his_grace_ascend.ogg differ
diff --git a/sound/effects/pope_entry.ogg b/sound/effects/pope_entry.ogg
new file mode 100644
index 00000000000..54cf14fc4fc
Binary files /dev/null and b/sound/effects/pope_entry.ogg differ