Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MIRROR] Final Objective: Battle Royale #2781

Merged
merged 1 commit into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions code/__DEFINES/antagonists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,6 @@ GLOBAL_LIST_INIT(human_invader_antagonists, list(
#define SPY_DIFFICULTY_MEDIUM "Medium"
/// Very difficult to accomplish, almost guaranteed to require crew conflict
#define SPY_DIFFICULTY_HARD "Hard"

/// Camera net used by battle royale objective
#define BATTLE_ROYALE_CAMERA_NET "battle_royale_camera_net"
3 changes: 3 additions & 0 deletions code/__DEFINES/dcs/signals/signals_traitor.dm
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@
#define COMSIG_TRAITOR_GRAFFITI_SLIPPED "traitor_demoralise_event"
/// For when someone is injected with the EHMS virus from /datum/traitor_objective_category/infect
#define COMSIG_EHMS_INJECTOR_INJECTED "after_ehms_inject"

/// Called by an battle royale implanter when successfully implanting someone. Passes the implanted mob.
#define COMSIG_ROYALE_IMPLANTED "royale_implanted"
230 changes: 230 additions & 0 deletions code/controllers/subsystem/battle_royale.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/// Global list of areas which are considered to be inside the same department for our purposes
GLOBAL_LIST_INIT(battle_royale_regions, list(
"Medical Bay" = list(
/area/station/command/heads_quarters/cmo,
/area/station/medical,
/area/station/security/checkpoint/medical,
),
"Research Division" = list(
/area/station/command/heads_quarters/rd,
/area/station/security/checkpoint/science,
/area/station/science,
),
"Engineering Bay" = list(
/area/station/command/heads_quarters/ce,
/area/station/engineering,
/area/station/maintenance/disposal/incinerator,
/area/station/security/checkpoint/engineering,
),
"Cargo Bay" = list(
/area/station/cargo,
/area/station/command/heads_quarters/qm,
/area/station/security/checkpoint/supply,
),
))

/// Basically just exists to hold references to datums so that they don't GC
SUBSYSTEM_DEF(battle_royale)
name = "Battle Royale"
flags = SS_NO_INIT | SS_NO_FIRE
/// List of battle royale datums currently running
var/list/active_battles

/// Start a new battle royale using a passed list of implants
/datum/controller/subsystem/battle_royale/proc/start_battle(list/competitors)
var/datum/battle_royale_controller/controller = new()
if (!controller.start(competitors))
return FALSE
LAZYADD(active_battles, controller)
if (LAZYLEN(active_battles) == 1)
start_broadcasting_network(BATTLE_ROYALE_CAMERA_NET)
RegisterSignal(controller, COMSIG_QDELETING, PROC_REF(battle_ended))
return TRUE

/// Drop reference when it kills itself
/datum/controller/subsystem/battle_royale/proc/battle_ended(datum/source)
SIGNAL_HANDLER
LAZYREMOVE(active_battles, source)
if (!LAZYLEN(active_battles))
stop_broadcasting_network(BATTLE_ROYALE_CAMERA_NET)


/// Datum which controls the conflict
/datum/battle_royale_controller
/// Where is our battle taking place?
var/chosen_area
/// Is the battle currently in progress?
var/battle_running = TRUE
/// Should we let everyone know that someone has died?
var/announce_deaths = TRUE
/// List of implants involved
var/list/contestant_implants = list()
/// Ways to describe that someone has died
var/static/list/euphemisms = list(
"cashed their last paycheque.",
"didn't make it...",
"didn't make the cut.",
"had their head blown clean off!",
"has been killed!",
"has failed the challenge!",
"has passed away.",
"has died.",
"is in a better place now.",
"isn't going to be clocking in tomorrow!",
"just flatlined.",
"isn't today's winner.",
"seems to have exploded!",
"was just murdered on live tv!",
"won't be making it to retirement.",
"won't be getting back up after that one.",
)
/// Ways to tell people not to salt in deadchat, surely effective
var/static/list/condolences = list(
"Better luck next time!",
"But stay tuned, there's still everything to play for!",
"Did you catch who did it?",
"It looked like that one really hurt...",
"Let's get that one on action replay!",
"Let's have a moment of silence, please.",
"Let's hope the next one does better.",
"Someone please notify their next of kin.",
"They had a good run.",
"Too bad!",
"What a shame!",
"What an upset!",
"What's going to happen next?",
"Who could have seen that coming?",
"Who will be next?",
)

/datum/battle_royale_controller/Destroy(force)
contestant_implants = null
return ..()

/// Start a battle royale with the list of provided implants
/datum/battle_royale_controller/proc/start(list/implants, battle_time = 10 MINUTES)
chosen_area = pick(GLOB.battle_royale_regions)
for (var/obj/item/implant/explosive/battle_royale/contestant_implant in implants)
contestant_implant.start_battle(chosen_area, GLOB.battle_royale_regions[chosen_area])
if (isnull(contestant_implant))
continue // Might have exploded if it was removed from a person
RegisterSignal(contestant_implant, COMSIG_QDELETING, PROC_REF(implant_destroyed))
contestant_implants |= contestant_implant

if (length(contestant_implants) <= 1)
return FALSE // Well there's not much point is there

priority_announce(
text = "Congratulations [station_name()], you have been chosen as the next site of the Rumble Royale! \n\
Viewers across the sector will watch our [convert_integer_to_words(length(contestant_implants))] lucky contestants battle their way into your [chosen_area] and fight until only one is left standing! \n\
If they don't make it in five minutes, they'll be disqualified. If you see one of our players struggling to get in, do lend them a hand... or don't, if you can live with the consequences! \n\
As a gesture of gratitude, we will be providing our premium broadcast to your entertainment monitors at no cost so that you can watch the excitement. \n\
Bystanders are advised not to intervene... but if you do, make it look good for the camera!",
title = "Rumble Royale Beginning",
sound = 'sound/machines/alarm.ogg',
has_important_message = TRUE,
sender_override = "Rumble Royale Pirate Broadcast Station",
color_override = "red",
)

for (var/obj/item/implant/explosive/battle_royale/contestant_implant as anything in contestant_implants)
contestant_implant.announce()
addtimer(CALLBACK(src, PROC_REF(limit_area)), battle_time / 2, TIMER_DELETE_ME)
addtimer(CALLBACK(src, PROC_REF(finish)), battle_time, TIMER_DELETE_ME)
return TRUE

/// An implant was destroyed, hopefully because it exploded. Count how many competitors remain.
/datum/battle_royale_controller/proc/implant_destroyed(obj/item/implant/implant)
SIGNAL_HANDLER
contestant_implants -= implant
if (!battle_running)
return

if (length(contestant_implants) <= 1)
announce_winner(implant)
else if (announce_deaths)
var/message = ""
if (isnull(implant.imp_in))
message = "Looks like someone removed and destroyed their implant, that's cheating!"
else
message = "[implant.imp_in.real_name] [pick(euphemisms)] [pick(condolences)]"
priority_announce(
text = message,
title = "Rumble Royale Casualty Report",
sound = 'sound/misc/notice1.ogg',
has_important_message = TRUE,
sender_override = "Rumble Royale Pirate Broadcast Station",
color_override = "red",
)

/// There's only one person left, we have a winner!
/datum/battle_royale_controller/proc/announce_winner(obj/item/implant/losing_implant)
battle_running = FALSE
if (length(contestant_implants) > 1)
return

var/message = ""
var/mob/living/loser = losing_implant.imp_in
var/obj/item/implant/winning_implant = pop(contestant_implants)
var/mob/living/winner = winning_implant?.imp_in

if (isnull(winner) && isnull(loser))
message = "Somehow, it seems like there's no winner tonight. What a disappointment!"
else
var/loser_text = isnull(loser) ? "With the disqualification of the other remaining contestant" : "With the death of [loser.real_name]"
var/winner_text = isnull(winner) ? "we must sadly announce that the would-be winner has also been disqualified. Such bad showmanship!" : "only [winner.real_name] remains. Congratulations, we have a winner!"
message = "[loser_text], [winner_text]"

if (!isnull(winner))
podspawn(list(
"target" = get_turf(winner),
"style" = STYLE_SYNDICATE,
"spawn" = /obj/item/food/roast_dinner,
))

priority_announce(
text = message,
title = "Rumble Royale Winner",
sound = 'sound/misc/notice1.ogg',
has_important_message = TRUE,
sender_override = "Rumble Royale Pirate Broadcast Station",
color_override = "red",
)

qdel(winning_implant) // You get to live!
winner?.mind?.remove_antag_datum(/datum/antagonist/survivalist/battle_royale)
qdel(src)

/// Called halfway through the battle, if you've not made it to the designated battle zone we kill you
/datum/battle_royale_controller/proc/limit_area()
priority_announce(
text = "We're halfway done folks! And bad news to anyone who hasn't made it to the [chosen_area]... you're out!",
title = "Rumble Royale Update",
sound = 'sound/misc/notice1.ogg',
has_important_message = TRUE,
sender_override = "Rumble Royale Pirate Broadcast Station",
color_override = "red",
)

for (var/obj/item/implant/explosive/battle_royale/contestant_implant as anything in contestant_implants)
contestant_implant.limit_areas()

/// Well you're out of time, bad luck
/datum/battle_royale_controller/proc/finish()
battle_running = FALSE

priority_announce(
text = "Sorry remaining contestants, your time is up. \
We're sorry to announce that this edition of Royal Rumble has no winner. \n\
Better luck next time!",
title = "Rumble Royale Concluded",
sound = 'sound/misc/notice1.ogg',
has_important_message = TRUE,
sender_override = "Rumble Royale Pirate Broadcast Station",
color_override = "red",
)

for (var/obj/item/implant/explosive/battle_royale/contestant_implant as anything in contestant_implants)
contestant_implant.explode()

qdel(src)
3 changes: 2 additions & 1 deletion code/datums/components/simple_bodycam.dm
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@

/datum/component/simple_bodycam/proc/camera_gone(datum/source)
SIGNAL_HANDLER
qdel(src)
if (!QDELETED(src))
qdel(src)
24 changes: 10 additions & 14 deletions code/datums/diseases/advance/symptoms/itching.dm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
COOLDOWN_DECLARE(itching_cooldown)
///if FALSE, there is a percentage chance that the mob will emote scratching while itching_cooldown is on cooldown. If TRUE, won't emote again until after the off cooldown scratch occurs.
var/off_cooldown_scratched = FALSE

/datum/symptom/itching/Start(datum/disease/advance/active_disease)
. = ..()
if(!.)
Expand All @@ -41,17 +42,12 @@
. = ..()
if(!.)
return
var/mob/living/carbon/affected_mob = active_disease.affected_mob
var/obj/item/bodypart/bodypart = affected_mob.get_bodypart(affected_mob.get_random_valid_zone(even_weights = TRUE))
if(bodypart && IS_ORGANIC_LIMB(bodypart) && !(bodypart.bodypart_flags & BODYPART_PSEUDOPART)) //robotic limbs will mean less scratching overall (why are golems able to damage themselves with self-scratching, but not androids? the world may never know)
var/can_scratch = scratch && !affected_mob.incapacitated()
if(can_scratch)
bodypart.receive_damage(0.5)
//below handles emotes, limiting the emote of emotes passed to chat
if(COOLDOWN_FINISHED(src, itching_cooldown) || !COOLDOWN_FINISHED(src, itching_cooldown) && prob(60) && !off_cooldown_scratched)
affected_mob.visible_message("[can_scratch ? span_warning("[affected_mob] scratches [affected_mob.p_their()] [bodypart.plaintext_zone].") : ""]", span_warning("Your [bodypart.plaintext_zone] itches. [can_scratch ? " You scratch it." : ""]"))
COOLDOWN_START(src, itching_cooldown, 5 SECONDS)
if(!off_cooldown_scratched && !COOLDOWN_FINISHED(src, itching_cooldown))
off_cooldown_scratched = TRUE
else
off_cooldown_scratched = FALSE

var/announce_scratch = COOLDOWN_FINISHED(src, itching_cooldown) || (!COOLDOWN_FINISHED(src, itching_cooldown) && prob(60) && !off_cooldown_scratched)
if (!active_disease.affected_mob.itch(silent = !announce_scratch, can_scratch = scratch) || !announce_scratch)
return
COOLDOWN_START(src, itching_cooldown, 5 SECONDS)
if(!off_cooldown_scratched && !COOLDOWN_FINISHED(src, itching_cooldown))
off_cooldown_scratched = TRUE
else
off_cooldown_scratched = FALSE
Loading
Loading