From 70fe7d1f861ccccb7a6c9a2c18e178a72e5ee233 Mon Sep 17 00:00:00 2001
From: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Date: Sat, 27 Feb 2021 13:35:25 +0100
Subject: [PATCH] [MIRROR] Dynamic now plays a part in controlling random event
antagonists (#3685)
* Dynamic now plays a part in controlling random event antagonists
* Update _event.dm
* Update spider_infestation.dm
Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
Co-authored-by: KathrinBailey <53862927+KathrinBailey@users.noreply.github.com>
---
code/__DEFINES/dcs/signals.dm | 5 ++
code/__DEFINES/dynamic.dm | 15 ++++--
code/game/gamemodes/dynamic/dynamic.dm | 48 +++++++++++++++++--
.../gamemodes/dynamic/dynamic_hijacking.dm | 26 ++++++++++
code/game/gamemodes/dynamic/readme.md | 14 ++++++
.../revenant/revenant_spawn_event.dm | 1 +
.../antagonists/slaughter/slaughterevent.dm | 1 +
code/modules/events/_event.dm | 21 ++++++--
code/modules/events/abductor.dm | 1 +
code/modules/events/alien_infestation.dm | 2 +
code/modules/events/blob.dm | 2 +
code/modules/events/nightmare.dm | 1 +
code/modules/events/operative.dm | 1 +
code/modules/events/pirates.dm | 1 +
code/modules/events/space_dragon.dm | 1 +
code/modules/events/space_ninja.dm | 1 +
code/modules/events/spider_infestation.dm | 1 +
code/modules/events/swarmer.dm | 1 +
tgstation.dme | 1 +
19 files changed, 134 insertions(+), 10 deletions(-)
create mode 100644 code/game/gamemodes/dynamic/dynamic_hijacking.dm
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index a9c9d34892b..07c2e10f0fd 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -27,6 +27,11 @@
#define COMSIG_GLOB_JOB_AFTER_SPAWN "!job_after_spawn"
/// crewmember joined the game (mob/living, rank)
#define COMSIG_GLOB_CREWMEMBER_JOINED "!crewmember_joined"
+/// Random event is trying to roll. (/datum/round_event_control/random_event)
+/// Called by (/datum/round_event_control/preRunEvent).
+#define COMSIG_GLOB_PRE_RANDOM_EVENT "!pre_random_event"
+ /// Do not allow this random event to continue.
+ #define CANCEL_PRE_RANDOM_EVENT (1<<0)
/// signals from globally accessible objects
diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm
index a5b1c15be5f..dedf05396c5 100644
--- a/code/__DEFINES/dynamic.dm
+++ b/code/__DEFINES/dynamic.dm
@@ -1,8 +1,17 @@
/// This is the only ruleset that should be picked this round, used by admins and should not be on rulesets in code.
-#define ONLY_RULESET 1
+#define ONLY_RULESET (1 << 0)
/// Only one ruleset with this flag will be picked.
-#define HIGH_IMPACT_RULESET 2
+#define HIGH_IMPACT_RULESET (1 << 1)
/// This ruleset can only be picked once. Anything that does not have a scaling_cost MUST have this.
-#define LONE_RULESET 4
+#define LONE_RULESET (1 << 2)
+
+/// No round event was hijacked this cycle
+#define HIJACKED_NOTHING "HIJACKED_NOTHING"
+
+/// This cycle, a round event was hijacked when the last midround event was too recent.
+#define HIJACKED_TOO_RECENT "HIJACKED_TOO_RECENT"
+
+/// This cycle, a round event was hijacked when the next midround event is too soon.
+#define HIJACKED_TOO_SOON "HIJACKED_TOO_SOON"
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 00a4c592c08..d2b5994c6e2 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -129,9 +129,24 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/// lower value rounds closer to the average.
var/roundstart_split_curve_width = 1.8
+ /// The minimum amount of time for antag random events to be hijacked.
+ var/random_event_hijack_minimum = 10 MINUTES
+
+ /// The maximum amount of time for antag random events to be hijacked.
+ var/random_event_hijack_maximum = 18 MINUTES
+
/// A list of recorded "snapshots" of the round, stored in the dynamic.json log
var/list/datum/dynamic_snapshot/snapshots
+ /// The time when the last midround injection was attempted, whether or not it was successful
+ var/last_midround_injection_attempt = 0
+
+ /// The amount to inject when a round event is hijacked
+ var/hijacked_random_event_injection_chance = 50
+
+ /// Whether or not a random event has been hijacked this midround cycle
+ var/random_event_hijacked = HIJACKED_NOTHING
+
/datum/game_mode/dynamic/admin_panel()
var/list/dat = list("
Game Mode PanelGame Mode Panel
")
dat += "Dynamic Mode \[VV\] \[Refresh\]
"
@@ -159,7 +174,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
dat += "[DR.ruletype] - [DR.name]
"
else
dat += "none.
"
- dat += "
Injection Timers: ([get_injection_chance(TRUE)]% chance)
"
+ dat += "
Injection Timers: ([get_injection_chance(dry_run = TRUE)]% latejoin chance, [get_midround_injection_chance(dry_run = TRUE)]% midround chance)
"
dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\]
"
dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\]
"
usr << browse(dat.Join(), "window=gamemode_panel;size=500x500")
@@ -359,6 +374,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
vars[variable] = configuration["Dynamic"][variable]
setup_parameters()
+ setup_hijacking()
var/valid_roundstart_ruleset = 0
for (var/rule in subtypesof(/datum/dynamic_ruleset))
@@ -647,7 +663,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
message_admins("DYNAMIC: Checking for midround injection.")
log_game("DYNAMIC: Checking for midround injection.")
- if (prob(get_injection_chance()))
+ last_midround_injection_attempt = world.time
+
+ if (prob(get_midround_injection_chance()))
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/midround/rule in midround_rules)
if (!rule.weight)
@@ -661,8 +679,17 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
drafted_rules[rule] = rule.get_weight()
if (drafted_rules.len > 0)
picking_midround_latejoin_rule(drafted_rules)
+ else if (random_event_hijacked == HIJACKED_TOO_SOON)
+ log_game("DYNAMIC: Midround injection failed when random event was hijacked. Spawning another random event in its place.")
-/// Gets the chance for latejoin and midround injection, the dry_run argument is only used for forced injection.
+ // A random event antag would have rolled had this injection check passed.
+ // As a refund, spawn a non-ghost-role random event.
+ SSevents.spawnEvent()
+ SSevents.reschedule()
+
+ random_event_hijacked = HIJACKED_NOTHING
+
+/// Gets the chance for latejoin injection, the dry_run argument is only used for forced injection.
/datum/game_mode/dynamic/proc/get_injection_chance(dry_run = FALSE)
if(forced_injection)
forced_injection = !dry_run
@@ -685,6 +712,16 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
chance -= lower_injection_chance
return round(max(0,chance))
+/// Gets the chance for midround injection, the dry_run argument is only used for forced injection.
+/// Usually defers to the latejoin injection chance.
+/datum/game_mode/dynamic/proc/get_midround_injection_chance(dry_run)
+ var/chance = get_injection_chance(dry_run)
+
+ if (random_event_hijacked != HIJACKED_NOTHING)
+ chance += hijacked_random_event_injection_chance
+
+ return chance
+
/// Removes type from the list
/datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type)
for(var/I in type_list)
@@ -801,6 +838,11 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (20 to INFINITY)
return rand(90, 100)
+/// Log to messages and to the game
+/datum/game_mode/dynamic/proc/dynamic_log(text)
+ message_admins("DYNAMIC: [text]")
+ log_game("DYNAMIC: [text]")
+
#undef FAKE_REPORT_CHANCE
#undef REPORT_NEG_DIVERGENCE
#undef REPORT_POS_DIVERGENCE
diff --git a/code/game/gamemodes/dynamic/dynamic_hijacking.dm b/code/game/gamemodes/dynamic/dynamic_hijacking.dm
new file mode 100644
index 00000000000..20c2e1d903e
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_hijacking.dm
@@ -0,0 +1,26 @@
+/datum/game_mode/dynamic/proc/setup_hijacking()
+ RegisterSignal(SSdcs, COMSIG_GLOB_PRE_RANDOM_EVENT, .proc/on_pre_random_event)
+
+/datum/game_mode/dynamic/proc/on_pre_random_event(datum/source, datum/round_event_control/round_event_control)
+ if (!round_event_control.dynamic_should_hijack)
+ return
+
+ if (random_event_hijacked != HIJACKED_NOTHING)
+ dynamic_log("Random event [round_event_control.name] tried to roll, but Dynamic vetoed it (random event has already ran).")
+ SSevents.spawnEvent()
+ SSevents.reschedule()
+ return CANCEL_PRE_RANDOM_EVENT
+
+ var/time_range = rand(random_event_hijack_minimum, random_event_hijack_maximum)
+
+ if (world.time - last_midround_injection_attempt < time_range)
+ random_event_hijacked = HIJACKED_TOO_RECENT
+ dynamic_log("Random event [round_event_control.name] tried to roll, but the last midround injection \
+ was too recent. Injection chance has been raised to [get_midround_injection_chance(dry_run = TRUE)]%.")
+ return CANCEL_PRE_RANDOM_EVENT
+
+ if (midround_injection_cooldown - world.time < time_range)
+ random_event_hijacked = HIJACKED_TOO_SOON
+ dynamic_log("Random event [round_event_control.name] tried to roll, but the next midround injection \
+ is too soon. Injection chance has been raised to [get_midround_injection_chance(dry_run = TRUE)]%.")
+ return CANCEL_PRE_RANDOM_EVENT
diff --git a/code/game/gamemodes/dynamic/readme.md b/code/game/gamemodes/dynamic/readme.md
index 223be8654d5..3a8d41f230b 100644
--- a/code/game/gamemodes/dynamic/readme.md
+++ b/code/game/gamemodes/dynamic/readme.md
@@ -168,3 +168,17 @@ The "Dynamic" key has the following configurable values:
- `threat_curve_width` - A number between 0.5 and 4. Higher value will favour extreme rounds and lower value rounds closer to the average.
- `roundstart_split_curve_centre` - A number between -5 and +5. Equivalent to threat_curve_centre, but for the budget split. A negative value will weigh towards midround rulesets, and a positive value will weight towards roundstart ones.
- `roundstart_split_curve_width` - A number between 0.5 and 4. Equivalent to threat_curve_width, but for the budget split. Higher value will favour more variance in splits and lower value rounds closer to the average.
+- `random_event_hijack_minimum` - The minimum amount of time for antag random events to be hijacked. (See [Random Event Hijacking](#random-event-hijacking))
+- `random_event_hijack_maximum` - The maximum amount of time for antag random events to be hijacked. (See [Random Event Hijacking](#random-event-hijacking))
+- `hijacked_random_event_injection_chance` - The amount of injection chance to give to Dynamic when a random event is hijacked. (See [Random Event Hijacking](#random-event-hijacking))
+
+## Random Event "Hijacking"
+Random events have the potential to be hijacked by Dynamic to keep the pace of midround injections, while also allowing greenshifts to contain some antagonists.
+
+`/datum/round_event_control/dynamic_should_hijack` is a variable to random events to allow Dynamic to hijack them, and defaults to FALSE. This is set to TRUE for random events that spawn antagonists.
+
+In `/datum/game_mode/dynamic/on_pre_random_event` (in `dynamic_hijacking.dm`), Dynamic hooks to random events. If the `dynamic_should_hijack` variable is TRUE, the following sequence of events occurs:
+
+
+
+`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance`.
diff --git a/code/modules/antagonists/revenant/revenant_spawn_event.dm b/code/modules/antagonists/revenant/revenant_spawn_event.dm
index c9a892cd64b..10ee621a9b8 100644
--- a/code/modules/antagonists/revenant/revenant_spawn_event.dm
+++ b/code/modules/antagonists/revenant/revenant_spawn_event.dm
@@ -6,6 +6,7 @@
weight = 7
max_occurrences = 1
min_players = 5
+ dynamic_should_hijack = TRUE
/datum/round_event/ghost_role/revenant
diff --git a/code/modules/antagonists/slaughter/slaughterevent.dm b/code/modules/antagonists/slaughter/slaughterevent.dm
index 64963abf343..ddde384a4e4 100644
--- a/code/modules/antagonists/slaughter/slaughterevent.dm
+++ b/code/modules/antagonists/slaughter/slaughterevent.dm
@@ -5,6 +5,7 @@
max_occurrences = 1
earliest_start = 1 HOURS
min_players = 20
+ dynamic_should_hijack = TRUE
diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm
index fff0ffadbef..47ec75e9331 100644
--- a/code/modules/events/_event.dm
+++ b/code/modules/events/_event.dm
@@ -1,3 +1,5 @@
+#define RANDOM_EVENT_ADMIN_INTERVENTION_TIME 60 // SKYRAT EDIT: Original value (10)
+
//this singleton datum is used by the events controller to dictate how it selects events
/datum/round_event_control
var/name //The human-readable name of the event
@@ -26,6 +28,9 @@
var/triggering //admin cancellation
+ /// Whether or not dynamic should hijack this event
+ var/dynamic_should_hijack = FALSE
+
/datum/round_event_control/New()
if(config && !wizardevent) // Magic is unaffected by configs
earliest_start = CEILING(earliest_start * CONFIG_GET(number/events_min_time_mul), 1)
@@ -55,18 +60,24 @@
return FALSE
if(ispath(typepath, /datum/round_event/ghost_role) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT))
return FALSE
+
+ var/datum/game_mode/dynamic/dynamic = SSticker.mode
+ if (istype(dynamic) && dynamic_should_hijack && dynamic.random_event_hijacked != HIJACKED_NOTHING)
+ return FALSE
+
return TRUE
/datum/round_event_control/proc/preRunEvent()
if(!ispath(typepath, /datum/round_event))
return EVENT_CANT_RUN
+ if (SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_RANDOM_EVENT, src) & CANCEL_PRE_RANDOM_EVENT)
+ return EVENT_INTERRUPTED
+
triggering = TRUE
if (alert_observers)
- //message_admins("Random Event triggering in 10 seconds: [name] (CANCEL)") //ORIGINAL
- //sleep(100) //ORIGINAL
- message_admins("Random Event triggering in 60 seconds: [name] (CANCEL)") //SKYRAT EDIT CHANGE - EVENTS
- sleep(600) //SKYRAT EDIT CHANGE - EVENTS
+ message_admins("Random Event triggering in [RANDOM_EVENT_ADMIN_INTERVENTION_TIME] seconds: [name] (CANCEL)")
+ sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME SECONDS)
var/gamemode = SSticker.mode.config_tag
var/players_amt = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE)
if(!canSpawnEvent(players_amt, gamemode))
@@ -217,3 +228,5 @@
processing = my_processing
SSevents.running += src
return ..()
+
+#undef RANDOM_EVENT_ADMIN_INTERVENTION_TIME
diff --git a/code/modules/events/abductor.dm b/code/modules/events/abductor.dm
index 6f239f7aa22..2ba534d42a0 100755
--- a/code/modules/events/abductor.dm
+++ b/code/modules/events/abductor.dm
@@ -4,6 +4,7 @@
weight = 10
max_occurrences = 1
min_players = 20
+ dynamic_should_hijack = TRUE
gamemode_blacklist = list("nuclear","wizard","revolution")
/datum/round_event/ghost_role/abductor
diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm
index e34d2cece49..df51f1af81e 100644
--- a/code/modules/events/alien_infestation.dm
+++ b/code/modules/events/alien_infestation.dm
@@ -8,6 +8,8 @@
min_players = 10
+ dynamic_should_hijack = TRUE
+
/datum/round_event_control/alien_infestation/canSpawnEvent()
. = ..()
if(!.)
diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm
index d4d8328013b..e7da374af9f 100644
--- a/code/modules/events/blob.dm
+++ b/code/modules/events/blob.dm
@@ -7,6 +7,8 @@
// min_players = 20 // SKYRAT EDIT -- Original
min_players = 60 // SKYRAT EDIT CHANGE -- Requires 60 alive non-afk players for blob
+ dynamic_should_hijack = TRUE
+
gamemode_blacklist = list("blob") //Just in case a blob survives that long
/datum/round_event_control/blob/canSpawnEvent(players, gamemode)
diff --git a/code/modules/events/nightmare.dm b/code/modules/events/nightmare.dm
index ac3a1da8b80..9d8694e8548 100644
--- a/code/modules/events/nightmare.dm
+++ b/code/modules/events/nightmare.dm
@@ -3,6 +3,7 @@
typepath = /datum/round_event/ghost_role/nightmare
max_occurrences = 1
min_players = 20
+ dynamic_should_hijack = TRUE
/datum/round_event/ghost_role/nightmare
minimum_required = 1
diff --git a/code/modules/events/operative.dm b/code/modules/events/operative.dm
index 9e92364b89d..b0eab57f6c9 100644
--- a/code/modules/events/operative.dm
+++ b/code/modules/events/operative.dm
@@ -3,6 +3,7 @@
typepath = /datum/round_event/ghost_role/operative
weight = 0 //Admin only
max_occurrences = 1
+ dynamic_should_hijack = TRUE
/datum/round_event/ghost_role/operative
minimum_required = 1
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index 339f7ca20cc..e451d78567f 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -6,6 +6,7 @@
max_occurrences = 1
min_players = 10
earliest_start = 30 MINUTES
+ dynamic_should_hijack = TRUE
gamemode_blacklist = list("nuclear")
#define PIRATES_ROGUES "Rogues"
diff --git a/code/modules/events/space_dragon.dm b/code/modules/events/space_dragon.dm
index ac2db2cf84c..0bf18ede2d2 100644
--- a/code/modules/events/space_dragon.dm
+++ b/code/modules/events/space_dragon.dm
@@ -6,6 +6,7 @@
weight = 10
max_occurrences = 1
min_players = 20
+ dynamic_should_hijack = TRUE
/datum/round_event/ghost_role/space_dragon
minimum_required = 1
diff --git a/code/modules/events/space_ninja.dm b/code/modules/events/space_ninja.dm
index ceef16a28d0..f82e087b2e6 100644
--- a/code/modules/events/space_ninja.dm
+++ b/code/modules/events/space_ninja.dm
@@ -6,6 +6,7 @@
weight = 10
earliest_start = 20 MINUTES
min_players = 20
+ dynamic_should_hijack = TRUE
/datum/round_event/ghost_role/space_ninja
minimum_required = 1
diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm
index 700019903e0..56cb91f589e 100644
--- a/code/modules/events/spider_infestation.dm
+++ b/code/modules/events/spider_infestation.dm
@@ -5,6 +5,7 @@
//max_occurrences = 1 //ORIGINAL
max_occurrences = 0 //SKYRAT EDIT CHANGE
//min_players = 20 - SKYRAT EDIT - moved to modular_skyrat/master_files
+ dynamic_should_hijack = TRUE
/datum/round_event/spider_infestation
announceWhen = 400
diff --git a/code/modules/events/swarmer.dm b/code/modules/events/swarmer.dm
index 2212b271019..2599cc92d79 100644
--- a/code/modules/events/swarmer.dm
+++ b/code/modules/events/swarmer.dm
@@ -5,6 +5,7 @@
weight = 3 //SKYRAT EDIT CHANGE - EVENTS - same as the dynamic ruleset
max_occurrences = 1 //Only once okay fam
min_players = 20
+ dynamic_should_hijack = TRUE
/datum/round_event/spawn_swarmer/announce(fake)
priority_announce("Our long-range sensors have detected that your station's defenses have been breached by some sort of alien device. We suggest searching for and destroying it as soon as possible.", "[command_name()] High-Priority Update")
diff --git a/tgstation.dme b/tgstation.dme
index 5d575bc138e..712011fd8f7 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -840,6 +840,7 @@
#include "code\game\gamemodes\clown_ops\clown_weapons.dm"
#include "code\game\gamemodes\cult\cult.dm"
#include "code\game\gamemodes\dynamic\dynamic.dm"
+#include "code\game\gamemodes\dynamic\dynamic_hijacking.dm"
#include "code\game\gamemodes\dynamic\dynamic_logging.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm"