From 5fa23b7e714d01914c8cf7f739ff8b69647e757b Mon Sep 17 00:00:00 2001 From: dwasint <82520990+dwasint@users.noreply.github.com> Date: Sat, 14 Oct 2023 20:53:57 -0400 Subject: [PATCH 01/85] need run_event from upstream --- code/__DEFINES/~monkestation/storytellers.dm | 95 ++ code/controllers/subsystem/statpanel.dm | 1 + code/controllers/subsystem/ticker.dm | 6 +- code/modules/admin/topic.dm | 1 + code/modules/events/_event.dm | 73 ++ code/modules/events/aurora_caelus.dm | 2 + code/modules/events/brain_trauma.dm | 2 + code/modules/events/brand_intelligence.dm | 2 + code/modules/events/bureaucratic_error.dm | 2 + code/modules/events/camerafailure.dm | 2 + code/modules/events/carp_migration.dm | 2 + .../modules/events/communications_blackout.dm | 2 + code/modules/events/creep_awakening.dm | 2 + code/modules/events/disease_outbreak.dm | 2 + code/modules/events/dust.dm | 2 + code/modules/events/electrical_storm.dm | 2 + code/modules/events/fake_virus.dm | 2 + code/modules/events/false_alarm.dm | 2 + code/modules/events/ghost_role/abductor.dm | 2 + .../events/ghost_role/alien_infestation.dm | 2 + code/modules/events/ghost_role/blob.dm | 2 + .../events/ghost_role/changeling_event.dm | 2 + .../events/ghost_role/fugitive_event.dm | 2 + code/modules/events/ghost_role/morph_event.dm | 2 + code/modules/events/ghost_role/nightmare.dm | 2 + code/modules/events/ghost_role/operative.dm | 2 + .../events/ghost_role/revenant_event.dm | 3 +- code/modules/events/ghost_role/sentience.dm | 2 + .../events/ghost_role/sentient_disease.dm | 2 + .../events/ghost_role/slaughter_event.dm | 2 + .../modules/events/ghost_role/space_dragon.dm | 2 + code/modules/events/ghost_role/space_ninja.dm | 2 + .../events/gravity_generator_blackout.dm | 2 + code/modules/events/grey_tide.dm | 2 + code/modules/events/grid_check.dm | 2 + code/modules/events/heart_attack.dm | 2 + .../immovable_rod/immovable_rod_event.dm | 2 + code/modules/events/ion_storm.dm | 2 + code/modules/events/mass_hallucination.dm | 2 + .../events/meteors/meteor_wave_events.dm | 2 + .../events/meteors/stray_meteor_event.dm | 2 + code/modules/events/mice_migration.dm | 2 + code/modules/events/portal_storm.dm | 2 + code/modules/events/processor_overload.dm | 2 + code/modules/events/radiation_leak.dm | 2 + code/modules/events/radiation_storm.dm | 4 +- code/modules/events/sandstorm.dm | 2 + code/modules/events/scrubber_clog.dm | 2 + code/modules/events/scrubber_overflow.dm | 2 + code/modules/events/shuttle_catastrophe.dm | 2 + code/modules/events/shuttle_insurance.dm | 2 + code/modules/events/space_vines/vine_event.dm | 6 +- code/modules/events/spider_infestation.dm | 2 + code/modules/events/stray_cargo.dm | 2 + code/modules/events/tram_malfunction.dm | 2 + code/modules/events/wisdomcow.dm | 6 +- code/modules/events/wormholes.dm | 2 + config/events.json | 13 + config/game_options.txt | 47 + .../new_antagonists/slasher/ghost_role.dm | 2 +- .../code/modules/storytellers/config.dm | 149 +++ .../storytellers/gamemode_subsystem.dm | 1070 +++++++++++++++++ .../code/modules/storytellers/readme.md | 44 + .../modules/storytellers/scheduled_events.dm | 94 ++ .../storytellers/storytellers/_storyteller.dm | 145 +++ .../storytellers/storytellers/ghost.dm | 6 + .../storytellers/storytellers/jester.dm | 13 + .../storytellers/storytellers/sleeper.dm | 13 + .../modules/storytellers/storytellers/vote.dm | 4 + .../storytellers/storytellers/warrior.dm | 12 + tgstation.dme | 9 + 71 files changed, 1904 insertions(+), 8 deletions(-) create mode 100644 code/__DEFINES/~monkestation/storytellers.dm create mode 100644 config/events.json create mode 100644 monkestation/code/modules/storytellers/config.dm create mode 100644 monkestation/code/modules/storytellers/gamemode_subsystem.dm create mode 100644 monkestation/code/modules/storytellers/readme.md create mode 100644 monkestation/code/modules/storytellers/scheduled_events.dm create mode 100644 monkestation/code/modules/storytellers/storytellers/_storyteller.dm create mode 100644 monkestation/code/modules/storytellers/storytellers/ghost.dm create mode 100644 monkestation/code/modules/storytellers/storytellers/jester.dm create mode 100644 monkestation/code/modules/storytellers/storytellers/sleeper.dm create mode 100644 monkestation/code/modules/storytellers/storytellers/vote.dm create mode 100644 monkestation/code/modules/storytellers/storytellers/warrior.dm diff --git a/code/__DEFINES/~monkestation/storytellers.dm b/code/__DEFINES/~monkestation/storytellers.dm new file mode 100644 index 000000000000..f4248d740d3e --- /dev/null +++ b/code/__DEFINES/~monkestation/storytellers.dm @@ -0,0 +1,95 @@ + +//Could be bitflags, but that would require a good amount of translations, which eh, either way works for me +/// When the event is combat oriented (spawning monsters, inherently hostile antags) +#define TAG_COMBAT "combat" +/// When the event is spooky (broken lights, some antags) +#define TAG_SPOOKY "spooky" +/// When the event is destructive in a decent capacity (meteors, blob) +#define TAG_DESTRUCTIVE "destructive" +/// When the event impacts most of the crewmembers in some capacity (comms blackout) +#define TAG_COMMUNAL "communal" +/// When the event targets a person for something (appendix, heart attack) +#define TAG_TARGETED "targeted" +/// When the event is positive and helps the crew, in some capacity (Shuttle Loan, Supply Pod) +#define TAG_POSITIVE "positive" +/// When one of the crewmembers becomes an antagonist +#define TAG_CREW_ANTAG "crew_antag" +/// When the antagonist event is focused around team cooperation. +#define TAG_TEAM_ANTAG "team_antag" +/// When one of the non-crewmember players becomes an antagonist +#define TAG_OUTSIDER_ANTAG "away_antag" +/// When the event impacts the overmap +#define TAG_OVERMAP "overmap" +/// When the event requires the station to be in space (meteors, carp) +#define TAG_SPACE "space" +/// When the event requires the station to be on planetary. +#define TAG_PLANETARY "planetary" + +#define EVENT_TRACK_MUNDANE "Mundane" +#define EVENT_TRACK_MODERATE "Moderate" +#define EVENT_TRACK_MAJOR "Major" +#define EVENT_TRACK_ROLESET "Roleset" +#define EVENT_TRACK_OBJECTIVES "Objectives" + +#define ALL_EVENTS "All" +#define UNCATEGORIZED_EVENTS "Uncategorized" + +#define STORYTELLER_WAIT_TIME 5 SECONDS + +#define EVENT_POINT_GAINED_PER_SECOND 0.05 + +#define TRACK_FAIL_POINT_PENALTY_MULTIPLIER 0.5 + +#define GAMEMODE_PANEL_MAIN "Main" +#define GAMEMODE_PANEL_VARIABLES "Variables" + +#define MUNDANE_POINT_THRESHOLD 40 +#define MODERATE_POINT_THRESHOLD 70 +#define MAJOR_POINT_THRESHOLD 130 +#define ROLESET_POINT_THRESHOLD 150 +#define OBJECTIVES_POINT_THRESHOLD 170 + +#define MUNDANE_MIN_POP 4 +#define MODERATE_MIN_POP 6 +#define MAJOR_MIN_POP 20 +#define ROLESET_MIN_POP 25 +#define OBJECTIVES_MIN_POP 20 + +/// Defines for how much pop do we need to stop applying a pop scalling penalty to event frequency. +#define MUNDANE_POP_SCALE_THRESHOLD 25 +#define MODERATE_POP_SCALE_THRESHOLD 32 +#define MAJOR_POP_SCALE_THRESHOLD 45 +#define ROLESET_POP_SCALE_THRESHOLD 45 +#define OBJECTIVES_POP_SCALE_THRESHOLD 45 + +/// The maximum penalty coming from pop scalling, when we're at the most minimum point, easing into 0 as we reach the SCALE_THRESHOLD. This is treated as a percentage. +#define MUNDANE_POP_SCALE_PENALTY 35 +#define MODERATE_POP_SCALE_PENALTY 35 +#define MAJOR_POP_SCALE_PENALTY 35 +#define ROLESET_POP_SCALE_PENALTY 35 +#define OBJECTIVES_POP_SCALE_PENALTY 35 + +#define STORYTELLER_VOTE "storyteller" + +#define EVENT_TRACKS list(EVENT_TRACK_MUNDANE, EVENT_TRACK_MODERATE, EVENT_TRACK_MAJOR, EVENT_TRACK_ROLESET, EVENT_TRACK_OBJECTIVES) +#define EVENT_PANEL_TRACKS list(EVENT_TRACK_MUNDANE, EVENT_TRACK_MODERATE, EVENT_TRACK_MAJOR, EVENT_TRACK_ROLESET, EVENT_TRACK_OBJECTIVES, UNCATEGORIZED_EVENTS, ALL_EVENTS) + +/// Defines for the antag cap to prevent midround injections. +#define ANTAG_CAP_FLAT 2 +#define ANTAG_CAP_DENOMINATOR 9 + +///Below are defines for roundstart point pool. The GAIN ones are multiplied by ready population +#define ROUNDSTART_MUNDANE_BASE 20 +#define ROUNDSTART_MUNDANE_GAIN 0.5 + +#define ROUNDSTART_MODERATE_BASE 35 +#define ROUNDSTART_MODERATE_GAIN 1.2 + +#define ROUNDSTART_MAJOR_BASE 40 +#define ROUNDSTART_MAJOR_GAIN 2 + +#define ROUNDSTART_ROLESET_BASE 60 +#define ROUNDSTART_ROLESET_GAIN 4 + +#define ROUNDSTART_OBJECTIVES_BASE 40 +#define ROUNDSTART_OBJECTIVES_GAIN 2 diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 13df2be77ba9..517c26798a60 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -26,6 +26,7 @@ SUBSYSTEM_DEF(statpanels) global_data = list( "Map: [SSmapping.config?.map_name || "Loading..."]", cached ? "Next Map: [cached.map_name]" : null, + "Storyteller: [SSgamemode.storyteller ? SSgamemode.storyteller.name : "N/A"]", //monkestation addition "Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]", "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]", "Round Time: [ROUND_TIME()]", diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index aed9754a2a46..0bd73935655e 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -225,10 +225,14 @@ SUBSYSTEM_DEF(ticker) var/init_start = world.timeofday mode = new /datum/game_mode/dynamic - + SSgamemode.init_storyteller() //monkestation addition CHECK_TICK //Configure mode and assign player to special mode stuff var/can_continue = 0 + //monkestation addition start + can_continue = SSgamemode.pre_setup() + CHECK_TICK + //monkestation addition end can_continue = src.mode.pre_setup() //Choose antagonists CHECK_TICK can_continue = can_continue && SSjob.DivideOccupations() //Distribute jobs diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index cc21cc3ebe45..b3600978a9a6 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -70,6 +70,7 @@ if(!check_rights(R_ADMIN)) return SSticker.mode.admin_panel() + SSgamemode.admin_panel(usr) //monkestation addition else if(href_list["call_shuttle"]) if(!check_rights(R_ADMIN)) diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index a5a7b699bea4..ca86be587ba7 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -42,6 +42,21 @@ /// Flags dictating whether this event should be run on certain kinds of map var/map_flags = NONE + //monkestation vars starts + var/roundstart = FALSE // BUBBER EDIT + var/cost = 1 // BUBBER EDIT + var/reoccurence_penalty_multiplier = 0.75 + var/shared_occurence_type + var/track = EVENT_TRACK_MODERATE + /// Last calculated weight that the storyteller assigned this event + var/calculated_weight = 0 + var/tags = list() /// Tags of the event + /// List of the shared occurence types. + var/static/list/shared_occurences = list() + /// Whether a roundstart event can happen post roundstart. Very important for events which override job assignments. + var/can_run_post_roundstart = TRUE + //monkestation vars end + /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) @@ -203,8 +218,12 @@ Runs the event var/fakeable = TRUE /// Whether a admin wants this event to be cancelled var/cancel_event = FALSE + //monkestation vars starts ///canceled on oshan var/oshan_blocked = FALSE + /// Whether the event called its start() yet or not. + var/has_started = FALSE + //monkestation vars end //Called first before processing. //Allows you to setup your event, such as randomly @@ -228,6 +247,60 @@ Runs the event SHOULD_CALL_PARENT(FALSE) return +//monkestation addition starts - STORYTELLERS +/// This section of event processing is in a proc because roundstart events may get their start invoked. +/datum/round_event/proc/try_start() + if(has_started) + return + has_started = TRUE + processing = FALSE + start() + processing = TRUE + +/datum/round_event_control/roundstart + roundstart = TRUE + earliest_start = 0 + +///Adds an occurence. Has to use the setter to properly handle shared occurences +/datum/round_event_control/proc/add_occurence() + if(shared_occurence_type) + if(!shared_occurences[shared_occurence_type]) + shared_occurences[shared_occurence_type] = 0 + shared_occurences[shared_occurence_type]++ + occurrences++ + +///Subtracts an occurence. Has to use the setter to properly handle shared occurences +/datum/round_event_control/proc/subtract_occurence() + if(shared_occurence_type) + if(!shared_occurences[shared_occurence_type]) + shared_occurences[shared_occurence_type] = 0 + shared_occurences[shared_occurence_type]-- + occurrences-- + +///Gets occurences. Has to use the getter to properly handle shared occurences +/datum/round_event_control/proc/get_occurences() + if(shared_occurence_type) + if(!shared_occurences[shared_occurence_type]) + shared_occurences[shared_occurence_type] = 0 + return shared_occurences[shared_occurence_type] + return occurrences + +/// Prints the action buttons for this event. +/datum/round_event_control/proc/get_href_actions() + if(SSticker.HasRoundStarted()) + if(roundstart) + if(!can_run_post_roundstart) + return "Fire Schedule" + return "Fire Schedule" + else + return "Fire Schedule Force Next" + else + if(roundstart) + return "Add Roundstart Force Roundstart" + else + return "Fire Schedule Force Next" +//monkestation addition ends - STORYTELLERS + //Called after something followable has been spawned by an event //Provides ghosts a follow link to an atom if possible //Only called once. diff --git a/code/modules/events/aurora_caelus.dm b/code/modules/events/aurora_caelus.dm index 8ce6fcdd89a3..c4094d0a9909 100644 --- a/code/modules/events/aurora_caelus.dm +++ b/code/modules/events/aurora_caelus.dm @@ -6,6 +6,8 @@ earliest_start = 5 MINUTES category = EVENT_CATEGORY_FRIENDLY description = "A colourful display can be seen through select windows. And the kitchen." + track = EVENT_TRACK_MUNDANE + tags = list(TAG_COMMUNAL, TAG_POSITIVE, TAG_SPACE) /datum/round_event_control/aurora_caelus/can_spawn_event(players, allow_magic = FALSE) if(!SSmapping.empty_space) diff --git a/code/modules/events/brain_trauma.dm b/code/modules/events/brain_trauma.dm index 77d17ce5ecbb..40df052b1ecd 100644 --- a/code/modules/events/brain_trauma.dm +++ b/code/modules/events/brain_trauma.dm @@ -6,6 +6,8 @@ description = "A crewmember gains a random trauma." min_wizard_trigger_potency = 2 max_wizard_trigger_potency = 6 + track = EVENT_TRACK_MUNDANE + tags = list(TAG_TARGETED) /datum/round_event/brain_trauma fakeable = FALSE diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index 813be9b3d8ba..1a829cdaf37e 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -9,6 +9,8 @@ min_wizard_trigger_potency = 2 max_wizard_trigger_potency = 6 admin_setup = list(/datum/event_admin_setup/listed_options/brand_intelligence) + track = EVENT_TRACK_MODERATE + tags = list(TAG_DESTRUCTIVE, TAG_COMMUNAL) /datum/round_event/brand_intelligence announce_when = 21 diff --git a/code/modules/events/bureaucratic_error.dm b/code/modules/events/bureaucratic_error.dm index 5f206134a921..89e2e0bd7ad6 100644 --- a/code/modules/events/bureaucratic_error.dm +++ b/code/modules/events/bureaucratic_error.dm @@ -5,6 +5,8 @@ weight = 5 category = EVENT_CATEGORY_BUREAUCRATIC description = "Randomly opens and closes job slots, along with changing the overflow role." + track = EVENT_TRACK_MAJOR // if you've ever dealt with 10 mimes you understand why. + tags = list(TAG_COMMUNAL) /datum/round_event/bureaucratic_error announce_when = 1 diff --git a/code/modules/events/camerafailure.dm b/code/modules/events/camerafailure.dm index 453b919c5b88..7459a575dc05 100644 --- a/code/modules/events/camerafailure.dm +++ b/code/modules/events/camerafailure.dm @@ -6,6 +6,8 @@ alert_observers = FALSE category = EVENT_CATEGORY_ENGINEERING description = "Turns off a random amount of cameras." + track = EVENT_TRACK_MUNDANE + tags = list(TAG_COMMUNAL, TAG_SPOOKY) /datum/round_event/camera_failure fakeable = FALSE diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm index e62d26598c23..22b64c9bdf21 100644 --- a/code/modules/events/carp_migration.dm +++ b/code/modules/events/carp_migration.dm @@ -10,6 +10,8 @@ min_wizard_trigger_potency = 0 max_wizard_trigger_potency = 3 admin_setup = list(/datum/event_admin_setup/carp_migration) + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL) /datum/round_event_control/carp_migration/New() . = ..() diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm index 0747998e6744..f0e7c4694aa0 100644 --- a/code/modules/events/communications_blackout.dm +++ b/code/modules/events/communications_blackout.dm @@ -6,6 +6,8 @@ description = "Heavily emps all telecommunication machines, blocking all communication for a while." min_wizard_trigger_potency = 0 max_wizard_trigger_potency = 3 + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL, TAG_SPOOKY) /datum/round_event/communications_blackout announce_when = 1 diff --git a/code/modules/events/creep_awakening.dm b/code/modules/events/creep_awakening.dm index 648c2cc9db04..a519ee9430bd 100644 --- a/code/modules/events/creep_awakening.dm +++ b/code/modules/events/creep_awakening.dm @@ -5,6 +5,8 @@ min_players = 20 category = EVENT_CATEGORY_HEALTH description = "A random crewmember becomes obsessed with another." + track = EVENT_TRACK_MODERATE + tags = list(TAG_CREW_ANTAG, TAG_TARGETED) /datum/round_event/obsessed fakeable = FALSE diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm index f57ed1774d4c..fdaeb012c2fc 100644 --- a/code/modules/events/disease_outbreak.dm +++ b/code/modules/events/disease_outbreak.dm @@ -32,6 +32,8 @@ admin_setup = list(/datum/event_admin_setup/minimum_candidate_requirement/disease_outbreak, /datum/event_admin_setup/listed_options/disease_outbreak) ///Disease recipient candidates var/list/disease_candidates = list() + track = EVENT_TRACK_MUNDANE + tags = list(TAG_TARGETED) /datum/round_event_control/disease_outbreak/can_spawn_event(players_amt, allow_magic = FALSE) . = ..() diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm index c12b86d5803c..0ebe81a5ef5d 100644 --- a/code/modules/events/dust.dm +++ b/code/modules/events/dust.dm @@ -8,6 +8,8 @@ category = EVENT_CATEGORY_SPACE description = "A single space dust is hurled at the station." map_flags = EVENT_SPACE_ONLY + track = EVENT_TRACK_MUNDANE + tags = list(TAG_DESTRUCTIVE, TAG_SPACE) /datum/round_event/space_dust start_when = 1 diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm index 1309887d4b73..8ec4e485bcbc 100644 --- a/code/modules/events/electrical_storm.dm +++ b/code/modules/events/electrical_storm.dm @@ -8,6 +8,8 @@ description = "Destroys all lights in a large area." min_wizard_trigger_potency = 0 max_wizard_trigger_potency = 4 + track = EVENT_TRACK_MUNDANE + tags = list(TAG_SPOOKY) /datum/round_event/electrical_storm var/lightsoutAmount = 1 diff --git a/code/modules/events/fake_virus.dm b/code/modules/events/fake_virus.dm index fb6bfd5be975..36ce68dd61cf 100644 --- a/code/modules/events/fake_virus.dm +++ b/code/modules/events/fake_virus.dm @@ -4,6 +4,8 @@ weight = 20 category = EVENT_CATEGORY_HEALTH description = "Some crewmembers suffer from temporary hypochondria." + track = EVENT_TRACK_MUNDANE + tags = list(TAG_TARGETED) /datum/round_event/fake_virus/start() var/list/fake_virus_victims = list() diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm index 6e5cfdc61a1f..303425f7a8c0 100644 --- a/code/modules/events/false_alarm.dm +++ b/code/modules/events/false_alarm.dm @@ -6,6 +6,8 @@ category = EVENT_CATEGORY_BUREAUCRATIC description = "Fakes an event announcement." admin_setup = list(/datum/event_admin_setup/listed_options/false_alarm) + track = EVENT_TRACK_MUNDANE + tags = list(TAG_COMMUNAL) /datum/round_event_control/falsealarm/can_spawn_event(players_amt, allow_magic = FALSE) . = ..() diff --git a/code/modules/events/ghost_role/abductor.dm b/code/modules/events/ghost_role/abductor.dm index f6928222cfdc..654a8eeb1f46 100644 --- a/code/modules/events/ghost_role/abductor.dm +++ b/code/modules/events/ghost_role/abductor.dm @@ -7,6 +7,8 @@ dynamic_should_hijack = TRUE category = EVENT_CATEGORY_INVASION description = "One or more abductor teams spawns, and they plan to experiment on the crew." + track = EVENT_TRACK_MAJOR + tags = list(TAG_TARGETED, TAG_SPOOKY) /datum/round_event/ghost_role/abductor minimum_required = 2 diff --git a/code/modules/events/ghost_role/alien_infestation.dm b/code/modules/events/ghost_role/alien_infestation.dm index 72ef044229b4..0d0036ac910d 100644 --- a/code/modules/events/ghost_role/alien_infestation.dm +++ b/code/modules/events/ghost_role/alien_infestation.dm @@ -9,6 +9,8 @@ dynamic_should_hijack = TRUE category = EVENT_CATEGORY_ENTITIES description = "A xenomorph larva spawns on a random vent." + track = EVENT_TRACK_MAJOR + tags = list(TAG_COMBAT) /datum/round_event_control/alien_infestation/can_spawn_event(players_amt, allow_magic = FALSE) . = ..() diff --git a/code/modules/events/ghost_role/blob.dm b/code/modules/events/ghost_role/blob.dm index a07d9d1be594..560612a782b3 100644 --- a/code/modules/events/ghost_role/blob.dm +++ b/code/modules/events/ghost_role/blob.dm @@ -10,6 +10,8 @@ dynamic_should_hijack = TRUE category = EVENT_CATEGORY_ENTITIES description = "Spawns a new blob overmind." + track = EVENT_TRACK_MAJOR + tags = list(TAG_DESTRUCTIVE, TAG_COMBAT) /datum/round_event_control/blob/can_spawn_event(players, allow_magic = FALSE) if(EMERGENCY_PAST_POINT_OF_NO_RETURN) // no blobs if the shuttle is past the point of no return diff --git a/code/modules/events/ghost_role/changeling_event.dm b/code/modules/events/ghost_role/changeling_event.dm index 570a6166093d..7248c4147829 100644 --- a/code/modules/events/ghost_role/changeling_event.dm +++ b/code/modules/events/ghost_role/changeling_event.dm @@ -14,6 +14,8 @@ dynamic_should_hijack = TRUE category = EVENT_CATEGORY_ENTITIES description = "A meteor containing a changeling is summoned and thrown at the exterior of the station." + track = EVENT_TRACK_MAJOR + tags = list(TAG_COMBAT) /datum/round_event/ghost_role/changeling minimum_required = 1 diff --git a/code/modules/events/ghost_role/fugitive_event.dm b/code/modules/events/ghost_role/fugitive_event.dm index 24aa4798c79c..ac5257c3d75e 100644 --- a/code/modules/events/ghost_role/fugitive_event.dm +++ b/code/modules/events/ghost_role/fugitive_event.dm @@ -9,6 +9,8 @@ category = EVENT_CATEGORY_INVASION description = "Fugitives will hide on the station, followed by hunters." map_flags = EVENT_SPACE_ONLY + track = EVENT_TRACK_MAJOR + tags = list(TAG_COMBAT) /datum/round_event/ghost_role/fugitives minimum_required = 1 diff --git a/code/modules/events/ghost_role/morph_event.dm b/code/modules/events/ghost_role/morph_event.dm index e0b75119c282..b04b0e3349bc 100644 --- a/code/modules/events/ghost_role/morph_event.dm +++ b/code/modules/events/ghost_role/morph_event.dm @@ -7,6 +7,8 @@ description = "Spawns a hungry shapeshifting blobby creature." min_wizard_trigger_potency = 4 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_ROLESET + tags = list(TAG_COMBAT, TAG_SPOOKY) /datum/round_event/ghost_role/morph minimum_required = 1 diff --git a/code/modules/events/ghost_role/nightmare.dm b/code/modules/events/ghost_role/nightmare.dm index 57b942988cdb..0e5f6c786e89 100644 --- a/code/modules/events/ghost_role/nightmare.dm +++ b/code/modules/events/ghost_role/nightmare.dm @@ -8,6 +8,8 @@ description = "Spawns a nightmare, aiming to darken the station." min_wizard_trigger_potency = 6 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_ROLESET + tags = list(TAG_COMBAT, TAG_SPOOKY) /datum/round_event/ghost_role/nightmare minimum_required = 1 diff --git a/code/modules/events/ghost_role/operative.dm b/code/modules/events/ghost_role/operative.dm index 33cd9e059f09..a7d076b68b7e 100644 --- a/code/modules/events/ghost_role/operative.dm +++ b/code/modules/events/ghost_role/operative.dm @@ -5,6 +5,8 @@ max_occurrences = 1 category = EVENT_CATEGORY_INVASION description = "A single nuclear operative assaults the station." + track = EVENT_TRACK_MAJOR + tags = list(TAG_DESTRUCTIVE, TAG_COMBAT) /datum/round_event/ghost_role/operative minimum_required = 1 diff --git a/code/modules/events/ghost_role/revenant_event.dm b/code/modules/events/ghost_role/revenant_event.dm index 27f3597a7ad2..607a57dfca42 100644 --- a/code/modules/events/ghost_role/revenant_event.dm +++ b/code/modules/events/ghost_role/revenant_event.dm @@ -11,7 +11,8 @@ description = "Spawns an angry, soul sucking ghost." min_wizard_trigger_potency = 4 max_wizard_trigger_potency = 7 - + track = EVENT_TRACK_ROLESET + tags = list(TAG_DESTRUCTIVE, TAG_SPOOKY) /datum/round_event/ghost_role/revenant var/ignore_mobcheck = FALSE diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index 646fcbe2e9c7..cc38f7202447 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -22,6 +22,8 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( description = "An animal or robot becomes sentient!" min_wizard_trigger_potency = 0 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL, TAG_SPOOKY) /datum/round_event/ghost_role/sentience diff --git a/code/modules/events/ghost_role/sentient_disease.dm b/code/modules/events/ghost_role/sentient_disease.dm index 662f6de22c8a..78f8097b8acf 100644 --- a/code/modules/events/ghost_role/sentient_disease.dm +++ b/code/modules/events/ghost_role/sentient_disease.dm @@ -9,6 +9,8 @@ description = "Spawns a sentient disease, who wants to infect as many people as possible." min_wizard_trigger_potency = 4 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_MAJOR + tags = list(TAG_COMBAT, TAG_DESTRUCTIVE) /datum/round_event/ghost_role/sentient_disease role_name = "sentient disease" diff --git a/code/modules/events/ghost_role/slaughter_event.dm b/code/modules/events/ghost_role/slaughter_event.dm index 8cb2b729aa4e..0c0234ba3ac1 100644 --- a/code/modules/events/ghost_role/slaughter_event.dm +++ b/code/modules/events/ghost_role/slaughter_event.dm @@ -10,6 +10,8 @@ description = "Spawns a slaughter demon, to hunt by travelling through pools of blood." min_wizard_trigger_potency = 6 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_MAJOR + tags = list(TAG_COMBAT, TAG_SPOOKY) /datum/round_event/ghost_role/slaughter minimum_required = 1 diff --git a/code/modules/events/ghost_role/space_dragon.dm b/code/modules/events/ghost_role/space_dragon.dm index 735d6d1dcb8f..c6f268290231 100644 --- a/code/modules/events/ghost_role/space_dragon.dm +++ b/code/modules/events/ghost_role/space_dragon.dm @@ -10,6 +10,8 @@ description = "Spawns a space dragon, which will try to take over the station." min_wizard_trigger_potency = 6 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_ROLESET + tags = list(TAG_COMBAT) /datum/round_event/ghost_role/space_dragon minimum_required = 1 diff --git a/code/modules/events/ghost_role/space_ninja.dm b/code/modules/events/ghost_role/space_ninja.dm index a14511b72779..e345daa30668 100644 --- a/code/modules/events/ghost_role/space_ninja.dm +++ b/code/modules/events/ghost_role/space_ninja.dm @@ -8,6 +8,8 @@ dynamic_should_hijack = TRUE category = EVENT_CATEGORY_INVASION description = "A space ninja infiltrates the station." + track = EVENT_TRACK_ROLESET + tags = list(TAG_COMBAT) /datum/round_event/ghost_role/space_ninja minimum_required = 1 diff --git a/code/modules/events/gravity_generator_blackout.dm b/code/modules/events/gravity_generator_blackout.dm index 89cc5a43367e..7992f52bb75a 100644 --- a/code/modules/events/gravity_generator_blackout.dm +++ b/code/modules/events/gravity_generator_blackout.dm @@ -6,6 +6,8 @@ description = "Turns off the gravity generator." min_wizard_trigger_potency = 0 max_wizard_trigger_potency = 4 + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL, TAG_SPACE) /datum/round_event_control/gravity_generator_blackout/can_spawn_event(players_amt, allow_magic = FALSE) . = ..() diff --git a/code/modules/events/grey_tide.dm b/code/modules/events/grey_tide.dm index 7c7e23950a96..86f20f9670da 100644 --- a/code/modules/events/grey_tide.dm +++ b/code/modules/events/grey_tide.dm @@ -8,6 +8,8 @@ description = "Bolts open all doors in one or more departments." min_wizard_trigger_potency = 0 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_MODERATE + tags = list(TAG_DESTRUCTIVE, TAG_SPOOKY) /datum/round_event/grey_tide announce_when = 50 diff --git a/code/modules/events/grid_check.dm b/code/modules/events/grid_check.dm index ecc70df98bcd..2743c4d839d9 100644 --- a/code/modules/events/grid_check.dm +++ b/code/modules/events/grid_check.dm @@ -10,6 +10,8 @@ /// Cooldown for the announement associated with this event. /// Necessary due to the fact that this event is player triggerable. COOLDOWN_DECLARE(announcement_spam_protection) + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL, TAG_SPOOKY) /datum/round_event/grid_check announce_when = 1 diff --git a/code/modules/events/heart_attack.dm b/code/modules/events/heart_attack.dm index 8a8902d5724c..eb995acefdb1 100644 --- a/code/modules/events/heart_attack.dm +++ b/code/modules/events/heart_attack.dm @@ -11,6 +11,8 @@ admin_setup = list(/datum/event_admin_setup/minimum_candidate_requirement/heart_attack, /datum/event_admin_setup/input_number/heart_attack) ///Candidates for recieving a healthy dose of heart disease var/list/heart_attack_candidates = list() + track = EVENT_TRACK_MODERATE + tags = list(TAG_TARGETED) /datum/round_event_control/heart_attack/can_spawn_event(players_amt, allow_magic = FALSE) . = ..() diff --git a/code/modules/events/immovable_rod/immovable_rod_event.dm b/code/modules/events/immovable_rod/immovable_rod_event.dm index 63d2e79b3911..0b8437cf4f9d 100644 --- a/code/modules/events/immovable_rod/immovable_rod_event.dm +++ b/code/modules/events/immovable_rod/immovable_rod_event.dm @@ -11,6 +11,8 @@ min_wizard_trigger_potency = 6 max_wizard_trigger_potency = 7 admin_setup = list(/datum/event_admin_setup/set_location/immovable_rod, /datum/event_admin_setup/question/immovable_rod) + track = EVENT_TRACK_MODERATE + tags = list(TAG_DESTRUCTIVE) /datum/round_event/immovable_rod announce_when = 5 diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm index 772d1576cdc0..f2edeac485a6 100644 --- a/code/modules/events/ion_storm.dm +++ b/code/modules/events/ion_storm.dm @@ -7,6 +7,8 @@ description = "Gives the AI a new, randomized law." min_wizard_trigger_potency = 2 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_MODERATE + tags = list(TAG_TARGETED) /datum/round_event/ion_storm var/replaceLawsetChance = 25 //chance the AI's lawset is completely replaced with something else per config weights diff --git a/code/modules/events/mass_hallucination.dm b/code/modules/events/mass_hallucination.dm index 85b1ef024775..2e2baf9a85f7 100644 --- a/code/modules/events/mass_hallucination.dm +++ b/code/modules/events/mass_hallucination.dm @@ -9,6 +9,8 @@ min_wizard_trigger_potency = 0 max_wizard_trigger_potency = 2 admin_setup = list(/datum/event_admin_setup/mass_hallucination) + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL) /datum/round_event/mass_hallucination fakeable = FALSE diff --git a/code/modules/events/meteors/meteor_wave_events.dm b/code/modules/events/meteors/meteor_wave_events.dm index e45c2b3fda97..475e3265b8c8 100644 --- a/code/modules/events/meteors/meteor_wave_events.dm +++ b/code/modules/events/meteors/meteor_wave_events.dm @@ -10,6 +10,8 @@ category = EVENT_CATEGORY_SPACE description = "A regular meteor wave." map_flags = EVENT_SPACE_ONLY + track = EVENT_TRACK_MAJOR + tags = list(TAG_COMMUNAL, TAG_SPACE, TAG_DESTRUCTIVE) /datum/round_event/meteor_wave start_when = 6 diff --git a/code/modules/events/meteors/stray_meteor_event.dm b/code/modules/events/meteors/stray_meteor_event.dm index 53daff1ac8a1..849be38b6a09 100644 --- a/code/modules/events/meteors/stray_meteor_event.dm +++ b/code/modules/events/meteors/stray_meteor_event.dm @@ -11,6 +11,8 @@ max_wizard_trigger_potency = 7 admin_setup = list(/datum/event_admin_setup/listed_options/stray_meteor) map_flags = EVENT_SPACE_ONLY + track = EVENT_TRACK_MODERATE + tags = list(TAG_DESTRUCTIVE, TAG_SPACE) /datum/round_event/stray_meteor announce_when = 1 diff --git a/code/modules/events/mice_migration.dm b/code/modules/events/mice_migration.dm index e7f31567f4c9..038ee8bcb193 100644 --- a/code/modules/events/mice_migration.dm +++ b/code/modules/events/mice_migration.dm @@ -4,6 +4,8 @@ weight = 10 category = EVENT_CATEGORY_ENTITIES description = "A horde of mice arrives, and perhaps even the Rat King themselves." + track = EVENT_TRACK_MUNDANE + tags = list(TAG_DESTRUCTIVE) /datum/round_event/mice_migration var/minimum_mice = 5 diff --git a/code/modules/events/portal_storm.dm b/code/modules/events/portal_storm.dm index 5f2bca5071ba..5fc32a413a74 100644 --- a/code/modules/events/portal_storm.dm +++ b/code/modules/events/portal_storm.dm @@ -6,6 +6,8 @@ earliest_start = 30 MINUTES category = EVENT_CATEGORY_ENTITIES description = "Syndicate troops pour out of portals." + track = EVENT_TRACK_MAJOR + tags = list(TAG_COMBAT) /datum/round_event/portal_storm/syndicate_shocktroop boss_types = list(/mob/living/basic/syndicate/melee/space/stormtrooper = 2) diff --git a/code/modules/events/processor_overload.dm b/code/modules/events/processor_overload.dm index ebcbb27f2781..9a18da1bd3c9 100644 --- a/code/modules/events/processor_overload.dm +++ b/code/modules/events/processor_overload.dm @@ -5,6 +5,8 @@ min_players = 20 category = EVENT_CATEGORY_ENGINEERING description = "Emps the telecomm processors, scrambling radio speech. Might blow up a few." + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL) /datum/round_event/processor_overload announce_when = 1 diff --git a/code/modules/events/radiation_leak.dm b/code/modules/events/radiation_leak.dm index 0fbe29927666..ed074e1459df 100644 --- a/code/modules/events/radiation_leak.dm +++ b/code/modules/events/radiation_leak.dm @@ -8,6 +8,8 @@ category = EVENT_CATEGORY_ENGINEERING min_wizard_trigger_potency = 3 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL) /datum/round_event/radiation_leak start_when = 1 // 2 seconds in diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm index 5b2b6b71ea1d..a0d055ef2860 100644 --- a/code/modules/events/radiation_storm.dm +++ b/code/modules/events/radiation_storm.dm @@ -1,11 +1,13 @@ /datum/round_event_control/radiation_storm name = "Radiation Storm" typepath = /datum/round_event/radiation_storm - max_occurrences = 1 + max_occurrences = 2 //monkestation edit - STORYTELLERS category = EVENT_CATEGORY_SPACE description = "Radiation storm affects the station, forcing the crew to escape to maintenance." min_wizard_trigger_potency = 3 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL) /datum/round_event/radiation_storm diff --git a/code/modules/events/sandstorm.dm b/code/modules/events/sandstorm.dm index 0288b32ec207..81abd914d200 100644 --- a/code/modules/events/sandstorm.dm +++ b/code/modules/events/sandstorm.dm @@ -19,6 +19,8 @@ max_wizard_trigger_potency = 7 admin_setup = list(/datum/event_admin_setup/listed_options/sandstorm) map_flags = EVENT_SPACE_ONLY + track = EVENT_TRACK_MODERATE + tags = list(TAG_DESTRUCTIVE) /datum/round_event/sandstorm start_when = 60 diff --git a/code/modules/events/scrubber_clog.dm b/code/modules/events/scrubber_clog.dm index 0bbb0801182a..bb236e166e2b 100644 --- a/code/modules/events/scrubber_clog.dm +++ b/code/modules/events/scrubber_clog.dm @@ -6,6 +6,8 @@ earliest_start = 5 MINUTES category = EVENT_CATEGORY_JANITORIAL description = "Harmless mobs climb out of a scrubber." + track = EVENT_TRACK_MUNDANE + tags = list(TAG_COMMUNAL) /datum/round_event/scrubber_clog announce_when = 10 diff --git a/code/modules/events/scrubber_overflow.dm b/code/modules/events/scrubber_overflow.dm index 897d07110317..507c5d046509 100644 --- a/code/modules/events/scrubber_overflow.dm +++ b/code/modules/events/scrubber_overflow.dm @@ -7,6 +7,8 @@ category = EVENT_CATEGORY_JANITORIAL description = "The scrubbers release a tide of mostly harmless froth." admin_setup = list(/datum/event_admin_setup/listed_options/scrubber_overflow) + track = EVENT_TRACK_MUNDANE + tags = list(TAG_COMMUNAL) /datum/round_event/scrubber_overflow announce_when = 1 diff --git a/code/modules/events/shuttle_catastrophe.dm b/code/modules/events/shuttle_catastrophe.dm index ed64c52a836a..28b9571a9e19 100644 --- a/code/modules/events/shuttle_catastrophe.dm +++ b/code/modules/events/shuttle_catastrophe.dm @@ -6,6 +6,8 @@ category = EVENT_CATEGORY_BUREAUCRATIC description = "Replaces the emergency shuttle with a random one." admin_setup = list(/datum/event_admin_setup/warn_admin/shuttle_catastrophe, /datum/event_admin_setup/listed_options/shuttle_catastrophe) + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL) /datum/round_event_control/shuttle_catastrophe/can_spawn_event(players, allow_magic = FALSE) . = ..() diff --git a/code/modules/events/shuttle_insurance.dm b/code/modules/events/shuttle_insurance.dm index d1e39125e346..d00875ead0ca 100644 --- a/code/modules/events/shuttle_insurance.dm +++ b/code/modules/events/shuttle_insurance.dm @@ -6,6 +6,8 @@ max_occurrences = 1 category = EVENT_CATEGORY_BUREAUCRATIC description = "A sketchy but legit insurance offer." + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL) /datum/round_event_control/shuttle_insurance/can_spawn_event(players, allow_magic = FALSE) . = ..() diff --git a/code/modules/events/space_vines/vine_event.dm b/code/modules/events/space_vines/vine_event.dm index a668f036cac3..014f5c2c71a5 100644 --- a/code/modules/events/space_vines/vine_event.dm +++ b/code/modules/events/space_vines/vine_event.dm @@ -14,6 +14,8 @@ /datum/event_admin_setup/input_number/spacevine_potency, /datum/event_admin_setup/input_number/spacevine_production, ) + track = EVENT_TRACK_MAJOR + tags = list(TAG_COMMUNAL, TAG_COMBAT) /datum/round_event/spacevine fakeable = FALSE @@ -64,7 +66,7 @@ /datum/event_admin_setup/set_location/spacevine/apply_to_event(datum/round_event/spacevine/event) event.override_turf = chosen_turf - + /datum/event_admin_setup/multiple_choice/spacevine input_text = "Select starting mutations." min_choices = 0 @@ -88,7 +90,7 @@ type_choices += text2path(choice) event.mutations_overridden = TRUE event.override_mutations = type_choices - + /datum/event_admin_setup/input_number/spacevine_potency input_text = "Set vine's potency (effects mutation frequency + max severity)" max_value = 100 diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index 52ad7474694d..95c8f357cc14 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -10,6 +10,8 @@ description = "Spawns spider eggs, ready to hatch." min_wizard_trigger_potency = 5 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_ROLESET + tags = list(TAG_COMBAT) /datum/round_event/spider_infestation announce_when = 400 diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm index e783f18ec025..0d0b9d478d03 100644 --- a/code/modules/events/stray_cargo.dm +++ b/code/modules/events/stray_cargo.dm @@ -8,6 +8,8 @@ category = EVENT_CATEGORY_BUREAUCRATIC description = "A pod containing a random supply crate lands on the station." admin_setup = list(/datum/event_admin_setup/set_location/stray_cargo, /datum/event_admin_setup/listed_options/stray_cargo) + track = EVENT_TRACK_MUNDANE + tags = list(TAG_COMMUNAL) /datum/event_admin_setup/set_location/stray_cargo input_text = "Aim pod at turf we're on?" diff --git a/code/modules/events/tram_malfunction.dm b/code/modules/events/tram_malfunction.dm index b5130a8c6934..312d045512ea 100644 --- a/code/modules/events/tram_malfunction.dm +++ b/code/modules/events/tram_malfunction.dm @@ -11,6 +11,8 @@ description = "Tram crossing signals malfunction, tram collision damage is increased." min_wizard_trigger_potency = 0 max_wizard_trigger_potency = 3 + track = EVENT_TRACK_MODERATE + tags = list(TAG_DESTRUCTIVE) //Check if there's a tram we can cause to malfunction. /datum/round_event_control/tram_malfunction/can_spawn_event(players_amt, allow_magic = FALSE) diff --git a/code/modules/events/wisdomcow.dm b/code/modules/events/wisdomcow.dm index 1ecd43797e58..7aa24b45a27f 100644 --- a/code/modules/events/wisdomcow.dm +++ b/code/modules/events/wisdomcow.dm @@ -10,6 +10,8 @@ /datum/event_admin_setup/listed_options/wisdom_cow, /datum/event_admin_setup/input_number/wisdom_cow, ) + track = EVENT_TRACK_MUNDANE + tags = list(TAG_COMMUNAL, TAG_POSITIVE) /datum/round_event/wisdomcow ///Location override that, if set causes the cow to spawn in a pre-determined locaction instead of randomly. @@ -56,5 +58,5 @@ /datum/event_admin_setup/input_number/wisdom_cow/apply_to_event(datum/round_event/wisdomcow/event) event.selected_experience = chosen_value - - + + diff --git a/code/modules/events/wormholes.dm b/code/modules/events/wormholes.dm index f80d432c8920..6bde3d3c738e 100644 --- a/code/modules/events/wormholes.dm +++ b/code/modules/events/wormholes.dm @@ -10,6 +10,8 @@ GLOBAL_LIST_EMPTY(all_wormholes) // So we can pick wormholes to teleport to description = "Space time anomalies appear on the station, randomly teleporting people who walk into them." min_wizard_trigger_potency = 3 max_wizard_trigger_potency = 7 + track = EVENT_TRACK_MODERATE + tags = list(TAG_COMMUNAL) /datum/round_event/wormholes announce_when = 10 diff --git a/config/events.json b/config/events.json new file mode 100644 index 000000000000..a30ccaf58ab5 --- /dev/null +++ b/config/events.json @@ -0,0 +1,13 @@ +{ + "/datum/round_event_control": + { + "weight" : 10, + "min_players" : 0, + "max_occurrences" : 100, + "earliest_start" : 20, + "track" : "Moderate", + "cost" : 1, + "reoccurence_penalty_multiplier" : 1, + "shared_occurence_type" : null + } +} diff --git a/config/game_options.txt b/config/game_options.txt index ce6d491a57af..94e6203385ca 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -540,3 +540,50 @@ MAXFINE 2000 METACURRENCY_NAME Metacoin TWITCH_KEY mrhouse + +## Gamemode configurations + +## Multipliers for points gained over time for event tracks. +MUNDANE_POINT_GAIN_MULTIPLIER 1 +MODERATE_POINT_GAIN_MULTIPLIER 1 +MAJOR_POINT_GAIN_MULTIPLIER 1 +ROLESET_POINT_GAIN_MULTIPLIER 1 +OBJECTIVES_POINT_GAIN_MULTIPLIER 1 + +## Multipliers for points to spend on roundstart events. +MUNDANE_ROUNDSTART_POINT_MULTIPLIER 1 +MODERATE_ROUNDSTART_POINT_MULTIPLIER 1 +MAJOR_ROUNDSTART_POINT_MULTIPLIER 1 +ROLESET_ROUNDSTART_POINT_MULTIPLIER 1 +OBJECTIVES_ROUNDSTART_POINT_MULTIPLIER 1 + +## Minimum population caps for event tracks to run their events. +MUNDANE_MIN_POP 0 +MODERATE_MIN_POP 0 +MAJOR_MIN_POP 0 +ROLESET_MIN_POP 0 +OBJECTIVES_MIN_POP 0 + +## Point thresholds for tracks to run events. The lesser the more frequent events will be. +MUNDANE_POINT_THRESHOLD 25 +MODERATE_POINT_THRESHOLD 50 +MAJOR_POINT_THRESHOLD 90 +ROLESET_POINT_THRESHOLD 120 +OBJECTIVES_POINT_THRESHOLD 130 + +## Allows the storyteller to scale event frequencies based on population +ALLOW_STORYTELLER_POP_SCALING + +## Thresholds that population frequency scalling penalize up to. +MUNDANE_POP_SCALE_THRESHOLD 10 +MODERATE_POP_SCALE_THRESHOLD 15 +MAJOR_POP_SCALE_THRESHOLD 40 +ROLESET_POP_SCALE_THRESHOLD 45 +OBJECTIVES_POP_SCALE_THRESHOLD 40 + +## The maximum penalties population scalling will apply to the tracks for having less pop than POP_SCALE_THRESHOLD. This is treated as percentages +MUNDANE_POP_SCALE_PENALTY 30 +MODERATE_POP_SCALE_PENALTY 30 +MAJOR_POP_SCALE_PENALTY 30 +ROLESET_POP_SCALE_PENALTY 30 +OBJECTIVES_POP_SCALE_PENALTY 30 diff --git a/monkestation/code/modules/new_antagonists/slasher/ghost_role.dm b/monkestation/code/modules/new_antagonists/slasher/ghost_role.dm index 216e9d103eaf..839af842845a 100644 --- a/monkestation/code/modules/new_antagonists/slasher/ghost_role.dm +++ b/monkestation/code/modules/new_antagonists/slasher/ghost_role.dm @@ -1,7 +1,7 @@ /datum/round_event_control/slasher name = "Slasher" typepath = /datum/round_event/ghost_role/slasher - weight = 0 // for now + weight = 14 // for now max_occurrences = 3 /datum/round_event/ghost_role/slasher diff --git a/monkestation/code/modules/storytellers/config.dm b/monkestation/code/modules/storytellers/config.dm new file mode 100644 index 000000000000..121a512099d1 --- /dev/null +++ b/monkestation/code/modules/storytellers/config.dm @@ -0,0 +1,149 @@ +///Gamemode related configs below +// Point Gain Multipliers +/datum/config_entry/number/mundane_point_gain_multiplier + config_entry_value = 1 + min_val = 0 + +/datum/config_entry/number/moderate_point_gain_multiplier + config_entry_value = 1 + min_val = 0 + +/datum/config_entry/number/major_point_gain_multiplier + config_entry_value = 1 + min_val = 0 + +/datum/config_entry/number/roleset_point_gain_multiplier + config_entry_value = 1 + min_val = 0 + +/datum/config_entry/number/objectives_point_gain_multiplier + config_entry_value = 1 + min_val = 0 + +// Roundstart points Multipliers +/datum/config_entry/number/mundane_roundstart_point_multiplier + config_entry_value = 1 + min_val = 0 + +/datum/config_entry/number/moderate_roundstart_point_multiplier + config_entry_value = 1 + min_val = 0 + +/datum/config_entry/number/major_roundstart_point_multiplier + config_entry_value = 1 + min_val = 0 + +/datum/config_entry/number/roleset_roundstart_point_multiplier + config_entry_value = 1 + min_val = 0 + +/datum/config_entry/number/objectives_roundstart_point_multiplier + config_entry_value = 1 + min_val = 0 + +// Minimum population +/datum/config_entry/number/mundane_min_pop + config_entry_value = MUNDANE_MIN_POP + integer = TRUE + min_val = 0 + +/datum/config_entry/number/moderate_min_pop + config_entry_value = MODERATE_MIN_POP + integer = TRUE + min_val = 0 + +/datum/config_entry/number/major_min_pop + config_entry_value = MAJOR_MIN_POP + integer = TRUE + min_val = 0 + +/datum/config_entry/number/roleset_min_pop + config_entry_value = ROLESET_MIN_POP + integer = TRUE + min_val = 0 + +/datum/config_entry/number/objectives_min_pop + config_entry_value = OBJECTIVES_MIN_POP + integer = TRUE + min_val = 0 + +// Point Thresholds +/datum/config_entry/number/mundane_point_threshold + config_entry_value = MUNDANE_POINT_THRESHOLD + integer = TRUE + min_val = 0 + +/datum/config_entry/number/moderate_point_threshold + config_entry_value = MODERATE_POINT_THRESHOLD + integer = TRUE + min_val = 0 + +/datum/config_entry/number/major_point_threshold + config_entry_value = MAJOR_POINT_THRESHOLD + integer = TRUE + min_val = 0 + +/datum/config_entry/number/roleset_point_threshold + config_entry_value = ROLESET_POINT_THRESHOLD + integer = TRUE + min_val = 0 + +/datum/config_entry/number/objectives_point_threshold + config_entry_value = OBJECTIVES_POINT_THRESHOLD + integer = TRUE + min_val = 0 + + +/datum/config_entry/flag/allow_storyteller_pop_scaling // Allows storyteller to scale down the event frequency by population + +// Pop scalling thresholds +/datum/config_entry/number/mundane_pop_scale_threshold + config_entry_value = MUNDANE_POP_SCALE_THRESHOLD + integer = TRUE + min_val = 0 + +/datum/config_entry/number/moderate_pop_scale_threshold + config_entry_value = MODERATE_POP_SCALE_THRESHOLD + integer = TRUE + min_val = 0 + +/datum/config_entry/number/major_pop_scale_threshold + config_entry_value = MAJOR_POP_SCALE_THRESHOLD + integer = TRUE + min_val = 0 + +/datum/config_entry/number/roleset_pop_scale_threshold + config_entry_value = ROLESET_POP_SCALE_THRESHOLD + integer = TRUE + min_val = 0 + +/datum/config_entry/number/objectives_pop_scale_threshold + config_entry_value = OBJECTIVES_POP_SCALE_THRESHOLD + integer = TRUE + min_val = 0 + +// Pop scalling penalties +/datum/config_entry/number/mundane_pop_scale_penalty + config_entry_value = MUNDANE_POP_SCALE_PENALTY + integer = TRUE + min_val = 0 + +/datum/config_entry/number/moderate_pop_scale_penalty + config_entry_value = MODERATE_POP_SCALE_PENALTY + integer = TRUE + min_val = 0 + +/datum/config_entry/number/major_pop_scale_penalty + config_entry_value = MAJOR_POP_SCALE_PENALTY + integer = TRUE + min_val = 0 + +/datum/config_entry/number/roleset_pop_scale_penalty + config_entry_value = ROLESET_POP_SCALE_PENALTY + integer = TRUE + min_val = 0 + +/datum/config_entry/number/objectives_pop_scale_penalty + config_entry_value = OBJECTIVES_POP_SCALE_PENALTY + integer = TRUE + min_val = 0 diff --git a/monkestation/code/modules/storytellers/gamemode_subsystem.dm b/monkestation/code/modules/storytellers/gamemode_subsystem.dm new file mode 100644 index 000000000000..d25ba06c5c43 --- /dev/null +++ b/monkestation/code/modules/storytellers/gamemode_subsystem.dm @@ -0,0 +1,1070 @@ +#define INIT_ORDER_GAMEMODE 70 + +SUBSYSTEM_DEF(gamemode) + name = "Gamemode" + init_order = INIT_ORDER_GAMEMODE + runlevels = RUNLEVEL_GAME + flags = SS_BACKGROUND | SS_KEEP_TIMING + wait = 2 SECONDS + + /// List of our event tracks for fast access during for loops. + var/list/event_tracks = EVENT_TRACKS + /// Our storyteller. He progresses our trackboards and picks out events + var/datum/storyteller/storyteller + /// Result of the storyteller vote. Defaults to the guide. + var/voted_storyteller = /datum/storyteller/guide + /// List of all the storytellers. Populated at init. Associative from type + var/list/storytellers = list() + /// Next process for our storyteller. The wait time is STORYTELLER_WAIT_TIME + var/next_storyteller_process = 0 + /// Associative list of even track points. + var/list/event_track_points = list( + EVENT_TRACK_MUNDANE = 0, + EVENT_TRACK_MODERATE = 0, + EVENT_TRACK_MAJOR = 0, + EVENT_TRACK_ROLESET = 0, + EVENT_TRACK_OBJECTIVES = 0 + ) + /// Last point amount gained of each track. Those are recorded for purposes of estimating how long until next event. + var/list/last_point_gains = list( + EVENT_TRACK_MUNDANE = 0, + EVENT_TRACK_MODERATE = 0, + EVENT_TRACK_MAJOR = 0, + EVENT_TRACK_ROLESET = 0, + EVENT_TRACK_OBJECTIVES = 0 + ) + /// Point thresholds at which the events are supposed to be rolled, it is also the base cost for events. + var/list/point_thresholds = list( + EVENT_TRACK_MUNDANE = MUNDANE_POINT_THRESHOLD, + EVENT_TRACK_MODERATE = MODERATE_POINT_THRESHOLD, + EVENT_TRACK_MAJOR = MAJOR_POINT_THRESHOLD, + EVENT_TRACK_ROLESET = ROLESET_POINT_THRESHOLD, + EVENT_TRACK_OBJECTIVES = OBJECTIVES_POINT_THRESHOLD + ) + + /// Minimum population thresholds for the tracks to fire off events. + var/list/min_pop_thresholds = list( + EVENT_TRACK_MUNDANE = MUNDANE_MIN_POP, + EVENT_TRACK_MODERATE = MODERATE_MIN_POP, + EVENT_TRACK_MAJOR = MAJOR_MIN_POP, + EVENT_TRACK_ROLESET = ROLESET_MIN_POP, + EVENT_TRACK_OBJECTIVES = OBJECTIVES_MIN_POP + ) + + /// Configurable multipliers for point gain over time. + var/list/point_gain_multipliers = list( + EVENT_TRACK_MUNDANE = 1, + EVENT_TRACK_MODERATE = 1, + EVENT_TRACK_MAJOR = 1, + EVENT_TRACK_ROLESET = 1, + EVENT_TRACK_OBJECTIVES = 1 + ) + /// Configurable multipliers for roundstart points. + var/list/roundstart_point_multipliers = list( + EVENT_TRACK_MUNDANE = 1, + EVENT_TRACK_MODERATE = 1, + EVENT_TRACK_MAJOR = 1, + EVENT_TRACK_ROLESET = 1, + EVENT_TRACK_OBJECTIVES = 1 + ) + /// Whether we allow pop scaling. This is configured by config, or the storyteller UI + var/allow_pop_scaling = TRUE + + /// Associative list of pop scale thresholds. + var/list/pop_scale_thresholds = list( + EVENT_TRACK_MUNDANE = MUNDANE_POP_SCALE_THRESHOLD, + EVENT_TRACK_MODERATE = MODERATE_POP_SCALE_THRESHOLD, + EVENT_TRACK_MAJOR = MAJOR_POP_SCALE_THRESHOLD, + EVENT_TRACK_ROLESET = ROLESET_POP_SCALE_THRESHOLD, + EVENT_TRACK_OBJECTIVES = OBJECTIVES_POP_SCALE_THRESHOLD + ) + + /// Associative list of pop scale penalties. + var/list/pop_scale_penalties = list( + EVENT_TRACK_MUNDANE = MUNDANE_POP_SCALE_PENALTY, + EVENT_TRACK_MODERATE = MODERATE_POP_SCALE_PENALTY, + EVENT_TRACK_MAJOR = MAJOR_POP_SCALE_PENALTY, + EVENT_TRACK_ROLESET = ROLESET_POP_SCALE_PENALTY, + EVENT_TRACK_OBJECTIVES = OBJECTIVES_POP_SCALE_PENALTY + ) + + /// Associative list of active multipliers from pop scale penalty. + var/list/current_pop_scale_multipliers = list( + EVENT_TRACK_MUNDANE = 1, + EVENT_TRACK_MODERATE = 1, + EVENT_TRACK_MAJOR = 1, + EVENT_TRACK_ROLESET = 1, + EVENT_TRACK_OBJECTIVES = 1, + ) + + + + /// Associative list of control events by their track category. Compiled in Init + var/list/event_pools = list() + + /// Events that we have scheduled to run in the nearby future + var/list/scheduled_events = list() + + /// Associative list of tracks to forced event controls. For admins to force events (though they can still invoke them freely outside of the track system) + var/list/forced_next_events = list() + + var/list/control = list() //list of all datum/round_event_control. Used for selecting events based on weight and occurrences. + var/list/running = list() //list of all existing /datum/round_event + var/list/currentrun = list() + + /// List of all uncategorized events, because they were wizard or holiday events + var/list/uncategorized = list() + + var/list/holidays //List of all holidays occuring today or null if no holidays + + /// Event frequency multiplier, it exists because wizard, eugh. + var/event_frequency_multiplier = 1 + + /// Current preview page for the statistics UI. + var/statistics_track_page = EVENT_TRACK_MUNDANE + /// Page of the UI panel. + var/panel_page = GAMEMODE_PANEL_MAIN + /// Whether we are viewing the roundstart events or not + var/roundstart_event_view = TRUE + + /// Whether the storyteller has been halted + var/halted_storyteller = FALSE + + /// Ready players for roundstart events. + var/ready_players = 0 + var/active_players = 0 + var/head_crew = 0 + var/eng_crew = 0 + var/sec_crew = 0 + var/med_crew = 0 + + var/wizardmode = FALSE + +/datum/controller/subsystem/gamemode/Initialize(time, zlevel) + // Populate event pools + for(var/track in event_tracks) + event_pools[track] = list() + + // Populate storytellers + for(var/type in subtypesof(/datum/storyteller)) + storytellers[type] = new type() + + for(var/type in typesof(/datum/round_event_control)) + var/datum/round_event_control/event = new type() + if(!event.typepath || !event.name) + continue //don't want this one! leave it for the garbage collector + control += event //add it to the list of all events (controls) + getHoliday() + + load_config_vars() + load_event_config_vars() + + ///Seeding events into track event pools needs to happen after event config vars are loaded + for(var/datum/round_event_control/event as anything in control) + if(event.holidayID || event.wizardevent) + uncategorized += event + continue + event_pools[event.track] += event //Add it to the categorized event pools +// return ..() + + +/datum/controller/subsystem/gamemode/fire(resumed = FALSE) + if(!resumed) + src.currentrun = running.Copy() + + ///Handle scheduled events + for(var/datum/scheduled_event/sch_event in scheduled_events) + if(world.time >= sch_event.start_time) + sch_event.try_fire() + else if(!sch_event.alerted_admins && world.time >= sch_event.start_time - 1 MINUTES) + ///Alert admins 1 minute before running and allow them to cancel or refund the event, once again. + sch_event.alerted_admins = TRUE + message_admins("Scheduled Event: [sch_event.event] will run in [(sch_event.start_time - world.time) / 10] seconds. (CANCEL) (REFUND)") + + if(!halted_storyteller && next_storyteller_process <= world.time && storyteller) + // We update crew information here to adjust population scalling and event thresholds for the storyteller. + update_crew_infos() + next_storyteller_process = world.time + STORYTELLER_WAIT_TIME + storyteller.process(STORYTELLER_WAIT_TIME * 0.1) + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while(currentrun.len) + var/datum/thing = currentrun[currentrun.len] + currentrun.len-- + if(thing) + thing.process(wait * 0.1) + else + running.Remove(thing) + if (MC_TICK_CHECK) + return + +/// Gets the number of antagonists the antagonist injection events will stop rolling after. +/datum/controller/subsystem/gamemode/proc/get_antag_cap() + var/cap = FLOOR((get_correct_popcount() / ANTAG_CAP_DENOMINATOR), 1) + ANTAG_CAP_FLAT + return cap + +/// Whether events can inject more antagonists into the round +/datum/controller/subsystem/gamemode/proc/can_inject_antags() + return (get_antag_cap() > GLOB.antagonists.len) + +/// Gets candidates for antagonist roles. +/datum/controller/subsystem/gamemode/proc/get_candidates(be_special, job_ban, observers, ready_newplayers, living_players, required_time, inherit_required_time = TRUE, midround_antag_pref, no_antags = TRUE, list/restricted_roles) + var/list/candidates = list() + var/list/candidate_candidates = list() //lol + + for(var/mob/player as anything in GLOB.player_list) + if(ready_newplayers && isnewplayer(player)) + var/mob/dead/new_player/new_player = player + if(new_player.ready == PLAYER_READY_TO_PLAY && new_player.mind && new_player.check_preferences()) + candidate_candidates += player + else if (observers && isobserver(player)) + candidate_candidates += player + else if (living_players && isliving(player)) + candidate_candidates += player + + for(var/mob/candidate as anything in candidate_candidates) + if(QDELETED(candidate) || !candidate.key || !candidate.client || !candidate.mind) + continue + if(no_antags && candidate.mind.special_role) + continue + if(restricted_roles && (candidate.mind.assigned_role.title in restricted_roles)) + continue + if(be_special) + if(!(candidate.client.prefs) || !(be_special in candidate.client.prefs.be_special)) + continue + + var/time_to_check + if(required_time) + time_to_check = required_time + else if (inherit_required_time) + time_to_check = GLOB.special_roles[be_special] + + if(time_to_check && candidate.client.get_remaining_days(time_to_check) > 0) + continue + + if(midround_antag_pref) + continue + + if(job_ban && is_banned_from(candidate.ckey, list(job_ban, ROLE_SYNDICATE))) + continue + candidates += candidate + return candidates + +/// Gets the correct popcount, returning READY people if roundstart, and active people if not. +/datum/controller/subsystem/gamemode/proc/get_correct_popcount() + if(SSticker.HasRoundStarted()) + update_crew_infos() + return active_players + else + calculate_ready_players() + return ready_players + +/// Refunds and removes a scheduled event. +/datum/controller/subsystem/gamemode/proc/refund_scheduled_event(datum/scheduled_event/refunded) + if(refunded.cost) + var/track_type = refunded.event.track + event_track_points[track_type] += refunded.cost + remove_scheduled_event(refunded) + +/// Removes a scheduled event. +/datum/controller/subsystem/gamemode/proc/remove_scheduled_event(datum/scheduled_event/removed) + scheduled_events -= removed + qdel(removed) + +/// We need to calculate ready players for the sake of roundstart events becoming eligible. +/datum/controller/subsystem/gamemode/proc/calculate_ready_players() + ready_players = 0 + for(var/mob/dead/new_player/player as anything in GLOB.new_player_list) + if(player.ready == PLAYER_READY_TO_PLAY) + ready_players++ + +/// We roll points to be spent for roundstart events, including antagonists. +/datum/controller/subsystem/gamemode/proc/roll_pre_setup_points() + if(storyteller.disable_distribution || halted_storyteller) + return + /// Distribute points + for(var/track in event_track_points) + var/base_amt + var/gain_amt + switch(track) + if(EVENT_TRACK_MUNDANE) + base_amt = ROUNDSTART_MUNDANE_BASE + gain_amt = ROUNDSTART_MUNDANE_GAIN + if(EVENT_TRACK_MODERATE) + base_amt = ROUNDSTART_MODERATE_BASE + gain_amt = ROUNDSTART_MODERATE_GAIN + if(EVENT_TRACK_MAJOR) + base_amt = ROUNDSTART_MAJOR_BASE + gain_amt = ROUNDSTART_MAJOR_GAIN + if(EVENT_TRACK_ROLESET) + base_amt = ROUNDSTART_ROLESET_BASE + gain_amt = ROUNDSTART_ROLESET_GAIN + if(EVENT_TRACK_OBJECTIVES) + base_amt = ROUNDSTART_OBJECTIVES_BASE + gain_amt = ROUNDSTART_OBJECTIVES_GAIN + var/calc_value = base_amt + (gain_amt * ready_players) + calc_value *= roundstart_point_multipliers[track] + calc_value *= storyteller.starting_point_multipliers[track] + calc_value *= (rand(100 - storyteller.roundstart_points_variance,100 + storyteller.roundstart_points_variance)/100) + event_track_points[track] = round(calc_value) + + /// If the storyteller guarantees an antagonist roll, add points to make it so. + if(storyteller.guarantees_roundstart_roleset && event_track_points[EVENT_TRACK_ROLESET] < point_thresholds[EVENT_TRACK_ROLESET]) + event_track_points[EVENT_TRACK_ROLESET] = point_thresholds[EVENT_TRACK_ROLESET] + + /// If we have any forced events, ensure we get enough points for them + for(var/track in event_tracks) + if(forced_next_events[track] && event_track_points[track] < point_thresholds[track]) + event_track_points[track] = point_thresholds[track] + +/// At this point we've rolled roundstart events and antags and we handle leftover points here. +/datum/controller/subsystem/gamemode/proc/handle_post_setup_points() + for(var/track in event_track_points) //Just halve the points for now. + event_track_points[track] *= 0.5 + +/// Because roundstart events need 2 steps of firing for purposes of antags, here is the first step handled, happening before occupation division. +/datum/controller/subsystem/gamemode/proc/handle_pre_setup_roundstart_events() + if(storyteller.disable_distribution) + return + if(halted_storyteller) + message_admins("WARNING: Didn't roll roundstart events (including antagonists) due to the storyteller being halted.") + return + while(TRUE) + if(!storyteller.handle_tracks()) + break + +/// Second step of handlind roundstart events, happening after people spawn. +/datum/controller/subsystem/gamemode/proc/handle_post_setup_roundstart_events() + /// Start all roundstart events on post_setup immediately + for(var/datum/round_event/event as anything in running) + if(!event.control.roundstart) + continue + ASYNC + event.try_start() +// INVOKE_ASYNC(event, /datum/round_event.proc/try_start) + +/// Schedules an event to run later. +/datum/controller/subsystem/gamemode/proc/schedule_event(datum/round_event_control/passed_event, passed_time, passed_cost, passed_ignore, passed_announce) + var/datum/scheduled_event/scheduled = new (passed_event, world.time + passed_time, passed_cost, passed_ignore, passed_announce) + var/round_started = SSticker.HasRoundStarted() + if(round_started) + message_admins("Event: [passed_event] has been scheduled to run in [passed_time / 10] seconds. (CANCEL) (REFUND)") + else //Only roundstart events can be scheduled before round start + message_admins("Event: [passed_event] has been scheduled to run on roundstart. (CANCEL)") + scheduled_events += scheduled + +/datum/controller/subsystem/gamemode/proc/update_crew_infos() + // Very similar logic to `get_active_player_count()` + active_players = 0 + head_crew = 0 + eng_crew = 0 + med_crew = 0 + sec_crew = 0 + for(var/mob/player_mob as anything in GLOB.player_list) + if(!player_mob.client) + continue + if(player_mob.stat) //If they're alive + continue + if(player_mob.client.is_afk()) //If afk + continue + if(!ishuman(player_mob)) + continue + active_players++ + if(player_mob.mind?.assigned_role) + var/datum/job/player_role = player_mob.mind.assigned_role + if(player_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) + head_crew++ + if(player_role.departments_bitflags & DEPARTMENT_BITFLAG_ENGINEERING) + eng_crew++ + if(player_role.departments_bitflags & DEPARTMENT_BITFLAG_MEDICAL) + med_crew++ + if(player_role.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY) + sec_crew++ + update_pop_scaling() + +/datum/controller/subsystem/gamemode/proc/update_pop_scaling() + for(var/track in event_tracks) + var/low_pop_bound = min_pop_thresholds[track] + var/high_pop_bound = pop_scale_thresholds[track] + var/scale_penalty = pop_scale_penalties[track] + + var/perceived_pop = min(max(low_pop_bound, active_players), high_pop_bound) + + var/divisor = high_pop_bound - low_pop_bound + /// If the bounds are equal, we'd be dividing by zero or worse, if upper is smaller than lower, we'd be increasing the factor, just make it 1 and continue. + /// this is only a problem for bad configs + if(divisor <= 0) + current_pop_scale_multipliers[track] = 1 + continue + var/scalar = (perceived_pop - low_pop_bound) / divisor + var/penalty = scale_penalty - (scale_penalty * scalar) + var/calculated_multiplier = 1 - (penalty / 100) + + current_pop_scale_multipliers[track] = calculated_multiplier + +/datum/controller/subsystem/gamemode/proc/TriggerEvent(datum/round_event_control/event) + . = event.preRunEvent() + if(. == EVENT_CANT_RUN)//we couldn't run this event for some reason, set its max_occurrences to 0 + event.max_occurrences = 0 + else if(. == EVENT_READY) + event.run_event(random = TRUE) // fallback to dynamic + +///Resets frequency multiplier. +/datum/controller/subsystem/gamemode/proc/resetFrequency() + event_frequency_multiplier = 1 + +/* /client/proc/forceEvent() + set name = "Trigger Event" + set category = "Admin.Events" + if(!holder ||!check_rights(R_FUN)) + return + holder.forceEvent(usr) */ + +/* /datum/admins/forceEvent(mob/user) + SSgamemode.event_panel(user) */ + + +////////////// +// HOLIDAYS // +////////////// +//Uncommenting ALLOW_HOLIDAYS in config.txt will enable holidays + +//It's easy to add stuff. Just add a holiday datum in code/modules/holiday/holidays.dm +//You can then check if it's a special day in any code in the game by doing if(SSgamemode.holidays["Groundhog Day"]) + +//You can also make holiday random events easily thanks to Pete/Gia's system. +//simply make a random event normally, then assign it a holidayID string which matches the holiday's name. +//Anything with a holidayID, which isn't in the holidays list, will never occur. + +//Please, Don't spam stuff up with stupid stuff (key example being april-fools Pooh/ERP/etc), +//And don't forget: CHECK YOUR CODE!!!! We don't want any zero-day bugs which happen only on holidays and never get found/fixed! + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +//ALSO, MOST IMPORTANTLY: Don't add stupid stuff! Discuss bonus content with Project-Heads first please!// +////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +//sets up the holidays and holidays list +/datum/controller/subsystem/gamemode/proc/getHoliday() + if(!CONFIG_GET(flag/allow_holidays)) + return // Holiday stuff was not enabled in the config! + for(var/H in subtypesof(/datum/holiday)) + var/datum/holiday/holiday = new H() + var/delete_holiday = TRUE + for(var/timezone in holiday.timezones) + var/time_in_timezone = world.realtime + timezone HOURS + + var/YYYY = text2num(time2text(time_in_timezone, "YYYY")) // get the current year + var/MM = text2num(time2text(time_in_timezone, "MM")) // get the current month + var/DD = text2num(time2text(time_in_timezone, "DD")) // get the current day + var/DDD = time2text(time_in_timezone, "DDD") // get the current weekday + + if(holiday.shouldCelebrate(DD, MM, YYYY, DDD)) + holiday.celebrate() + LAZYSET(holidays, holiday.name, holiday) + delete_holiday = FALSE + break + if(delete_holiday) + qdel(holiday) + + if(holidays) + holidays = shuffle(holidays) + // regenerate station name because holiday prefixes. + set_station_name(new_station_name()) + world.update_status() + +/datum/controller/subsystem/gamemode/proc/toggleWizardmode() + wizardmode = !wizardmode //TODO: decide what to do with wiz events + message_admins("Summon Events has been [wizardmode ? "enabled, events will occur [SSgamemode.event_frequency_multiplier] times as fast" : "disabled"]!") + log_game("Summon Events was [wizardmode ? "enabled" : "disabled"]!") + +///Attempts to select players for special roles the mode might have. +/datum/controller/subsystem/gamemode/proc/pre_setup() + calculate_ready_players() + roll_pre_setup_points() + handle_pre_setup_roundstart_events() + return TRUE + +///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things +/datum/controller/subsystem/gamemode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report. + if(!report) + report = !CONFIG_GET(flag/no_intercept_report) + addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) + + if(CONFIG_GET(flag/reopen_roundstart_suicide_roles)) + var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay) + if(delay) + delay = (delay SECONDS) + else + delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined. + addtimer(CALLBACK(GLOBAL_PROC, .proc/reopen_roundstart_suicide_roles), delay) + + if(SSdbcore.Connect()) + var/list/to_set = list() + var/arguments = list() + if(storyteller) + to_set += "game_mode = :game_mode" + arguments["game_mode"] = storyteller.name + if(GLOB.revdata.originmastercommit) + to_set += "commit_hash = :commit_hash" + arguments["commit_hash"] = GLOB.revdata.originmastercommit + if(to_set.len) + arguments["round_id"] = GLOB.round_id + var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id", + arguments + ) + query_round_game_mode.Execute() + qdel(query_round_game_mode) + generate_station_goals() + handle_post_setup_roundstart_events() + handle_post_setup_points() + roundstart_event_view = FALSE + return TRUE + + +///Handles late-join antag assignments +/datum/controller/subsystem/gamemode/proc/make_antag_chance(mob/living/carbon/human/character) + return + +/datum/controller/subsystem/gamemode/proc/check_finished(force_ending) //to be called by SSticker + if(!SSticker.setup_done) + return FALSE + if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME)) + return TRUE + if(GLOB.station_was_nuked) + return TRUE + if(force_ending) + return TRUE + +/* + * Generate a list of station goals available to purchase to report to the crew. + * + * Returns a formatted string all station goals that are available to the station. + */ +/datum/controller/subsystem/gamemode/proc/generate_station_goal_report() + if(!GLOB.station_goals.len) + return + . = "
Special Orders for [station_name()]:
" + for(var/datum/station_goal/station_goal as anything in GLOB.station_goals) + station_goal.on_report() + . += station_goal.get_report() + return + +/* + * Generate a list of active station traits to report to the crew. + * + * Returns a formatted string of all station traits (that are shown) affecting the station. + */ +/datum/controller/subsystem/gamemode/proc/generate_station_trait_report() + if(!SSstation.station_traits.len) + return + . = "
Identified shift divergencies:
" + for(var/datum/station_trait/station_trait as anything in SSstation.station_traits) + if(!station_trait.show_in_report) + continue + . += "[station_trait.get_report()]
" + return + +/* /proc/reopen_roundstart_suicide_roles() + var/include_command = CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions) + var/list/reopened_jobs = list() + for(var/mob/living/quitter in GLOB.suicided_mob_list) + var/datum/job/job = SSjob.GetJob(quitter.job) + if(!job || !(job.job_flags & JOB_REOPEN_ON_ROUNDSTART_LOSS)) + continue + if(!include_command && job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) + continue + job.current_positions = max(job.current_positions - 1, 0) + reopened_jobs += quitter.job + if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report)) + if(reopened_jobs.len) + var/reopened_job_report_positions + for(var/dead_dudes_job in reopened_jobs) + reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]" + var/suicide_command_report = "Central Command Human Resources Board
\ + Notice of Personnel Change

\ + To personnel management staff aboard [station_name()]:

\ + Our medical staff have detected a series of anomalies in the vital sensors \ + of some of the staff aboard your station.

\ + Further investigation into the situation on our end resulted in us discovering \ + a series of rather... unforturnate decisions that were made on the part of said staff.

\ + As such, we have taken the liberty to automatically reopen employment opportunities for the positions of the crew members \ + who have decided not to partake in our research. We will be forwarding their cases to our employment review board \ + to determine their eligibility for continued service with the company (and of course the \ + continued storage of cloning records within the central medical backup server.)

\ + The following positions have been reopened on our behalf:

\ + [reopened_job_report_positions]
" + print_command_report(suicide_command_report, "Central Command Personnel Update") */ + +////////////////////////// +//Reports player logouts// +////////////////////////// +/* /proc/display_roundstart_logout_report() + var/list/msg = list("[SPAN_BOLDNOTICE("Roundstart logout report")]\n\n") + for(var/i in GLOB.mob_living_list) + var/mob/living/L = i + var/mob/living/carbon/C = L + if (istype(C) && !C.last_mind) + continue // never had a client + if(L.ckey && !GLOB.directory[L.ckey]) + msg += "[L.name] ([L.key]), the [L.job] (Disconnected)\n" + if(L.ckey && L.client) + var/failed = FALSE + if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something) + msg += "[L.name] ([L.key]), the [L.job] (Connected, Inactive)\n" + failed = TRUE //AFK client + if(!failed && L.stat) + if(L.suiciding) //Suicider + msg += "[L.name] ([L.key]), the [L.job] ([SPAN_BOLDANNOUNCE("Suicide")])\n" + failed = TRUE //Disconnected client + if(!failed && (L.stat == UNCONSCIOUS || L.stat == HARD_CRIT)) + msg += "[L.name] ([L.key]), the [L.job] (Dying)\n" + failed = TRUE //Unconscious + if(!failed && L.stat == DEAD) + msg += "[L.name] ([L.key]), the [L.job] (Dead)\n" + failed = TRUE //Dead + continue //Happy connected client + for(var/mob/dead/observer/D in GLOB.dead_mob_list) + if(D.mind && D.mind.current == L) + if(L.stat == DEAD) + if(L.suiciding) //Suicider + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] ([SPAN_BOLDANNOUNCE("Suicide")])\n" + continue //Disconnected client + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" + continue //Dead mob, ghost abandoned + else + if(D.can_reenter_corpse) + continue //Adminghost, or cult/wizard ghost + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] ([SPAN_BOLDANNOUNCE("Ghosted")])\n" + continue //Ghosted while alive + for (var/C in GLOB.admins) + to_chat(C, msg.Join()) */ + +/datum/controller/subsystem/gamemode/proc/generate_station_goals() + var/list/possible = subtypesof(/datum/station_goal) + var/goal_weights = 0 + while(possible.len && goal_weights < 1) // station goal budget is 1 + var/datum/station_goal/picked = pick_n_take(possible) + goal_weights += initial(picked.weight) + GLOB.station_goals += new picked + +//Set result and news report here +/datum/controller/subsystem/gamemode/proc/set_round_result() + SSticker.mode_result = "undefined" + if(GLOB.station_was_nuked) + SSticker.news_report = STATION_DESTROYED_NUKE + if(EMERGENCY_ESCAPED_OR_ENDGAMED) + SSticker.news_report = STATION_EVACUATED + if(SSshuttle.emergency.is_hijacked()) + SSticker.news_report = SHUTTLE_HIJACK + +/// Loads json event config values from events.txt +/datum/controller/subsystem/gamemode/proc/load_event_config_vars() + var/json_file = file("[global.config.directory]/events.json") + if(!fexists(json_file)) + return + var/list/decoded = json_decode(file2text(json_file)) + for(var/event_text_path in decoded) + var/event_path = text2path(event_text_path) + var/datum/round_event_control/event + for(var/datum/round_event_control/iterated_event as anything in control) + if(iterated_event.type == event_path) + event = iterated_event + break + if(!event) + continue + var/list/var_list = decoded[event_text_path] + for(var/variable in var_list) + var/value = var_list[variable] + switch(variable) + if("weight") + event.weight = value + if("min_players") + event.min_players = value + if("max_occurrences") + event.max_occurrences = value + if("earliest_start") + event.earliest_start = value * (1 MINUTES) + if("track") + if(value in event_tracks) + event.track = value + if("cost") + event.cost = value + if("reoccurence_penalty_multiplier") + event.reoccurence_penalty_multiplier = value + if("shared_occurence_type") + if(!isnull(value)) + value = text2path(value) + event.shared_occurence_type = value + +/// Loads config values from game_options.txt +/datum/controller/subsystem/gamemode/proc/load_config_vars() + point_gain_multipliers[EVENT_TRACK_MUNDANE] = CONFIG_GET(number/mundane_point_gain_multiplier) + point_gain_multipliers[EVENT_TRACK_MODERATE] = CONFIG_GET(number/moderate_point_gain_multiplier) + point_gain_multipliers[EVENT_TRACK_MAJOR] = CONFIG_GET(number/major_point_gain_multiplier) + point_gain_multipliers[EVENT_TRACK_ROLESET] = CONFIG_GET(number/roleset_point_gain_multiplier) + point_gain_multipliers[EVENT_TRACK_OBJECTIVES] = CONFIG_GET(number/objectives_point_gain_multiplier) + + roundstart_point_multipliers[EVENT_TRACK_MUNDANE] = CONFIG_GET(number/mundane_roundstart_point_multiplier) + roundstart_point_multipliers[EVENT_TRACK_MODERATE] = CONFIG_GET(number/moderate_roundstart_point_multiplier) + roundstart_point_multipliers[EVENT_TRACK_MAJOR] = CONFIG_GET(number/major_roundstart_point_multiplier) + roundstart_point_multipliers[EVENT_TRACK_ROLESET] = CONFIG_GET(number/roleset_roundstart_point_multiplier) + roundstart_point_multipliers[EVENT_TRACK_OBJECTIVES] = CONFIG_GET(number/objectives_roundstart_point_multiplier) + + min_pop_thresholds[EVENT_TRACK_MUNDANE] = CONFIG_GET(number/mundane_min_pop) + min_pop_thresholds[EVENT_TRACK_MODERATE] = CONFIG_GET(number/moderate_min_pop) + min_pop_thresholds[EVENT_TRACK_MAJOR] = CONFIG_GET(number/major_min_pop) + min_pop_thresholds[EVENT_TRACK_ROLESET] = CONFIG_GET(number/roleset_min_pop) + min_pop_thresholds[EVENT_TRACK_OBJECTIVES] = CONFIG_GET(number/objectives_min_pop) + + point_thresholds[EVENT_TRACK_MUNDANE] = CONFIG_GET(number/mundane_point_threshold) + point_thresholds[EVENT_TRACK_MODERATE] = CONFIG_GET(number/moderate_point_threshold) + point_thresholds[EVENT_TRACK_MAJOR] = CONFIG_GET(number/major_point_threshold) + point_thresholds[EVENT_TRACK_ROLESET] = CONFIG_GET(number/roleset_point_threshold) + point_thresholds[EVENT_TRACK_OBJECTIVES] = CONFIG_GET(number/objectives_point_threshold) + +/datum/controller/subsystem/gamemode/proc/storyteller_vote_choices() + var/client_amount = GLOB.clients.len + var/list/choices = list() + for(var/storyteller_type in storytellers) + var/datum/storyteller/storyboy = storytellers[storyteller_type] + if(!storyboy.votable) + continue + if((storyboy.population_min && storyboy.population_min > client_amount) || (storyboy.population_max && storyboy.population_max < client_amount)) + continue + choices += storyboy.name + ///Because the vote subsystem is dumb and does not support any descriptions, we dump them into world. + to_chat(world, span_notice("[storyboy.name]")) + to_chat(world, span_notice("[storyboy.desc]")) + return choices + +/datum/controller/subsystem/gamemode/proc/storyteller_vote_result(winner_name) + /// Find the winner + /// Hijacking the proc because we don't have a vote right now.. + var/datum/storyteller/storyteller = pick(storytellers) + message_admins("We picked [storyteller]") + voted_storyteller = storyteller + if(storyteller) + return + for(var/storyteller_type in storytellers) + var/datum/storyteller/storyboy = storytellers[storyteller_type] + if(storyboy.name == winner_name) + voted_storyteller = storyteller_type + break + +/datum/controller/subsystem/gamemode/proc/init_storyteller() + /// Hijacking the proc because we don't have a vote right now.. + var/datum/storyteller/storyteller_pick = pick(storytellers) + message_admins("We picked [storyteller_pick] for this rounds storyteller, randomly.") + voted_storyteller = storyteller_pick + if(storyteller) // If this is true, then an admin bussed one, don't overwrite it + return + set_storyteller(voted_storyteller) + +/datum/controller/subsystem/gamemode/proc/set_storyteller(passed_type) + if(!storytellers[passed_type]) + message_admins("Attempted to set an invalid storyteller type: [passed_type].") + CRASH("Attempted to set an invalid storyteller type: [passed_type].") + storyteller = storytellers[passed_type] + to_chat(world, span_notice("Storyteller is [storyteller.name]!")) + to_chat(world, span_notice("[storyteller.welcome_text]")) + +/// Panel containing information, variables and controls about the gamemode and scheduled event +/datum/controller/subsystem/gamemode/proc/admin_panel(mob/user) + update_crew_infos() + var/round_started = SSticker.HasRoundStarted() + var/list/dat = list() + dat += "Storyteller: [storyteller ? "[storyteller.name]" : "None"] " + dat += " HALT Storyteller Event Panel Set Storyteller Refresh" + dat += "
Storyteller determines points gained, event chances, and is the entity responsible for rolling events." + dat += "
Active Players: [active_players] (Head: [head_crew], Sec: [sec_crew], Eng: [eng_crew], Med: [med_crew])" + dat += "
" + dat += "Main" + dat += " Variables" + dat += "
" + switch(panel_page) + if(GAMEMODE_PANEL_VARIABLES) + dat += "Reload Config Vars Configs located in game_options.txt." + dat += "
Point Gains Multipliers (only over time):" + dat += "
This affects points gained over time towards scheduling new events of the tracks." + for(var/track in event_tracks) + dat += "
[track]: [point_gain_multipliers[track]]" + dat += "
" + + dat += "Roundstart Points Multipliers:" + dat += "
This affects points generated for roundstart events and antagonists." + for(var/track in event_tracks) + dat += "
[track]: [roundstart_point_multipliers[track]]" + dat += "
" + + dat += "Minimum Population for Tracks:" + dat += "
This are the minimum population caps for events to be able to run." + for(var/track in event_tracks) + dat += "
[track]: [min_pop_thresholds[track]]" + dat += "
" + + dat += "Point Thresholds:" + dat += "
Those are thresholds the tracks require to reach with points to make an event." + for(var/track in event_tracks) + dat += "
[track]: [point_thresholds[track]]" + + if(GAMEMODE_PANEL_MAIN) + var/even = TRUE + dat += "

Event Tracks:

" + dat += "Every track represents progression towards scheduling an event of it's severity" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + for(var/track in event_tracks) + even = !even + var/background_cl = even ? "#17191C" : "#23273C" + var/lower = event_track_points[track] + var/upper = point_thresholds[track] + var/percent = round((lower/upper)*100) + var/next = 0 + var/last_points = last_point_gains[track] + if(last_points) + next = round((upper - lower) / last_points / STORYTELLER_WAIT_TIME * 40 / 6) / 10 + dat += "" + dat += "" //Track + dat += "" //Progress + dat += "" //Next + var/datum/round_event_control/forced_event = forced_next_events[track] + var/forced = forced_event ? "[forced_event.name] X" : "" + dat += "" //Forced + dat += "" //Actions + dat += "" + dat += "
TrackProgressNextForcedActions
[track][percent]% ([lower]/[upper])~[next] m.[forced]Set Pts. Next Event
" + + dat += "

Scheduled Events:

" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + var/sorted_scheduled = list() + for(var/datum/scheduled_event/scheduled as anything in scheduled_events) + sorted_scheduled[scheduled] = scheduled.start_time + sortTim(sorted_scheduled, cmp=/proc/cmp_numeric_asc, associative = TRUE) + even = TRUE + for(var/datum/scheduled_event/scheduled as anything in sorted_scheduled) + even = !even + var/background_cl = even ? "#17191C" : "#23273C" + dat += "" + dat += "" //Name + dat += "" //Severity + var/time = (scheduled.event.roundstart && !round_started) ? "ROUNDSTART" : "[(scheduled.start_time - world.time) / (1 SECONDS)] s." + dat += "" //Time + dat += "" //Actions + dat += "" + dat += "
NameSeverityTimeActions
[scheduled.event.name][scheduled.event.track][time][scheduled.get_href_actions()]
" + + dat += "

Running Events:

" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + even = TRUE + for(var/datum/round_event/event as anything in running) + even = !even + var/background_cl = even ? "#17191C" : "#23273C" + dat += "" + dat += "" //Name + dat += "" //Actions + dat += "" + dat += "
NameActions
[event.control.name]-TBA-
" + + var/datum/browser/popup = new(user, "gamemode_admin_panel", "Gamemode Panel", 670, 650) + popup.set_content(dat.Join()) + popup.open() + + /// Panel containing information and actions regarding events +/datum/controller/subsystem/gamemode/proc/event_panel(mob/user) + var/list/dat = list() + if(storyteller) + dat += "Storyteller: [storyteller.name]" + dat += "
Repetition penalty multiplier: [storyteller.event_repetition_multiplier]" + dat += "
Cost variance: [storyteller.cost_variance]" + if(storyteller.tag_multipliers) + dat += "
Tag multipliers:" + for(var/tag in storyteller.tag_multipliers) + dat += "[tag]:[storyteller.tag_multipliers[tag]] | " + storyteller.calculate_weights(statistics_track_page) + else + dat += "Storyteller: None
Weight and chance statistics will be inaccurate due to the present lack of a storyteller." + dat += "
Roundstart Events Forced Roundstart events will use rolled points, and are guaranteed to trigger (even if the used points are not enough)" + dat += "
Avg. event intervals: " + for(var/track in event_tracks) + if(last_point_gains[track]) + var/est_time = round(point_thresholds[track] / last_point_gains[track] / STORYTELLER_WAIT_TIME * 40 / 6) / 10 + dat += "[track]: ~[est_time] m. | " + dat += "
" + for(var/track in EVENT_PANEL_TRACKS) + dat += "[track]" + dat += "
" + /// Create event info and stats table + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + var/even = TRUE + var/total_weight = 0 + var/list/event_lookup + switch(statistics_track_page) + if(ALL_EVENTS) + event_lookup = control + if(UNCATEGORIZED_EVENTS) + event_lookup = uncategorized + else + event_lookup = event_pools[statistics_track_page] + var/list/assoc_spawn_weight = list() + for(var/datum/round_event_control/event as anything in event_lookup) + if(event.roundstart != roundstart_event_view) + continue + if(event.can_spawn_event()) + total_weight += event.calculated_weight + assoc_spawn_weight[event] = event.calculated_weight + else + assoc_spawn_weight[event] = 0 + sortTim(assoc_spawn_weight, cmp=/proc/cmp_numeric_dsc, associative = TRUE) + for(var/datum/round_event_control/event as anything in assoc_spawn_weight) + even = !even + var/background_cl = even ? "#17191C" : "#23273C" + dat += "" + dat += "" //Name + dat += "" + var/occurence_string = "[event.occurrences]" + if(event.shared_occurence_type) + occurence_string += " (shared: [event.get_occurences()])" + dat += "" //Occurences + dat += "" //Minimum pop + dat += "" //Minimum time + dat += "" //Can happen? + var/weight_string = "([event.calculated_weight] /raw.[event.weight])" + if(assoc_spawn_weight[event]) + var/percent = round((event.calculated_weight / total_weight) * 100) + weight_string = "[percent]% - [weight_string]" + dat += "" //Weight + dat += "" //Actions + dat += "" + dat += "
NameTagsOccurencesM.PopM.TimeCan OccurWeightActions
[event.name]" //Tags + for(var/tag in event.tags) + dat += "[tag] " + dat += "[occurence_string][event.min_players][event.earliest_start / (1 MINUTES)] m.[assoc_spawn_weight[event] ? "Yes" : "No"][weight_string][event.get_href_actions()]
" + var/datum/browser/popup = new(user, "gamemode_event_panel", "Event Panel", 1000, 600) + popup.set_content(dat.Join()) + popup.open() + +/datum/controller/subsystem/gamemode/Topic(href, href_list) + . = ..() + var/mob/user = usr + if(!check_rights(R_ADMIN)) + return + switch(href_list["panel"]) + if("main") + switch(href_list["action"]) + if("set_storyteller") + message_admins("[key_name_admin(usr)] is picking a new Storyteller.") + var/list/name_list = list() + for(var/storyteller_type in storytellers) + var/datum/storyteller/storyboy = storytellers[storyteller_type] + name_list[storyboy.name] = storyboy.type + var/new_storyteller_name = input(usr, "Choose new storyteller (circumvents voted one):", "Storyteller") as null|anything in name_list + if(!new_storyteller_name) + message_admins("[key_name_admin(usr)] has cancelled picking a Storyteller.") + return + message_admins("[key_name_admin(usr)] has chosen [new_storyteller_name] as the new Storyteller.") + var/new_storyteller_type = name_list[new_storyteller_name] + set_storyteller(new_storyteller_type) + if("halt_storyteller") + halted_storyteller = !halted_storyteller + message_admins("[key_name_admin(usr)] has [halted_storyteller ? "HALTED" : "un-halted"] the Storyteller.") + if("vars") + var/track = href_list["track"] + switch(href_list["var"]) + if("pts_multiplier") + var/new_value = input(usr, "New value:", "Set new value") as num|null + if(isnull(new_value) || new_value < 0) + return + message_admins("[key_name_admin(usr)] set point gain multiplier for [track] track to [new_value].") + point_gain_multipliers[track] = new_value + if("roundstart_pts") + var/new_value = input(usr, "New value:", "Set new value") as num|null + if(isnull(new_value) || new_value < 0) + return + message_admins("[key_name_admin(usr)] set roundstart pts multiplier for [track] track to [new_value].") + roundstart_point_multipliers[track] = new_value + if("min_pop") + var/new_value = input(usr, "New value:", "Set new value") as num|null + if(isnull(new_value) || new_value < 0) + return + message_admins("[key_name_admin(usr)] set minimum population for [track] track to [new_value].") + min_pop_thresholds[track] = new_value + if("pts_threshold") + var/new_value = input(usr, "New value:", "Set new value") as num|null + if(isnull(new_value) || new_value < 0) + return + message_admins("[key_name_admin(usr)] set point threshold of [track] track to [new_value].") + point_thresholds[track] = new_value + if("reload_config_vars") + message_admins("[key_name_admin(usr)] reloaded gamemode config vars.") + load_config_vars() + if("tab") + var/tab = href_list["tab"] + panel_page = tab + if("open_stats") + event_panel(user) + return + if("track_action") + var/track = href_list["track"] + if(!(track in event_tracks)) + return + switch(href_list["track_action"]) + if("remove_forced") + if(forced_next_events[track]) + var/datum/round_event_control/event = forced_next_events[track] + message_admins("[key_name_admin(usr)] removed forced event [event.name] from track [track].") + forced_next_events -= track + if("set_pts") + var/set_pts = input(usr, "New point amount ([point_thresholds[track]]+ invokes event):", "Set points for [track]") as num|null + if(isnull(set_pts)) + return + event_track_points[track] = set_pts + message_admins("[key_name_admin(usr)] set points of [track] track to [set_pts].") + log_admin_private("[key_name(usr)] set points of [track] track to [set_pts].") + if("next_event") + message_admins("[key_name_admin(usr)] invoked next event for [track] track.") + log_admin_private("[key_name(usr)] invoked next event for [track] track.") + event_track_points[track] = point_thresholds[track] + if(storyteller) + storyteller.handle_tracks() + admin_panel(user) + if("stats") + switch(href_list["action"]) + if("set_roundstart") + roundstart_event_view = !roundstart_event_view + if("set_cat") + var/new_category = href_list["cat"] + if(new_category in EVENT_PANEL_TRACKS) + statistics_track_page = new_category + event_panel(user) diff --git a/monkestation/code/modules/storytellers/readme.md b/monkestation/code/modules/storytellers/readme.md new file mode 100644 index 000000000000..ace62b725afa --- /dev/null +++ b/monkestation/code/modules/storytellers/readme.md @@ -0,0 +1,44 @@ +## Title: + + +MODULE ID: STORYTELLERS + +### Description: + +This PR adds adds on to the current dynamic system by having events be guided by storytellers, this also caches the events ran last round and depending on severity cuts their weights by x % to make rounds not repeat as often. + + + + +### TG Proc/File Changes: + + + - N/A + +### Defines: + + + - code\__DEFINES\~monkestation\storytellers.dm + +### Master file additions + +- code\modules\events\_event.dm +- code\modules\admin\topic.dm +- code\controllers\subsystem\ticker.dm +- code\controllers\subsystem\statpanel.dm +- all event files + + + +### Included files that are not contained in this module: + +- N/A + + +### Credits: + + + +Made by Unknown Coders on Horizon (Horizon's Repo atleast as of 10/14/2023 no longer exists if this changes please let me know on discord #Borbop) + +Ported by Dwasint diff --git a/monkestation/code/modules/storytellers/scheduled_events.dm b/monkestation/code/modules/storytellers/scheduled_events.dm new file mode 100644 index 000000000000..60414cc948eb --- /dev/null +++ b/monkestation/code/modules/storytellers/scheduled_events.dm @@ -0,0 +1,94 @@ +///Scheduled event datum for SSgamemode to put events into. +/datum/scheduled_event + /// What event are scheduling. + var/datum/round_event_control/event + /// When do we start our event + var/start_time = 0 + /// If we were created by a storyteller, here's a cost to refund in case. + var/cost + /// Whether we alerted admins about this schedule when it's close to being invoked. + var/alerted_admins = FALSE + /// Whether we are faking an occurence or not + var/fakes_occurence = TRUE + /// Whether this ignores event can run checks. If bussed by an admin, you want to ignore checks + var/ignores_checks + /// Whether the scheduled event will override the announcement change. If null it won't. TRUE = force yes. FALSE = force no. + var/announce_change + +/datum/scheduled_event/New(datum/round_event_control/passed_event, passed_time, passed_cost, passed_ignore, passed_announce) + . = ..() + event = passed_event + start_time = passed_time + cost = passed_cost + ignores_checks = passed_ignore + announce_change = passed_announce + /// Add a fake occurence to make the weightings/checks properly respect the scheduled event. + event.add_occurence() + fakes_occurence = TRUE + +/datum/scheduled_event/proc/remove_occurence() + if(fakes_occurence) + /// Remove the fake occurence if we still have it + event.subtract_occurence() + fakes_occurence = FALSE + +/// For admins who want to reschedule the event. +/datum/scheduled_event/proc/reschedule(new_time) + start_time = new_time + alerted_admins = FALSE + +/datum/scheduled_event/proc/get_href_actions() + var/round_started = SSticker.HasRoundStarted() + if(round_started) + return "Fire Reschedule Cancel Refund" + else + return "Cancel" + +/// Try and fire off the scheduled event +/datum/scheduled_event/proc/try_fire() + /// Remove our fake occurence pre-emptively for the checks. + remove_occurence() + + ///If we can't spawn the scheduled event, refund it. + if(!ignores_checks && !event.can_spawn_event(FALSE)) //FALSE argument to ignore popchecks, to prevent scheduled events from failing from people dying/cryoing etc. + message_admins("Scheduled Event: [event] was unable to run and has been refunded.") + SSgamemode.refund_scheduled_event(src) + return + + ///Trigger the event and remove the scheduled datum + message_admins("Scheduled Event: [event] successfully triggered.") + SSgamemode.TriggerEvent(event) + SSgamemode.remove_scheduled_event(src) + +/datum/scheduled_event/Destroy() + remove_occurence() + event = null + return ..() + +/datum/scheduled_event/Topic(href, href_list) + . = ..() + if(QDELETED(src)) + return + var/round_started = SSticker.HasRoundStarted() + switch(href_list["action"]) + if("cancel") + message_admins("[key_name_admin(usr)] cancelled scheduled event [event.name].") + log_admin_private("[key_name(usr)] cancelled scheduled event [event.name].") + SSgamemode.remove_scheduled_event(src) + if("refund") + message_admins("[key_name_admin(usr)] refunded scheduled event [event.name].") + log_admin_private("[key_name(usr)] refunded scheduled event [event.name].") + SSgamemode.refund_scheduled_event(src) + if("reschedule") + var/new_schedule = input(usr, "New schedule time (in seconds):", "Reschedule Event") as num|null + if(isnull(new_schedule) || QDELETED(src)) + return + start_time = world.time + new_schedule * 1 SECONDS + message_admins("[key_name_admin(usr)] rescheduled event [event.name] to [new_schedule] seconds.") + log_admin_private("[key_name(usr)] rescheduled event [event.name] to [new_schedule] seconds.") + if("fire") + if(!round_started) + return + message_admins("[key_name_admin(usr)] has fired scheduled event [event.name].") + log_admin_private("[key_name(usr)] has fired scheduled event [event.name].") + try_fire() diff --git a/monkestation/code/modules/storytellers/storytellers/_storyteller.dm b/monkestation/code/modules/storytellers/storytellers/_storyteller.dm new file mode 100644 index 000000000000..9910b47a1272 --- /dev/null +++ b/monkestation/code/modules/storytellers/storytellers/_storyteller.dm @@ -0,0 +1,145 @@ + +///The storyteller datum. He operates with the SSgamemode data to run events +/datum/storyteller + /// Name of our storyteller. + var/name = "Badly coded storyteller" + /// Description of our storyteller. + var/desc = "Report this to the coders." + /// Text that the players will be greeted with when this storyteller is chosen. + var/welcome_text = "Set your eyes on the horizon." + /// This is the multiplier for repetition penalty in event weight. The lower the harsher it is + var/event_repetition_multiplier = 0.6 + /// Multipliers for starting points. + var/list/starting_point_multipliers = list( + EVENT_TRACK_MUNDANE = 1, + EVENT_TRACK_MODERATE = 1, + EVENT_TRACK_MAJOR = 1, + EVENT_TRACK_ROLESET = 1, + EVENT_TRACK_OBJECTIVES = 1 + ) + /// Multipliers for point gains. + var/list/point_gains_multipliers = list( + EVENT_TRACK_MUNDANE = 1, + EVENT_TRACK_MODERATE = 1, + EVENT_TRACK_MAJOR = 1, + EVENT_TRACK_ROLESET = 1, + EVENT_TRACK_OBJECTIVES = 1 + ) + /// Multipliers of weight to apply for each tag of an event. + var/list/tag_multipliers + + /// Variance in cost of the purchased events. Effectively affects frequency of events + var/cost_variance = 15 + + /// Variance in the budget of roundstart points. + var/roundstart_points_variance = 15 + + /// Whether the storyteller guaranteed a roleset roll (antag) on roundstart. (Still needs to pass pop check) + var/guarantees_roundstart_roleset = TRUE + + /// Whether the storyteller has the distributions disabled. Important for ghost storytellers + var/disable_distribution = FALSE + + /// Whether people can vote for the storyteller + var/votable = TRUE + /// If defined, will need a minimum of population to be votable + var/population_min + /// If defined, it will not be votable if exceeding the population + var/population_max + +/datum/storyteller/process(delta_time) + if(disable_distribution) + return + add_points(delta_time) + handle_tracks() + +/// Add points to all tracks while respecting the multipliers. +/datum/storyteller/proc/add_points(delta_time) + var/datum/controller/subsystem/gamemode/mode = SSgamemode + var/base_point = EVENT_POINT_GAINED_PER_SECOND * delta_time * mode.event_frequency_multiplier + for(var/track in mode.event_track_points) + var/point_gain = base_point * point_gains_multipliers[track] * mode.point_gain_multipliers[track] + if(mode.allow_pop_scaling) + point_gain *= mode.current_pop_scale_multipliers[track] + mode.event_track_points[track] += point_gain + mode.last_point_gains[track] = point_gain + +/// Goes through every track of the gamemode and checks if it passes a threshold to buy an event, if does, buys one. +/datum/storyteller/proc/handle_tracks() + . = FALSE //Has return value for the roundstart loop + var/datum/controller/subsystem/gamemode/mode = SSgamemode + for(var/track in mode.event_track_points) + var/points = mode.event_track_points[track] + if(points >= mode.point_thresholds[track] && find_and_buy_event_from_track(track)) + . = TRUE + +/// Find and buy a valid event from a track. +/datum/storyteller/proc/find_and_buy_event_from_track(track) + . = FALSE + var/datum/controller/subsystem/gamemode/mode = SSgamemode + var/datum/round_event_control/picked_event + if(mode.forced_next_events[track]) //Forced event by admin + /// Dont check any prerequisites, it has been forced by an admin + picked_event = mode.forced_next_events[track] + mode.forced_next_events -= track + else + mode.update_crew_infos() + var/pop_required = mode.min_pop_thresholds[track] + if(mode.active_players < pop_required) + message_admins("Storyteller failed to pick an event for track of [track] due to insufficient population. (required: [pop_required] active pop for [track]. Current: [mode.active_players])") + mode.event_track_points[track] *= TRACK_FAIL_POINT_PENALTY_MULTIPLIER + return + calculate_weights(track) + var/list/valid_events = list() + // Determine which events are valid to pick + for(var/datum/round_event_control/event as anything in mode.event_pools[track]) + if(event.can_spawn_event()) + valid_events[event] = event.calculated_weight + ///If we didn't get any events, remove the points inform admins and dont do anything + if(!length(valid_events)) + message_admins("Storyteller failed to pick an event for track of [track].") + mode.event_track_points[track] *= TRACK_FAIL_POINT_PENALTY_MULTIPLIER + return + picked_event = pick_weight(valid_events) + if(!picked_event) + message_admins("WARNING: Storyteller picked a null from event pool. Aborting event roll.") + stack_trace("WARNING: Storyteller picked a null from event pool.") + return + buy_event(picked_event, track) + . = TRUE + +/// Find and buy a valid event from a track. +/datum/storyteller/proc/buy_event(datum/round_event_control/bought_event, track) + var/datum/controller/subsystem/gamemode/mode = SSgamemode + // Perhaps use some bell curve instead of a flat variance? + var/total_cost = bought_event.cost * mode.point_thresholds[track] + if(!bought_event.roundstart) + total_cost *= (1 + (rand(-cost_variance, cost_variance)/100)) //Apply cost variance if not roundstart event + mode.event_track_points[track] -= total_cost + message_admins("Storyteller purchased and triggered [bought_event] event, on [track] track, for [total_cost] cost.") + if(bought_event.roundstart) + mode.TriggerEvent(bought_event) + else + mode.schedule_event(bought_event, (rand(3, 4) MINUTES), total_cost) + +/// Calculates the weights of the events from a passed track. +/datum/storyteller/proc/calculate_weights(track) + var/datum/controller/subsystem/gamemode/mode = SSgamemode + for(var/datum/round_event_control/event as anything in mode.event_pools[track]) + var/weight_total = event.weight + /// Apply tag multipliers if able + if(tag_multipliers) + for(var/tag in tag_multipliers) + if(tag in event.tags) + weight_total *= tag_multipliers[tag] + /// Apply occurence multipliers if able + var/occurences = event.get_occurences() + if(occurences) + ///If the event has occured already, apply a penalty multiplier based on amount of occurences + weight_total -= event.reoccurence_penalty_multiplier * weight_total * (1 - (event_repetition_multiplier ** occurences)) + /// Write it + event.calculated_weight = weight_total + +/datum/storyteller/guide + name = "The Guide" + desc = "The Guide will provide a balanced and varied experience. Consider this the default experience." diff --git a/monkestation/code/modules/storytellers/storytellers/ghost.dm b/monkestation/code/modules/storytellers/storytellers/ghost.dm new file mode 100644 index 000000000000..b5f697567ae7 --- /dev/null +++ b/monkestation/code/modules/storytellers/storytellers/ghost.dm @@ -0,0 +1,6 @@ + +/datum/storyteller/ghost + name = "The Ghost" + desc = "The Ghost will not run a single event or create an antagonist." + disable_distribution = TRUE + population_max = 10 diff --git a/monkestation/code/modules/storytellers/storytellers/jester.dm b/monkestation/code/modules/storytellers/storytellers/jester.dm new file mode 100644 index 000000000000..83de9ea12245 --- /dev/null +++ b/monkestation/code/modules/storytellers/storytellers/jester.dm @@ -0,0 +1,13 @@ + +/datum/storyteller/jester + name = "The Jester" + desc = "The Jester will create much more events, with higher possibilities of them repeating." + event_repetition_multiplier = 0.8 + point_gains_multipliers = list( + EVENT_TRACK_MUNDANE = 1.2, + EVENT_TRACK_MODERATE = 1.4, + EVENT_TRACK_MAJOR = 1.4, + EVENT_TRACK_ROLESET = 1, + EVENT_TRACK_OBJECTIVES = 1 + ) + population_min = 10 diff --git a/monkestation/code/modules/storytellers/storytellers/sleeper.dm b/monkestation/code/modules/storytellers/storytellers/sleeper.dm new file mode 100644 index 000000000000..5455566b544b --- /dev/null +++ b/monkestation/code/modules/storytellers/storytellers/sleeper.dm @@ -0,0 +1,13 @@ + +/datum/storyteller/sleeper + name = "The Sleeper" + desc = "The Sleeper will create less impactful events, especially ones involving combat or destruction. The chill experience." + point_gains_multipliers = list( + EVENT_TRACK_MUNDANE = 1, + EVENT_TRACK_MODERATE = 0.7, + EVENT_TRACK_MAJOR = 0.7, + EVENT_TRACK_ROLESET = 0.7, + EVENT_TRACK_OBJECTIVES = 1 + ) + guarantees_roundstart_roleset = FALSE + tag_multipliers = list(TAG_COMBAT = 0.6, TAG_DESTRUCTIVE = 0.7) diff --git a/monkestation/code/modules/storytellers/storytellers/vote.dm b/monkestation/code/modules/storytellers/storytellers/vote.dm new file mode 100644 index 000000000000..6bf8ebc10bc3 --- /dev/null +++ b/monkestation/code/modules/storytellers/storytellers/vote.dm @@ -0,0 +1,4 @@ +/datum/vote/storyteller +/datum/vote/storyteller/can_be_initiated(forced) + choices = SSgamemode.storyteller_vote_choices() + . = ..() diff --git a/monkestation/code/modules/storytellers/storytellers/warrior.dm b/monkestation/code/modules/storytellers/storytellers/warrior.dm new file mode 100644 index 000000000000..d1eb510aac66 --- /dev/null +++ b/monkestation/code/modules/storytellers/storytellers/warrior.dm @@ -0,0 +1,12 @@ +/datum/storyteller/warrior + name = "The Warrior" + desc = "The Warrior will create more impactful events, often focused on combat." + point_gains_multipliers = list( + EVENT_TRACK_MUNDANE = 1, + EVENT_TRACK_MODERATE = 1.3, + EVENT_TRACK_MAJOR = 1.3, + EVENT_TRACK_ROLESET = 1, + EVENT_TRACK_OBJECTIVES = 1 + ) + tag_multipliers = list(TAG_COMBAT = 1.5) + population_min = 10 diff --git a/tgstation.dme b/tgstation.dme index 43a71fe4e469..6d1c436cfc1b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -390,6 +390,7 @@ #include "code\__DEFINES\~monkestation\smoothing.dm" #include "code\__DEFINES\~monkestation\span.dm" #include "code\__DEFINES\~monkestation\status_effects.dm" +#include "code\__DEFINES\~monkestation\storytellers.dm" #include "code\__DEFINES\~monkestation\traits.dm" #include "code\__DEFINES\~monkestation\dcs\signals\signals_atom.dm" #include "code\__DEFINES\~monkestation\dcs\signals\signals_carbon.dm" @@ -6266,6 +6267,14 @@ #include "monkestation\code\modules\store\store_items\suits.dm" #include "monkestation\code\modules\store\store_items\toys.dm" #include "monkestation\code\modules\store\store_items\under.dm" +#include "monkestation\code\modules\storytellers\config.dm" +#include "monkestation\code\modules\storytellers\gamemode_subsystem.dm" +#include "monkestation\code\modules\storytellers\scheduled_events.dm" +#include "monkestation\code\modules\storytellers\storytellers\_storyteller.dm" +#include "monkestation\code\modules\storytellers\storytellers\ghost.dm" +#include "monkestation\code\modules\storytellers\storytellers\jester.dm" +#include "monkestation\code\modules\storytellers\storytellers\sleeper.dm" +#include "monkestation\code\modules\storytellers\storytellers\vote.dm" #include "monkestation\code\modules\surgery\bodyparts\arachnid_bodyparts.dm" #include "monkestation\code\modules\surgery\bodyparts\clockwork_bodyparts.dm" #include "monkestation\code\modules\surgery\bodyparts\ipc_bodyparts.dm" From 20d1da35be204479aa6080d0a47b238d2ac21aa3 Mon Sep 17 00:00:00 2001 From: dwasint <82520990+dwasint@users.noreply.github.com> Date: Sat, 14 Oct 2023 20:54:15 -0400 Subject: [PATCH 02/85] Update gamemode_subsystem.dm --- monkestation/code/modules/storytellers/gamemode_subsystem.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkestation/code/modules/storytellers/gamemode_subsystem.dm b/monkestation/code/modules/storytellers/gamemode_subsystem.dm index d25ba06c5c43..6b69e3f0e1a9 100644 --- a/monkestation/code/modules/storytellers/gamemode_subsystem.dm +++ b/monkestation/code/modules/storytellers/gamemode_subsystem.dm @@ -409,7 +409,7 @@ SUBSYSTEM_DEF(gamemode) if(. == EVENT_CANT_RUN)//we couldn't run this event for some reason, set its max_occurrences to 0 event.max_occurrences = 0 else if(. == EVENT_READY) - event.run_event(random = TRUE) // fallback to dynamic + event.runEvent(random = TRUE) // fallback to dynamic ///Resets frequency multiplier. /datum/controller/subsystem/gamemode/proc/resetFrequency() From b8e5496ebe9c758eb8efbf66db00454115e06d3e Mon Sep 17 00:00:00 2001 From: dwasint <82520990+dwasint@users.noreply.github.com> Date: Sat, 14 Oct 2023 21:12:33 -0400 Subject: [PATCH 03/85] start of it all --- .../code/modules/antagonists/florida_man/florida_events.dm | 2 ++ monkestation/code/modules/new_antagonists/slasher/ghost_role.dm | 2 ++ 2 files changed, 4 insertions(+) diff --git a/monkestation/code/modules/antagonists/florida_man/florida_events.dm b/monkestation/code/modules/antagonists/florida_man/florida_events.dm index aa63c44d3cda..e54a5044a74f 100644 --- a/monkestation/code/modules/antagonists/florida_man/florida_events.dm +++ b/monkestation/code/modules/antagonists/florida_man/florida_events.dm @@ -3,6 +3,8 @@ typepath = /datum/round_event/ghost_role/florida_man weight = 14 max_occurrences = 3 + track = EVENT_TRACK_MUNDANE + tags = list(TAG_COMMUNAL, TAG_COMBAT) /datum/round_event/ghost_role/florida_man minimum_required = 1 diff --git a/monkestation/code/modules/new_antagonists/slasher/ghost_role.dm b/monkestation/code/modules/new_antagonists/slasher/ghost_role.dm index 839af842845a..8914aa86b0da 100644 --- a/monkestation/code/modules/new_antagonists/slasher/ghost_role.dm +++ b/monkestation/code/modules/new_antagonists/slasher/ghost_role.dm @@ -3,6 +3,8 @@ typepath = /datum/round_event/ghost_role/slasher weight = 14 // for now max_occurrences = 3 + track = EVENT_TRACK_MODERATE + tags = list(TAG_SPOOKY, TAG_COMBAT) /datum/round_event/ghost_role/slasher minimum_required = 1 From 98e1a4b1fbbb53491cffcc25f1e70a12781ab9d8 Mon Sep 17 00:00:00 2001 From: dwasint <82520990+dwasint@users.noreply.github.com> Date: Sun, 15 Oct 2023 01:09:21 -0400 Subject: [PATCH 04/85] tons of stuff --- code/controllers/master.dm | 2 + code/controllers/subsystem/vote.dm | 4 + code/game/gamemodes/dynamic/dynamic.dm | 2 +- code/modules/events/_event.dm | 6 +- .../converted_events/_base_event.dm | 99 +++++++++++++++++++ .../converted_events/solo/changeling.dm | 29 ++++++ .../converted_events/solo/heretic.dm | 31 ++++++ .../converted_events/solo/traitor.dm | 28 ++++++ .../converted_events/solo/wizard.dm | 27 +++++ .../storytellers/gamemode_subsystem.dm | 27 +++-- .../storytellers/storytellers/jester.dm | 2 +- .../modules/storytellers/storytellers/vote.dm | 30 +++++- .../storytellers/storytellers/warrior.dm | 2 +- tgstation.dme | 4 + tgui/packages/tgui/interfaces/VotePanel.tsx | 3 + 15 files changed, 274 insertions(+), 22 deletions(-) create mode 100644 monkestation/code/modules/storytellers/converted_events/_base_event.dm create mode 100644 monkestation/code/modules/storytellers/converted_events/solo/changeling.dm create mode 100644 monkestation/code/modules/storytellers/converted_events/solo/heretic.dm create mode 100644 monkestation/code/modules/storytellers/converted_events/solo/traitor.dm create mode 100644 monkestation/code/modules/storytellers/converted_events/solo/wizard.dm diff --git a/code/controllers/master.dm b/code/controllers/master.dm index a7013f058ed6..f44a5f5161e9 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -282,6 +282,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(sleep_offline_after_initializations && CONFIG_GET(flag/resume_after_initializations)) world.sleep_offline = FALSE initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10 + /// run votes + SSvote.initiate_vote(/datum/vote/storyteller, "pick round storyteller", forced = TRUE) // idk where else to run this lol /** * Initialize a given subsystem and handle the results. diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 0513c666b774..787748405aaa 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -285,12 +285,16 @@ SUBSYSTEM_DEF(vote) "message" = vote.message, ) + if(vote.has_desc) + vote_data += list("desc" = vote.return_desc(vote_name)) + if(vote == current_vote) var/list/choices = list() for(var/key in current_vote.choices) choices += list(list( "name" = key, "votes" = current_vote.choices[key], + "desc" = current_vote.return_desc(key) )) data["currentVote"] = list( diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index c8c1b2d45064..022de70d54a6 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -135,7 +135,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) var/waittime_h = 1800 /// Maximum amount of threat allowed to generate. - var/max_threat_level = 50 + var/max_threat_level = 0 //disables dynamic threat PLEASE DONT LET ME MERGE THIS /// The extra chance multiplier that a heavy impact midround ruleset will run next time. /// For example, if this is set to 50, then the next heavy roll will be about 50% more likely to happen. diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index ca86be587ba7..a6d2092990d7 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -43,8 +43,8 @@ var/map_flags = NONE //monkestation vars starts - var/roundstart = FALSE // BUBBER EDIT - var/cost = 1 // BUBBER EDIT + var/roundstart = FALSE + var/cost = 1 var/reoccurence_penalty_multiplier = 0.75 var/shared_occurence_type var/track = EVENT_TRACK_MODERATE @@ -88,6 +88,8 @@ // Admin-created events override this. /datum/round_event_control/proc/can_spawn_event(players_amt, allow_magic = FALSE) SHOULD_CALL_PARENT(TRUE) + if(roundstart && world.time-SSticker.round_start_time >= 2 MINUTES) + return FALSE if(occurrences >= max_occurrences) return FALSE if(earliest_start >= world.time-SSticker.round_start_time) diff --git a/monkestation/code/modules/storytellers/converted_events/_base_event.dm b/monkestation/code/modules/storytellers/converted_events/_base_event.dm new file mode 100644 index 000000000000..0f2c2f457862 --- /dev/null +++ b/monkestation/code/modules/storytellers/converted_events/_base_event.dm @@ -0,0 +1,99 @@ +/datum/round_event_control/antagonist + reoccurence_penalty_multiplier = 0 + track = EVENT_TRACK_ROLESET + /// Protected roles from the antag roll. People will not get those roles if a config is enabled + var/list/protected_roles + /// Restricted roles from the antag roll + var/list/restricted_roles + +/datum/round_event_control/antagonist/New() + . = ..() + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_roles |= protected_roles + +/datum/round_event_control/antagonist/can_spawn_event(popchecks = TRUE, allow_magic) + . = ..() + if(!.) + return + if(!roundstart && !SSgamemode.can_inject_antags()) + return FALSE + +/datum/round_event_control/antagonist/solo + typepath = /datum/round_event/antagonist/solo + /// How many baseline antags do we spawn + var/base_antags = 1 + /// How many maximum antags can we spawn + var/maximum_antags = 3 + /// For this many players we'll add 1 up to the maximum antag amount + var/denominator = 20 + /// The antag flag to be used + var/antag_flag + /// The antag datum to be applied + var/antag_datum + /// Prompt players for consent to turn them into antags before doing so. Dont allow this for roundstart. + var/prompted_picking = FALSE + +/datum/round_event_control/antagonist/solo/can_spawn_event(popchecks = TRUE, allow_magic) + . = ..() + if(!.) + return + var/antag_amt = get_antag_amount() + var/list/candidates = get_candidates() + if(candidates.len < antag_amt) + return FALSE + +/datum/round_event_control/antagonist/solo/proc/get_antag_amount() + var/people = SSgamemode.get_correct_popcount() + var/amount = base_antags + FLOOR(people / denominator, 1) + return min(amount, maximum_antags) + +/datum/round_event_control/antagonist/solo/proc/get_candidates() + var/round_started = SSticker.HasRoundStarted() + var/new_players_arg = round_started ? FALSE : TRUE + var/living_players_arg = round_started ? TRUE : FALSE + var/midround_antag_pref_arg = round_started ? FALSE : TRUE + + var/list/candidates = SSgamemode.get_candidates(antag_flag, antag_flag, ready_newplayers = new_players_arg, living_players = living_players_arg, midround_antag_pref = midround_antag_pref_arg, restricted_roles = restricted_roles) + return candidates + +/datum/round_event/antagonist + fakeable = FALSE + end_when = 60 //This is so prompted picking events have time to run //TODO: refactor events so they can be the masters of themselves, instead of relying on some weirdly timed vars + +/datum/round_event/antagonist/solo + // ALL of those variables are internal. Check the control event to change them + /// The antag flag passed from control + var/antag_flag + /// The antag datum passed from control + var/antag_datum + /// The antag count passed from control + var/antag_count + /// The restricted roles (jobs) passed from control + var/list/restricted_roles + /// The minds we've setup in setup() and need to finalize in start() + var/list/setup_minds = list() + /// Whether we prompt the players before picking them. + var/prompted_picking = FALSE //TODO: Implement this + +/datum/round_event/antagonist/solo/setup() + var/datum/round_event_control/antagonist/solo/cast_control = control + antag_count = cast_control.get_antag_amount() + antag_flag = cast_control.antag_flag + antag_datum = cast_control.antag_datum + restricted_roles = cast_control.restricted_roles + prompted_picking = cast_control.prompted_picking + var/list/candidates = cast_control.get_candidates() + for(var/i in 1 to antag_count) + if(!candidates.len) + break + var/mob/candidate = pick_n_take(candidates) + setup_minds += candidate.mind + candidate.mind.special_role = antag_flag + candidate.mind.restricted_roles = restricted_roles + +/datum/round_event/antagonist/solo/start() + for(var/datum/mind/antag_mind as anything in setup_minds) + add_datum_to_mind(antag_mind) + +/datum/round_event/antagonist/solo/proc/add_datum_to_mind(datum/mind/antag_mind) + antag_mind.add_antag_datum(antag_datum) diff --git a/monkestation/code/modules/storytellers/converted_events/solo/changeling.dm b/monkestation/code/modules/storytellers/converted_events/solo/changeling.dm new file mode 100644 index 000000000000..a34fe92bb3ce --- /dev/null +++ b/monkestation/code/modules/storytellers/converted_events/solo/changeling.dm @@ -0,0 +1,29 @@ +/datum/round_event_control/antagonist/solo/changeling + antag_flag = ROLE_CHANGELING + antag_datum = /datum/antagonist/changeling + protected_roles = list( + JOB_CAPTAIN, + JOB_HEAD_OF_PERSONNEL, + JOB_CHIEF_ENGINEER, + JOB_CHIEF_MEDICAL_OFFICER, + JOB_RESEARCH_DIRECTOR, + JOB_DETECTIVE, + JOB_HEAD_OF_SECURITY, + JOB_PRISONER, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + restricted_roles = list( + JOB_AI, + JOB_CYBORG, + ) + min_players = 20 + +/datum/round_event_control/antagonist/solo/changeling/roundstart + name = "Changelings" + roundstart = TRUE + earliest_start = 0 + +/datum/round_event_control/antagonist/solo/changeling/midround + name = "Genome Awakening (Changelings)" + prompted_picking = TRUE diff --git a/monkestation/code/modules/storytellers/converted_events/solo/heretic.dm b/monkestation/code/modules/storytellers/converted_events/solo/heretic.dm new file mode 100644 index 000000000000..070845820619 --- /dev/null +++ b/monkestation/code/modules/storytellers/converted_events/solo/heretic.dm @@ -0,0 +1,31 @@ +/datum/round_event_control/antagonist/solo/heretic + antag_flag = ROLE_HERETIC + antag_datum = /datum/antagonist/heretic + protected_roles = list( + JOB_CAPTAIN, + JOB_HEAD_OF_PERSONNEL, + JOB_CHIEF_ENGINEER, + JOB_CHIEF_MEDICAL_OFFICER, + JOB_RESEARCH_DIRECTOR, + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_PRISONER, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + restricted_roles = list( + JOB_AI, + JOB_CYBORG, + ) + weight = 4 + min_players = 20 + +/datum/round_event_control/antagonist/solo/heretic/roundstart + name = "Heretics" + roundstart = TRUE + earliest_start = 0 + +/datum/round_event_control/antagonist/solo/heretic/midround + name = "Midround Heretics" + prompted_picking = TRUE diff --git a/monkestation/code/modules/storytellers/converted_events/solo/traitor.dm b/monkestation/code/modules/storytellers/converted_events/solo/traitor.dm new file mode 100644 index 000000000000..f48a4ff726a1 --- /dev/null +++ b/monkestation/code/modules/storytellers/converted_events/solo/traitor.dm @@ -0,0 +1,28 @@ +/datum/round_event_control/antagonist/solo/traitor + antag_flag = ROLE_TRAITOR + antag_datum = /datum/antagonist/traitor + protected_roles = list( + JOB_CAPTAIN, + JOB_HEAD_OF_PERSONNEL, + JOB_CHIEF_ENGINEER, + JOB_CHIEF_MEDICAL_OFFICER, + JOB_RESEARCH_DIRECTOR, + JOB_DETECTIVE, + JOB_HEAD_OF_SECURITY, + JOB_PRISONER, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + restricted_roles = list( + JOB_AI, + JOB_CYBORG, + ) + +/datum/round_event_control/antagonist/solo/traitor/roundstart + name = "Traitors" + roundstart = TRUE + earliest_start = 0 SECONDS + +/datum/round_event_control/antagonist/solo/traitor/midround + name = "Sleeper Agents (Traitors)" + prompted_picking = TRUE diff --git a/monkestation/code/modules/storytellers/converted_events/solo/wizard.dm b/monkestation/code/modules/storytellers/converted_events/solo/wizard.dm new file mode 100644 index 000000000000..081dbf45f4f3 --- /dev/null +++ b/monkestation/code/modules/storytellers/converted_events/solo/wizard.dm @@ -0,0 +1,27 @@ +/datum/round_event_control/antagonist/solo/wizard + name = "Wizard" + typepath = /datum/round_event/antagonist/solo/wizard + antag_flag = ROLE_WIZARD + antag_datum = /datum/antagonist/wizard + restricted_roles = list( + JOB_CAPTAIN, + JOB_HEAD_OF_SECURITY, + ) // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted + maximum_antags = 1 + roundstart = TRUE + weight = 2 + min_players = 35 + max_occurrences = 1 + +/datum/round_event_control/antagonist/solo/wizard/can_spawn_event(popchecks = TRUE, allow_magic) + . = ..() + if(!.) + return + if(GLOB.wizardstart.len == 0) + return FALSE + +/datum/round_event/antagonist/solo/wizard + +/datum/round_event/antagonist/solo/wizard/add_datum_to_mind(datum/mind/antag_mind) + . = ..() + antag_mind.current.forceMove(pick(GLOB.wizardstart)) diff --git a/monkestation/code/modules/storytellers/gamemode_subsystem.dm b/monkestation/code/modules/storytellers/gamemode_subsystem.dm index 6b69e3f0e1a9..66b617ec63c8 100644 --- a/monkestation/code/modules/storytellers/gamemode_subsystem.dm +++ b/monkestation/code/modules/storytellers/gamemode_subsystem.dm @@ -165,6 +165,7 @@ SUBSYSTEM_DEF(gamemode) uncategorized += event continue event_pools[event.track] += event //Add it to the categorized event pools + // return ..() @@ -244,8 +245,8 @@ SUBSYSTEM_DEF(gamemode) if(time_to_check && candidate.client.get_remaining_days(time_to_check) > 0) continue - if(midround_antag_pref) - continue + //if(midround_antag_pref) + //continue if(job_ban && is_banned_from(candidate.ckey, list(job_ban, ROLE_SYNDICATE))) continue @@ -738,19 +739,21 @@ SUBSYSTEM_DEF(gamemode) if((storyboy.population_min && storyboy.population_min > client_amount) || (storyboy.population_max && storyboy.population_max < client_amount)) continue choices += storyboy.name + choices[storyboy.name] = 0 ///Because the vote subsystem is dumb and does not support any descriptions, we dump them into world. to_chat(world, span_notice("[storyboy.name]")) to_chat(world, span_notice("[storyboy.desc]")) return choices +/datum/controller/subsystem/gamemode/proc/storyteller_desc(storyteller_name) + for(var/storyteller_type in storytellers) + var/datum/storyteller/storyboy = storytellers[storyteller_type] + if(storyboy.name != storyteller_name) + continue + return storyboy.desc + + /datum/controller/subsystem/gamemode/proc/storyteller_vote_result(winner_name) - /// Find the winner - /// Hijacking the proc because we don't have a vote right now.. - var/datum/storyteller/storyteller = pick(storytellers) - message_admins("We picked [storyteller]") - voted_storyteller = storyteller - if(storyteller) - return for(var/storyteller_type in storytellers) var/datum/storyteller/storyboy = storytellers[storyteller_type] if(storyboy.name == winner_name) @@ -758,12 +761,6 @@ SUBSYSTEM_DEF(gamemode) break /datum/controller/subsystem/gamemode/proc/init_storyteller() - /// Hijacking the proc because we don't have a vote right now.. - var/datum/storyteller/storyteller_pick = pick(storytellers) - message_admins("We picked [storyteller_pick] for this rounds storyteller, randomly.") - voted_storyteller = storyteller_pick - if(storyteller) // If this is true, then an admin bussed one, don't overwrite it - return set_storyteller(voted_storyteller) /datum/controller/subsystem/gamemode/proc/set_storyteller(passed_type) diff --git a/monkestation/code/modules/storytellers/storytellers/jester.dm b/monkestation/code/modules/storytellers/storytellers/jester.dm index 83de9ea12245..a85dd5329110 100644 --- a/monkestation/code/modules/storytellers/storytellers/jester.dm +++ b/monkestation/code/modules/storytellers/storytellers/jester.dm @@ -10,4 +10,4 @@ EVENT_TRACK_ROLESET = 1, EVENT_TRACK_OBJECTIVES = 1 ) - population_min = 10 + //population_min = 10 diff --git a/monkestation/code/modules/storytellers/storytellers/vote.dm b/monkestation/code/modules/storytellers/storytellers/vote.dm index 6bf8ebc10bc3..705b9a5f733b 100644 --- a/monkestation/code/modules/storytellers/storytellers/vote.dm +++ b/monkestation/code/modules/storytellers/storytellers/vote.dm @@ -1,4 +1,30 @@ +/datum/vote/var/has_desc = FALSE + +/datum/vote/proc/return_desc(vote_name) + return "" + /datum/vote/storyteller -/datum/vote/storyteller/can_be_initiated(forced) - choices = SSgamemode.storyteller_vote_choices() + name = "Storyteller" + message = "Vote for the storyteller!" + has_desc = TRUE + + +/datum/vote/storyteller/New() . = ..() + default_choices = list() + default_choices = SSgamemode.storyteller_vote_choices() + + +/datum/vote/storyteller/return_desc(vote_name) + return SSgamemode.storyteller_desc(vote_name) + +/datum/vote/storyteller/create_vote() + . = ..() + if((length(choices) == 1)) // Only one choice, no need to vote. Let's just auto-rotate it to the only remaining map because it would just happen anyways. + var/de_facto_winner = choices[1] + SSgamemode.storyteller_vote_result(de_facto_winner) + to_chat(world, span_boldannounce("The storyteller vote has been skipped because there is only one storyteller left to vote for. The map has been changed to [de_facto_winner].")) + return FALSE + +/datum/vote/storyteller/finalize_vote(winning_option) + SSgamemode.storyteller_vote_result(winning_option) diff --git a/monkestation/code/modules/storytellers/storytellers/warrior.dm b/monkestation/code/modules/storytellers/storytellers/warrior.dm index d1eb510aac66..e54c94992977 100644 --- a/monkestation/code/modules/storytellers/storytellers/warrior.dm +++ b/monkestation/code/modules/storytellers/storytellers/warrior.dm @@ -9,4 +9,4 @@ EVENT_TRACK_OBJECTIVES = 1 ) tag_multipliers = list(TAG_COMBAT = 1.5) - population_min = 10 + //population_min = 10 diff --git a/tgstation.dme b/tgstation.dme index 6d1c436cfc1b..de1d24cd0818 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6270,6 +6270,10 @@ #include "monkestation\code\modules\storytellers\config.dm" #include "monkestation\code\modules\storytellers\gamemode_subsystem.dm" #include "monkestation\code\modules\storytellers\scheduled_events.dm" +#include "monkestation\code\modules\storytellers\converted_events\_base_event.dm" +#include "monkestation\code\modules\storytellers\converted_events\solo\changeling.dm" +#include "monkestation\code\modules\storytellers\converted_events\solo\traitor.dm" +#include "monkestation\code\modules\storytellers\converted_events\solo\wizard.dm" #include "monkestation\code\modules\storytellers\storytellers\_storyteller.dm" #include "monkestation\code\modules\storytellers\storytellers\ghost.dm" #include "monkestation\code\modules\storytellers\storytellers\jester.dm" diff --git a/tgui/packages/tgui/interfaces/VotePanel.tsx b/tgui/packages/tgui/interfaces/VotePanel.tsx index 0b4dfbe76383..13eacc5d1a9f 100644 --- a/tgui/packages/tgui/interfaces/VotePanel.tsx +++ b/tgui/packages/tgui/interfaces/VotePanel.tsx @@ -19,6 +19,7 @@ type Vote = { type Option = { name: string; votes: number; + desc: string; }; type ActiveVote = { @@ -180,6 +181,7 @@ const ChoicesPanel = (props, context) => { textAlign="right" buttons={