diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index c211b501fc1..d7bb6714614 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -611,6 +611,8 @@
#define COMSIG_GRILL_COMPLETED "item_grill_completed"
///Called when an armor plate is successfully applied to an object
#define COMSIG_ARMOR_PLATED "armor_plated"
+///Called when an item gets recharged by the ammo powerup
+#define COMSIG_ITEM_RECHARGED "item_recharged"
///from base of [/obj/item/proc/tool_check_callback]: (mob/living/user)
#define COMSIG_TOOL_IN_USE "tool_in_use"
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 2c4a7bad3b0..57c9708bff4 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -33,6 +33,10 @@
#define STATUS_EFFECT_DETERMINED /datum/status_effect/determined //currently in a combat high from being seriously wounded
+#define STATUS_EFFECT_LIGHTNINGORB /datum/status_effect/lightningorb //Speed from a lightning orb!
+
+#define STATUS_EFFECT_MAYHEM /datum/status_effect/mayhem //Total bloodbath. Activated by orb of mayhem pickup/bottle of mayhem item.
+
/////////////
// DEBUFFS //
/////////////
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index e710cc06e0e..84c315993b1 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -462,3 +462,51 @@
name = "Blessing of Wounded Soldier"
desc = "Some people seek power through redemption, one thing many people don't know is that battle is the ultimate redemption and wounds let you bask in eternal glory."
icon_state = "wounded_soldier"
+
+/datum/status_effect/lightningorb
+ id = "Lightning Orb"
+ duration = 30 SECONDS
+ alert_type = /atom/movable/screen/alert/status_effect/lightningorb
+
+/datum/status_effect/lightningorb/on_apply()
+ . = ..()
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/yellow_orb)
+ to_chat(owner, "You feel fast!")
+
+/datum/status_effect/lightningorb/on_remove()
+ . = ..()
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/yellow_orb)
+ to_chat(owner, "You slow down.")
+
+/atom/movable/screen/alert/status_effect/lightningorb
+ name = "Lightning Orb"
+ desc = "The speed surges through you!"
+ icon_state = "lightningorb"
+
+/datum/status_effect/mayhem
+ id = "Mayhem"
+ duration = 2 MINUTES
+ /// The chainsaw spawned by the status effect
+ var/obj/item/chainsaw/doomslayer/chainsaw
+
+/datum/status_effect/mayhem/on_apply()
+ . = ..()
+ to_chat(owner, "RIP AND TEAR")
+ SEND_SOUND(owner, sound('sound/hallucinations/veryfar_noise.ogg'))
+ new /datum/hallucination/delusion(owner, forced = TRUE, force_kind = "demon", duration = duration, skip_nearby = FALSE)
+ chainsaw = new(get_turf(owner))
+ owner.log_message("entered a blood frenzy", LOG_ATTACK)
+ ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT)
+ owner.drop_all_held_items()
+ owner.put_in_hands(chainsaw, forced = TRUE)
+ chainsaw.attack_self(owner)
+ owner.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25)
+ to_chat(owner, "KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!")
+ var/datum/client_colour/colour = owner.add_client_colour(/datum/client_colour/bloodlust)
+ QDEL_IN(colour, 1.1 SECONDS)
+
+/datum/status_effect/mayhem/on_remove()
+ . = ..()
+ to_chat(owner, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.")
+ owner.log_message("exited a blood frenzy", LOG_ATTACK)
+ QDEL_NULL(chainsaw)
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
index 9080f136e76..50488d59f4d 100644
--- a/code/game/objects/effects/mines.dm
+++ b/code/game/objects/effects/mines.dm
@@ -130,95 +130,6 @@
name = "bwoink mine"
sound = 'sound/effects/adminhelp.ogg'
-/obj/effect/mine/pickup
- name = "pickup"
- desc = "pick me up"
- icon = 'icons/effects/effects.dmi'
- icon_state = "electricity2"
- density = FALSE
- var/duration = 0
-
-/obj/effect/mine/pickup/Initialize()
- . = ..()
- animate(src, pixel_y = 4, time = 20, loop = -1)
-
-/obj/effect/mine/pickup/triggermine(mob/victim)
- if(triggered)
- return
- triggered = 1
- invisibility = INVISIBILITY_ABSTRACT
- mineEffect(victim)
- qdel(src)
-
-
-/obj/effect/mine/pickup/bloodbath
- name = "Red Orb"
- desc = "You feel angry just looking at it."
- duration = 1200 //2min
- color = "#FF0000"
- var/mob/living/doomslayer
- var/obj/item/chainsaw/doomslayer/chainsaw
-
-/obj/effect/mine/pickup/bloodbath/mineEffect(mob/living/carbon/victim)
- if(!victim.client || !istype(victim))
- return
- to_chat(victim, "RIP AND TEAR")
-
- INVOKE_ASYNC(src, .proc/blood_delusion, victim)
-
- chainsaw = new(victim.loc)
- victim.log_message("entered a blood frenzy", LOG_ATTACK)
-
- ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT)
- victim.drop_all_held_items()
- victim.put_in_hands(chainsaw, forced = TRUE)
- chainsaw.attack_self(victim)
- victim.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25)
- to_chat(victim, "KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!")
-
- var/datum/client_colour/colour = victim.add_client_colour(/datum/client_colour/bloodlust)
- QDEL_IN(colour, 11)
- doomslayer = victim
- RegisterSignal(src, COMSIG_PARENT_QDELETING, .proc/end_blood_frenzy)
- QDEL_IN(WEAKREF(src), duration)
-
-/obj/effect/mine/pickup/bloodbath/proc/end_blood_frenzy()
- if(doomslayer)
- to_chat(doomslayer, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.")
- doomslayer.log_message("exited a blood frenzy", LOG_ATTACK)
- if(chainsaw)
- qdel(chainsaw)
-
-/obj/effect/mine/pickup/bloodbath/proc/blood_delusion(mob/living/carbon/victim)
- new /datum/hallucination/delusion(victim, TRUE, "demon", duration, 0)
-
-/obj/effect/mine/pickup/healing
- name = "Blue Orb"
- desc = "You feel better just looking at it."
- color = "#0000FF"
-
-/obj/effect/mine/pickup/healing/mineEffect(mob/living/carbon/victim)
- if(!victim.client || !istype(victim))
- return
- to_chat(victim, "You feel great!")
- victim.revive(full_heal = TRUE, admin_revive = TRUE)
-
-/obj/effect/mine/pickup/speed
- name = "Yellow Orb"
- desc = "You feel faster just looking at it."
- color = "#FFFF00"
- duration = 300
-
-/obj/effect/mine/pickup/speed/mineEffect(mob/living/carbon/victim)
- if(!victim.client || !istype(victim))
- return
- to_chat(victim, "You feel fast!")
- victim.add_movespeed_modifier(/datum/movespeed_modifier/yellow_orb)
- sleep(duration)
- victim.remove_movespeed_modifier(/datum/movespeed_modifier/yellow_orb)
- to_chat(victim, "You slow down.")
-
-
/// These mines spawn pellet_clouds around them when triggered
/obj/effect/mine/shrapnel
name = "shrapnel mine"
diff --git a/code/game/objects/effects/powerup.dm b/code/game/objects/effects/powerup.dm
new file mode 100644
index 00000000000..7d68f9e5352
--- /dev/null
+++ b/code/game/objects/effects/powerup.dm
@@ -0,0 +1,126 @@
+/obj/effect/powerup
+ name = "power-up"
+ icon = 'icons/effects/effects.dmi'
+ density = FALSE
+ anchored = TRUE
+ resistance_flags = INDESTRUCTIBLE
+ /// How long in deciseconds it will take for the powerup to respawn, if no value it won't respawn
+ var/respawn_time
+ /// How long the powerup stays on the ground, if no value it will stay forever
+ var/lifetime
+ /// Message given when powerup is picked up
+ var/pickup_message
+ /// Sound played when powerup is picked up
+ var/pickup_sound
+ /// Cooldown for the powerup to respawn after it's been used
+ COOLDOWN_DECLARE(respawn_cooldown)
+
+/obj/effect/powerup/Initialize()
+ ..()
+ if(lifetime)
+ QDEL_IN(src, lifetime)
+
+/obj/effect/powerup/Crossed(atom/movable/movable_atom)
+ . = ..()
+ trigger(movable_atom)
+
+/obj/effect/powerup/Bump(atom/bumped_atom)
+ trigger(bumped_atom)
+
+/obj/effect/powerup/Bumped(atom/movable/movable_atom)
+ trigger(movable_atom)
+
+/// Triggers the effect of the powerup on the target, returns FALSE if the target is not /mob/living, is dead or the cooldown hasn't finished, returns TRUE otherwise
+/obj/effect/powerup/proc/trigger(mob/living/target)
+ if(!istype(target) || target.stat == DEAD)
+ return FALSE
+ if(respawn_time)
+ if(!COOLDOWN_FINISHED(src, respawn_cooldown))
+ return FALSE
+ COOLDOWN_START(src, respawn_cooldown, respawn_time)
+ alpha = 100
+ addtimer(VARSET_CALLBACK(src, alpha, initial(alpha)), respawn_time)
+ else
+ qdel(src)
+ if(pickup_message)
+ to_chat(target, "[pickup_message]")
+ if(pickup_sound)
+ playsound(get_turf(target), pickup_sound, 50, TRUE, -1)
+ return TRUE
+
+/obj/effect/powerup/health
+ name = "health pickup"
+ desc = "Blessing from the havens."
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "medicalpack"
+ respawn_time = 30 SECONDS
+ pickup_message = "Health restored!"
+ pickup_sound = 'sound/magic/staff_healing.ogg'
+ /// How much the pickup heals when picked up
+ var/heal_amount = 50
+ /// Does this pickup fully heal when picked up
+ var/full_heal = FALSE
+ /// If full heal, does this do an admin level heal?
+ var/admin_heal = FALSE
+
+/obj/effect/powerup/health/trigger(mob/living/target)
+ . = ..()
+ if(!.)
+ return
+ if(full_heal)
+ target.fully_heal(admin_heal)
+ else if(heal_amount)
+ target.heal_ordered_damage(heal_amount, list(BRUTE, BURN))
+
+/obj/effect/powerup/health/full
+ name = "mega health pickup"
+ desc = "Now this is what I'm talking about."
+ icon_state = "duffel-med"
+ full_heal = TRUE
+
+/obj/effect/powerup/ammo
+ name = "ammo pickup"
+ desc = "You like revenge, right? Everybody likes revenge! Well, let's go get some!"
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "ammobox"
+ respawn_time = 30 SECONDS
+ pickup_message = "Ammunition reloaded!"
+ pickup_sound = 'sound/weapons/gun/shotgun/rack.ogg'
+
+/obj/effect/powerup/ammo/trigger(mob/living/target)
+ . = ..()
+ if(!.)
+ return
+ for(var/obj/item/gun in target.GetAllContents())
+ if(!isgun(gun) && !istype(gun, /obj/item/flamethrower))
+ continue
+ SEND_SIGNAL(gun, COMSIG_ITEM_RECHARGED)
+
+/obj/effect/powerup/ammo/ctf
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "at_shield1"
+ respawn_time = FALSE
+ lifetime = 30 SECONDS
+
+/obj/effect/powerup/speed
+ name = "Lightning Orb"
+ desc = "You feel faster just looking at it."
+ icon_state = "electricity2"
+ pickup_sound = 'sound/magic/lightningshock.ogg'
+
+/obj/effect/powerup/speed/trigger(mob/living/target)
+ . = ..()
+ if(!.)
+ return
+ target.apply_status_effect(STATUS_EFFECT_LIGHTNINGORB)
+
+/obj/effect/powerup/mayhem
+ name = "Orb of Mayhem"
+ desc = "You feel angry just looking at it."
+ icon_state = "impact_laser"
+
+/obj/effect/powerup/mayhem/trigger(mob/living/target)
+ . = ..()
+ if(!.)
+ return
+ target.apply_status_effect(STATUS_EFFECT_MAYHEM)
diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm
index 6318e85b3ad..1d50421d7bd 100644
--- a/code/game/objects/items/flamethrower.dm
+++ b/code/game/objects/items/flamethrower.dm
@@ -236,6 +236,7 @@
if(create_with_tank)
ptank = new /obj/item/tank/internals/plasma/full(src)
update_icon()
+ RegisterSignal(src, COMSIG_ITEM_RECHARGED, .proc/instant_refill)
/obj/item/flamethrower/full
create_full = TRUE
@@ -259,3 +260,11 @@
/obj/item/assembly/igniter/proc/ignite_turf(obj/item/flamethrower/F,turf/open/location,release_amount = 0.05)
F.default_ignite(location,release_amount)
+
+/obj/item/flamethrower/proc/instant_refill()
+ if(ptank)
+ ptank.air_contents.assert_gas(/datum/gas/plasma)
+ ptank.air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*ptank.volume/(R_IDEAL_GAS_EQUATION*T20C)
+ else
+ ptank = new /obj/item/tank/internals/plasma/full(src)
+ update_icon()
diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm
index 6a12116852c..5790c685681 100644
--- a/code/modules/admin/fun_balloon.dm
+++ b/code/modules/admin/fun_balloon.dm
@@ -170,11 +170,7 @@
var/mob/living/M = AM
M.forceMove(get_turf(LA))
to_chat(M, "You're trapped in a deadly arena! To escape, you'll need to drag a severed head to the escape portals.", confidential = TRUE)
- INVOKE_ASYNC(src, .proc/do_bloodbath, M)
-
-/obj/effect/forcefield/arena_shuttle_entrance/proc/do_bloodbath(mob/living/L)
- var/obj/effect/mine/pickup/bloodbath/B = new (L)
- B.mineEffect(L)
+ M.apply_status_effect(STATUS_EFFECT_MAYHEM)
/area/shuttle_arena
name = "arena"
diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm
index d7e02b5a54d..89474732cdc 100644
--- a/code/modules/awaymissions/capture_the_flag.dm
+++ b/code/modules/awaymissions/capture_the_flag.dm
@@ -4,7 +4,6 @@
#define FLAG_RETURN_TIME 200 // 20 seconds
#define INSTAGIB_RESPAWN 50 //5 seconds
#define DEFAULT_RESPAWN 150 //15 seconds
-#define AMMO_DROP_LIFETIME 300
#define CTF_REQUIRED_PLAYERS 4
/obj/item/ctf
@@ -184,8 +183,7 @@
///assoc list for classes. If there's only one, it'll just equip. Otherwise, it lets you pick which outfit!
var/list/ctf_gear = list("white" = /datum/outfit/ctf)
var/instagib_gear = /datum/outfit/ctf/instagib
- var/ammo_type = /obj/effect/ctf/ammo
-
+ var/ammo_type = /obj/effect/powerup/ammo/ctf
// Fast paced gameplay, no real time for burn infections.
var/player_traits = list(TRAIT_NEVER_WOUNDED)
@@ -670,45 +668,6 @@
alpha = 100
resistance_flags = INDESTRUCTIBLE
-/obj/effect/ctf/ammo
- name = "ammo pickup"
- desc = "You like revenge, right? Everybody likes revenge! Well, \
- let's go get some!"
- icon = 'icons/effects/effects.dmi'
- icon_state = "at_shield1"
- layer = ABOVE_MOB_LAYER
- alpha = 255
- invisibility = 0
-
-/obj/effect/ctf/ammo/Initialize(mapload)
- ..()
- QDEL_IN(src, AMMO_DROP_LIFETIME)
-
-/obj/effect/ctf/ammo/Crossed(atom/movable/AM)
- . = ..()
- reload(AM)
-
-/obj/effect/ctf/ammo/Bump(atom/A)
- reload(A)
-
-/obj/effect/ctf/ammo/Bumped(atom/movable/AM)
- reload(AM)
-
-/obj/effect/ctf/ammo/proc/reload(mob/living/M)
- if(!ishuman(M))
- return
- for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines)
- if(M in CTF.spawned_mobs)
- var/outfit = CTF.spawned_mobs[M]
- var/datum/outfit/O = new outfit
- for(var/obj/item/gun/G in M)
- qdel(G)
- O.equip(M)
- to_chat(M, "Ammunition reloaded!")
- playsound(get_turf(M), 'sound/weapons/gun/shotgun/rack.ogg', 50, TRUE, -1)
- qdel(src)
- break
-
/obj/effect/ctf/dead_barricade
name = "dead barrier"
desc = "It provided cover in fire fights. And now it's gone."
@@ -774,5 +733,4 @@
#undef FLAG_RETURN_TIME
#undef INSTAGIB_RESPAWN
#undef DEFAULT_RESPAWN
-#undef AMMO_DROP_LIFETIME
#undef CTF_REQUIRED_PLAYERS
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 0d53a79dc20..9ac3f955ce8 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -1075,12 +1075,11 @@
/obj/item/mayhem/attack_self(mob/user)
for(var/mob/living/carbon/human/H in range(7,user))
- var/obj/effect/mine/pickup/bloodbath/B = new(H)
- INVOKE_ASYNC(B, /obj/effect/mine/pickup/bloodbath/.proc/mineEffect, H)
+ H.apply_status_effect(STATUS_EFFECT_MAYHEM)
to_chat(user, "You shatter the bottle!")
playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, TRUE)
message_admins("[ADMIN_LOOKUPFLW(user)] has activated a bottle of mayhem!")
- log_combat(user, null, "activated a bottle of mayhem", src)
+ user.log_message("activated a bottle of mayhem", LOG_ATTACK)
qdel(src)
//Colossus
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index 352a40a1a54..df51f04f080 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -124,6 +124,7 @@
else
chamber_round(replace_new_round = TRUE)
update_icon()
+ RegisterSignal(src, COMSIG_ITEM_RECHARGED, .proc/instant_reload)
/obj/item/gun/ballistic/vv_edit_var(vname, vval)
. = ..()
@@ -612,6 +613,15 @@ GLOBAL_LIST_INIT(gun_saw_types, typecacheof(list(
process_fire(user, user, FALSE)
. = TRUE
+/obj/item/gun/ballistic/proc/instant_reload()
+ if(magazine)
+ magazine.top_off()
+ else
+ if(!mag_type)
+ return
+ magazine = new mag_type(src)
+ chamber_round()
+ update_icon()
/obj/item/suppressor
name = "suppressor"
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index a9f92eeb6ad..5cae6c77ea0 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -44,6 +44,7 @@
if(selfcharge)
START_PROCESSING(SSobj, src)
update_icon()
+ RegisterSignal(src, COMSIG_ITEM_RECHARGED, .proc/instant_recharge)
/obj/item/gun/energy/ComponentInitialize()
. = ..()
@@ -243,3 +244,10 @@
playsound(user, BB.hitsound, 50, TRUE)
cell.use(E.e_cost)
. = "[user] casually lights [A.loc == user ? "[user.p_their()] [A.name]" : A] with [src]. Damn."
+
+/obj/item/gun/energy/proc/instant_recharge()
+ if(!cell)
+ return
+ cell.charge = cell.maxcharge
+ recharge_newshot(no_cyborg_drain = TRUE)
+ update_icon()
diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm
index 896876c118e..fdff86776af 100644
--- a/code/modules/projectiles/guns/magic.dm
+++ b/code/modules/projectiles/guns/magic.dm
@@ -54,6 +54,7 @@
chambered = new ammo_type(src)
if(can_charge)
START_PROCESSING(SSobj, src)
+ RegisterSignal(src, COMSIG_ITEM_RECHARGED, .proc/instant_recharge)
/obj/item/gun/magic/Destroy()
@@ -89,3 +90,8 @@
switch(var_name)
if(NAMEOF(src, charges))
recharge_newshot()
+
+/obj/item/gun/magic/proc/instant_recharge()
+ charges = max_charges
+ recharge_newshot()
+ update_icon()
diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi
index fb35edddf62..ca92a6c390c 100755
Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 042d46d3a6f..ddf88660cba 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -959,6 +959,7 @@
#include "code\game\objects\effects\overlays.dm"
#include "code\game\objects\effects\phased_mob.dm"
#include "code\game\objects\effects\portals.dm"
+#include "code\game\objects\effects\powerup.dm"
#include "code\game\objects\effects\proximity.dm"
#include "code\game\objects\effects\spiders.dm"
#include "code\game\objects\effects\step_triggers.dm"