diff --git a/code/__DEFINES/round.dm b/code/__DEFINES/round.dm
new file mode 100644
index 000000000000..1959f63b098f
--- /dev/null
+++ b/code/__DEFINES/round.dm
@@ -0,0 +1,13 @@
+#define ROUND_TRAIT_HUMAN_POSITIVE "human_positive"
+#define ROUND_TRAIT_HUMAN_NEGATIVE "human_negative"
+
+#define ROUND_TRAIT_XENO_POSITIVE "xeno_positive"
+#define ROUND_TRAIT_XENO_NEGATIVE "xeno_negative"
+
+#define ROUND_TRAIT_NEUTRAL "neutral"
+
+
+#define ROUND_TRAIT_ABSTRACT (1<<0)
+
+/// The data file that future station traits are stored in
+#define FUTURE_ROUND_TRAITS_FILE "data/future_round_traits.json"
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 8a65a4b961ff..47d65d1377d5 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -118,6 +118,7 @@
#define SS_INIT_INFLUXDRIVER 28
#define SS_INIT_SUPPLY_SHUTTLE 25
#define SS_INIT_GARBAGE 24
+#define SS_INIT_ROUND 23.6
#define SS_INIT_EVENTS 23.5
#define SS_INIT_JOB 23
#define SS_INIT_REDIS 22.5
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 1aaf3714182e..f7407587b075 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -213,6 +213,16 @@
//This item will force clickdrag to work even if the preference to disable is enabled. (Full-auto items)
#define TRAIT_OVERRIDE_CLICKDRAG "t_override_clickdrag"
+
+// Round Traits
+#define TRAIT_ROUND_CRYOSLEEP_SICKNESS "t_cryosleep_sickness"
+#define TRAIT_ROUND_FLU_SEASON "t_flu_season"
+#define TRAIT_ROUND_FAULTY_PIPING "t_faulty_piping"
+#define TRAIT_ROUND_ECONOMIC_BOOM "t_economic_boom"
+#define TRAIT_ROUND_ECONOMIC_SLUMP "t_economic_slump"
+#define TRAIT_ROUND_STANDARD_ISSUE "t_standard_issue"
+#define TRAIT_ROUND_WRONG_TUBES "t_wrongtubes"
+
//This item will use special rename component behaviors.
//ie. naming a regulation tape "example" will become regulation tape (example)
#define TRAIT_ITEM_RENAME_SPECIAL "t_item_rename_special"
@@ -345,6 +355,8 @@ GLOBAL_LIST(trait_name_map)
#define TRAIT_SOURCE_ABILITY(ability) "t_s_ability_[ability]"
///Status trait forced by the xeno action charge
#define TRAIT_SOURCE_XENO_ACTION_CHARGE "t_s_xeno_action_charge"
+/// status trait given by a round trait
+#define TRAIT_SOURCE_ROUND "t_s_round_trait"
//-- structure traits --
///Status trait coming from being flipped or unflipped.
#define TRAIT_SOURCE_FLIP_TABLE "t_s_flip_table"
diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm
index 36058a44fc37..95db85564dc7 100644
--- a/code/_globalvars/global_lists.dm
+++ b/code/_globalvars/global_lists.dm
@@ -181,6 +181,8 @@ GLOBAL_LIST_INIT_TYPED(hive_datum, /datum/hive_status, list(
XENO_HIVE_RENEGADE = new /datum/hive_status/corrupted/renegade(),
))
+GLOBAL_DATUM_INIT(xeno_round_traits, /datum/xeno_round_traits, new())
+
GLOBAL_LIST_INIT(xeno_evolve_times, setup_xeno_evolve_times())
/proc/setup_xeno_evolve_times()
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index 6c689e995504..974ae3a7dfdd 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -21,9 +21,15 @@ GLOBAL_VAR_INIT(minimum_exterior_lighting_alpha, 255)
GLOBAL_DATUM_INIT(item_to_box_mapping, /datum/item_to_box_mapping, init_item_to_box_mapping())
+
+GLOBAL_VAR(temperature_change)
+
+GLOBAL_VAR(squad_mappings)
+
/// Offset for the Operation time
GLOBAL_VAR_INIT(time_offset, setup_offset())
/// Sets the offset 2 lines above.
/proc/setup_offset()
return rand(10 MINUTES, 24 HOURS)
+
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 976256cb6c97..125ab1effa77 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -551,6 +551,8 @@ This maintains a list of ip addresses that are able to bypass topic filtering.
/// logs all timers in buckets on automatic bucket reset (Useful for timer debugging)
/datum/config_entry/flag/log_timers_on_bucket_reset
+/datum/config_entry/flag/forbid_round_traits
+
/datum/config_entry/number/hard_deletes_overrun_threshold
integer = FALSE
min_val = 0
diff --git a/code/controllers/subsystem/processing/round.dm b/code/controllers/subsystem/processing/round.dm
new file mode 100644
index 000000000000..bfceedaa3548
--- /dev/null
+++ b/code/controllers/subsystem/processing/round.dm
@@ -0,0 +1,102 @@
+PROCESSING_SUBSYSTEM_DEF(round)
+ name = "Round"
+ init_order = SS_INIT_ROUND
+ flags = SS_BACKGROUND
+ runlevels = RUNLEVEL_GAME
+ wait = 5 SECONDS
+
+ ///A list of currently active round traits
+ var/list/round_traits = list()
+ ///Assoc list of trait type || assoc list of traits with weighted value. Used for picking traits from a specific category.
+ var/list/selectable_traits_by_types = list(
+ ROUND_TRAIT_HUMAN_POSITIVE = list(),
+ ROUND_TRAIT_HUMAN_NEGATIVE = list(),
+ ROUND_TRAIT_XENO_POSITIVE = list(),
+ ROUND_TRAIT_XENO_NEGATIVE = list(),
+ ROUND_TRAIT_NEUTRAL = list(),
+ )
+
+/datum/controller/subsystem/processing/round/Initialize()
+
+ //If doing unit tests we don't do none of that trait shit ya know?
+ // Autowiki also wants consistent outputs, for example making sure the vending machine page always reports the normal products
+ #if !defined(UNIT_TESTS) && !defined(AUTOWIKI)
+ SetupTraits()
+ #endif
+
+ return SS_INIT_SUCCESS
+
+///Rolls for the amount of traits and adds them to the traits list
+/datum/controller/subsystem/processing/round/proc/SetupTraits()
+ if (CONFIG_GET(flag/forbid_round_traits))
+ return
+
+ if (fexists(FUTURE_ROUND_TRAITS_FILE))
+ var/forced_traits_contents = file2text(FUTURE_ROUND_TRAITS_FILE)
+ fdel(FUTURE_ROUND_TRAITS_FILE)
+
+ var/list/forced_traits_text_paths = json_decode(forced_traits_contents)
+ forced_traits_text_paths = SANITIZE_LIST(forced_traits_text_paths)
+
+ for (var/trait_text_path in forced_traits_text_paths)
+ var/round_trait_path = text2path(trait_text_path)
+ if (!ispath(round_trait_path, /datum/round_trait) || round_trait_path == /datum/round_trait)
+ var/message = "Invalid round trait path [round_trait_path] was requested in the future round traits!"
+ log_game(message)
+ message_admins(message)
+ continue
+
+ setup_trait(round_trait_path)
+
+ return
+
+ for(var/datum/round_trait/trait_typepath as anything in subtypesof(/datum/round_trait))
+
+ // If forced, (probably debugging), just set it up now, keep it out of the pool.
+ if(initial(trait_typepath.force))
+ setup_trait(trait_typepath)
+ continue
+
+ if(initial(trait_typepath.trait_flags) & ROUND_TRAIT_ABSTRACT)
+ continue //Dont add abstract ones to it
+ selectable_traits_by_types[initial(trait_typepath.trait_type)][trait_typepath] = initial(trait_typepath.weight)
+
+ var/human_positive_trait_count = pick(50;0, 5;1, 1;2)
+ var/human_negative_trait_count = pick(50;0, 5;1, 1;2)
+
+ var/xeno_positive_trait_count = pick(50;0, 5;1, 1;2)
+ var/xeno_negative_trait_count = pick(50;0, 5;1, 1;2)
+
+ var/neutral_trait_count = pick(50;0, 5;1, 1;2)
+
+ pick_traits(ROUND_TRAIT_HUMAN_POSITIVE, human_positive_trait_count)
+ pick_traits(ROUND_TRAIT_HUMAN_NEGATIVE, human_negative_trait_count)
+
+ pick_traits(ROUND_TRAIT_XENO_POSITIVE, xeno_positive_trait_count)
+ pick_traits(ROUND_TRAIT_XENO_NEGATIVE, xeno_negative_trait_count)
+
+ pick_traits(ROUND_TRAIT_NEUTRAL, neutral_trait_count)
+
+///Picks traits of a specific category (e.g. bad or good) and a specified amount, then initializes them, adds them to the list of traits,
+///then removes them from possible traits as to not roll twice.
+/datum/controller/subsystem/processing/round/proc/pick_traits(trait_sign, amount)
+ if(!amount)
+ return
+ for(var/iterator in 1 to amount)
+ var/datum/round_trait/trait_type = pick_weight(selectable_traits_by_types[trait_sign]) //Rolls from the table for the specific trait type
+ selectable_traits_by_types[trait_sign] -= trait_type
+ setup_trait(trait_type)
+
+///Creates a given trait of a specific type, while also removing any blacklisted ones from the future pool.
+/datum/controller/subsystem/processing/round/proc/setup_trait(datum/round_trait/trait_type)
+ if(!trait_type)
+ return
+
+ var/datum/round_trait/trait_instance = new trait_type()
+ round_traits += trait_instance
+ log_game("round trait: [trait_instance.name] chosen for this round.")
+ if(!trait_instance.blacklist)
+ return
+ for(var/i in trait_instance.blacklist)
+ var/datum/round_trait/trait_to_remove = i
+ selectable_traits_by_types[initial(trait_to_remove.trait_type)] -= trait_to_remove
diff --git a/code/datums/diseases/flu.dm b/code/datums/diseases/flu.dm
index f2c029587616..4c168df410db 100644
--- a/code/datums/diseases/flu.dm
+++ b/code/datums/diseases/flu.dm
@@ -64,3 +64,7 @@
affected_mob.apply_damage(1, TOX)
affected_mob.updatehealth()
return
+
+
+/datum/disease/flu/cryo_flu
+ spread_type = NON_CONTAGIOUS
diff --git a/code/datums/round_traits/_round_traits.dm b/code/datums/round_traits/_round_traits.dm
new file mode 100644
index 000000000000..839a47f80ce4
--- /dev/null
+++ b/code/datums/round_traits/_round_traits.dm
@@ -0,0 +1,60 @@
+///Base class of round traits. These are used to influence rounds in one way or the other by influencing the levers of the round.
+
+/datum/round_trait
+ ///Name of the trait
+ var/name = "unnamed round trait"
+ ///The type of this trait. Used to classify how this trait influences the round
+ var/trait_type = ROUND_TRAIT_NEUTRAL
+ ///Whether or not this trait uses process()
+ var/trait_processes = FALSE
+ ///Whether this trait is always enabled; generally used for debugging
+ var/force = FALSE
+ ///Chance relative to other traits of its type to be picked
+ var/weight = 10
+ ///Does this trait show in the human report?
+ var/show_in_human_report = FALSE
+ ///Does this trait show in the xeno report?
+ var/show_in_xeno_report = FALSE
+ ///What message to show in the human report?
+ var/human_report_message
+ ///What message to show in the xeno report?
+ var/xeno_report_message
+ ///What trait does this round trait give? gives none if null
+ var/trait_to_give
+ ///What traits are incompatible with this one?
+ var/blacklist
+ ///Extra flags for round traits such as it being abstract
+ var/trait_flags
+ /// Whether or not this trait can be reverted by an admin
+ var/can_revert = TRUE
+
+
+/datum/round_trait/New()
+ . = ..()
+ SSticker.OnRoundstart(CALLBACK(src, PROC_REF(on_round_start)))
+ if(trait_processes)
+ START_PROCESSING(SSround, src)
+ if(trait_to_give)
+ ADD_TRAIT(SSround, trait_to_give, TRAIT_SOURCE_ROUND)
+
+/datum/round_trait/Destroy()
+ SSround.round_traits -= src
+ return ..()
+
+///Proc ran when round starts. Use this for roundstart effects.
+/datum/round_trait/proc/on_round_start()
+ return
+
+///type of info the human report has on this trait, if any.
+/datum/round_trait/proc/get_report()
+ return "[name] - [human_report_message]"
+
+/// Will attempt to revert the round trait, used by admins.
+/datum/round_trait/proc/revert()
+ if(!can_revert)
+ CRASH("revert() was called on [type], which can't be reverted!")
+
+ if(trait_to_give)
+ REMOVE_TRAIT(SSround, trait_to_give, TRAIT_SOURCE_ROUND)
+
+ qdel(src)
diff --git a/code/datums/round_traits/admin_panel.dm b/code/datums/round_traits/admin_panel.dm
new file mode 100644
index 000000000000..ed667d7e4263
--- /dev/null
+++ b/code/datums/round_traits/admin_panel.dm
@@ -0,0 +1,133 @@
+/// Opens the station traits admin panel
+/datum/admins/proc/round_traits_panel()
+ set name = "Modify Round Traits"
+ set category = "Admin.Events"
+
+ var/static/datum/round_traits_panel/round_traits_panel = new
+ round_traits_panel.tgui_interact(usr)
+
+/datum/round_traits_panel
+ var/static/list/future_traits
+
+/datum/round_traits_panel/ui_data(mob/user)
+ var/list/data = list()
+
+ data["too_late_to_revert"] = too_late_to_revert()
+
+ var/list/current_round_traits = list()
+ for (var/datum/round_trait/round_trait as anything in SSround.round_traits)
+ current_round_traits += list(list(
+ "name" = round_trait.name,
+ "can_revert" = round_trait.can_revert,
+ "ref" = REF(round_trait),
+ ))
+
+ data["current_traits"] = current_round_traits
+ data["future_round_traits"] = future_traits
+
+ return data
+
+/datum/round_traits_panel/ui_static_data(mob/user)
+ var/list/data = list()
+
+ var/list/valid_round_traits = list()
+
+ for (var/datum/round_trait/round_trait_path as anything in subtypesof(/datum/round_trait))
+ valid_round_traits += list(list(
+ "name" = initial(round_trait_path.name),
+ "path" = round_trait_path,
+ ))
+
+ data["valid_round_traits"] = valid_round_traits
+
+ return data
+
+/datum/round_traits_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if (.)
+ return
+
+ switch (action)
+ if ("revert")
+ var/ref = params["ref"]
+ if (!ref)
+ return TRUE
+
+ var/datum/round_trait/round_trait = locate(ref)
+
+ if (!istype(round_trait))
+ return TRUE
+
+ if (too_late_to_revert())
+ to_chat(usr, SPAN_WARNING("It's too late to revert station traits, the round has already started!"))
+ return TRUE
+
+ if (!round_trait.can_revert)
+ stack_trace("[round_trait.type] can't be reverted, but was requested anyway.")
+ return TRUE
+
+ var/message = "[key_name(usr)] reverted the station trait [round_trait.name] ([round_trait.type])"
+ log_admin(message)
+ message_admins(message)
+
+ round_trait.revert()
+
+ return TRUE
+ if ("setup_future_traits")
+ if (too_late_for_future_traits())
+ to_chat(usr, SPAN_WARNING("It's too late to add future station traits, the round is already over!"))
+ return TRUE
+
+ var/list/new_future_traits = list()
+ var/list/round_trait_names = list()
+
+ for (var/round_trait_text in params["round_traits"])
+ var/datum/round_trait/round_trait_path = text2path(round_trait_text)
+ if (!ispath(round_trait_path, /datum/round_trait) || round_trait_path == /datum/round_trait)
+ log_admin("[key_name(usr)] tried to set an invalid future station trait: [round_trait_text]")
+ to_chat(usr, SPAN_WARNING("Invalid future station trait: [round_trait_text]"))
+ return TRUE
+
+ round_trait_names += initial(round_trait_path.name)
+
+ new_future_traits += list(list(
+ "name" = initial(round_trait_path.name),
+ "path" = round_trait_path,
+ ))
+
+ var/message = "[key_name(usr)] has prepared the following station traits for next round: [round_trait_names.Join(", ") || "None"]"
+ log_admin(message)
+ message_admins(message)
+
+ future_traits = new_future_traits
+ rustg_file_write(json_encode(params["round_traits"]), FUTURE_ROUND_TRAITS_FILE)
+
+ return TRUE
+ if ("clear_future_traits")
+ if (!future_traits)
+ to_chat(usr, SPAN_WARNING("There are no future station traits."))
+ return TRUE
+
+ var/message = "[key_name(usr)] has cleared the station traits for next round."
+ log_admin(message)
+ message_admins(message)
+
+ fdel(FUTURE_ROUND_TRAITS_FILE)
+ future_traits = null
+
+ return TRUE
+
+/datum/round_traits_panel/proc/too_late_for_future_traits()
+ return SSticker.current_state >= GAME_STATE_FINISHED
+
+/datum/round_traits_panel/proc/too_late_to_revert()
+ return SSticker.current_state >= GAME_STATE_PLAYING
+
+/datum/round_traits_panel/ui_status(mob/user, datum/ui_state/state)
+ return check_rights_for(user.client, R_EVENT) ? UI_INTERACTIVE : UI_CLOSE
+
+/datum/round_traits_panel/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "RoundTraitsPanel")
+ ui.open()
diff --git a/code/datums/round_traits/human_negative.dm b/code/datums/round_traits/human_negative.dm
new file mode 100644
index 000000000000..afb0c7efcb0a
--- /dev/null
+++ b/code/datums/round_traits/human_negative.dm
@@ -0,0 +1,15 @@
+/datum/round_trait/flu_season
+ name = "Flu Season"
+ trait_type = ROUND_TRAIT_HUMAN_NEGATIVE
+ weight = 5
+ show_in_human_report = TRUE
+ human_report_message = "Someone was sick in cryo, and the tubes linking the pods carried the disease to the rest of us. Seems like we might be getting sick."
+ trait_to_give = TRAIT_ROUND_FLU_SEASON
+
+/datum/round_trait/faulty_piping
+ name = "Faulty Piping"
+ trait_type = ROUND_TRAIT_NEUTRAL
+ weight = 1
+ show_in_human_report = TRUE
+ human_report_message = "Looks like something got busted last time we were in drydock. Pipes are busted all over the place, looks a real mess."
+ trait_to_give = TRAIT_ROUND_FAULTY_PIPING
diff --git a/code/datums/round_traits/human_positive.dm b/code/datums/round_traits/human_positive.dm
new file mode 100644
index 000000000000..09ed38b751fb
--- /dev/null
+++ b/code/datums/round_traits/human_positive.dm
@@ -0,0 +1,18 @@
+/datum/round_trait/book_balancing
+ name = "A.S.R.S. Book Balancing"
+ trait_type = ROUND_TRAIT_HUMAN_POSITIVE
+ weight = 5
+ show_in_human_report = TRUE
+ human_report_message = "Someone got us more money upfront for less over time. Hope the operation doesn't drag on."
+
+/datum/round_trait/book_balancing/New()
+ . = ..()
+
+ var/who = pick("The Requistions Officer", "The Commanding Officer", "The Executive Officer", "A Rifleman")
+ var/what = pick("made a deal", "had a negotation while", "made a trade", "had an exchange", "had an interesting conversation")
+ var/where = pick("on a recent stop to a leisure planet", "on shore leave at a station", "on a back water colony")
+ human_report_message = "[who] [what] [where] and managed to secure some additional funding for the operation. Unfortunately, the upfront funding results in less resources available over time."
+
+/datum/round_trait/book_balancing/on_round_start()
+ supply_controller.points += 800
+ supply_controller.points_per_process = 0.6
diff --git a/code/datums/round_traits/neutral.dm b/code/datums/round_traits/neutral.dm
new file mode 100644
index 000000000000..ad42c3de33c7
--- /dev/null
+++ b/code/datums/round_traits/neutral.dm
@@ -0,0 +1,81 @@
+/datum/round_trait/black_market
+ name = "Illicit Dealings"
+ trait_type = ROUND_TRAIT_NEUTRAL
+ weight = 5
+ show_in_human_report = TRUE
+ human_report_message = "The software managing the A.S.R.S. has been playing up lately, with some unusual transmissions appearing on the terminals. Seems strange."
+
+/datum/round_trait/black_market/on_round_start()
+ supply_controller.black_market_enabled = TRUE
+
+/datum/round_trait/bad_wakeup
+ name = "Faulty Cryogenics"
+ trait_type = ROUND_TRAIT_NEUTRAL
+ weight = 5
+ show_in_human_report = TRUE
+ human_report_message = "ARES was flagging up some errors for a minor subsystem in the cryogenics pods. Should be all fine."
+ trait_to_give = TRAIT_ROUND_CRYOSLEEP_SICKNESS
+
+/datum/round_trait/economic_boom
+ name = "Economic Boom"
+ trait_type = ROUND_TRAIT_NEUTRAL
+ weight = 5
+ show_in_human_report = TRUE
+ human_report_message = "A small bonus has been deposited in the accounts of our personnel by The Company. Seemingly, they want everyone to co-operate with them on this operation."
+ trait_to_give = TRAIT_ROUND_ECONOMIC_BOOM
+ blacklist = list(/datum/round_trait/economic_boom)
+
+/datum/round_trait/economic_boom/on_round_start()
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "As an act of generosity from Weyland-Yutani, we have deposited a bonus in each and every account located on the [MAIN_SHIP_NAME]. We hope it is spent effectively, and we hope to see increased co-operation in the future.", "Weyland-Yutani Account Manager"), 2 MINUTES)
+
+/datum/round_trait/economic_slump
+ name = "Economic Slump"
+ trait_type = ROUND_TRAIT_NEUTRAL
+ weight = 5
+ show_in_human_report = TRUE
+ human_report_message = "Command's been cutting costs again. While we were in hypersleep, we were fired and rehired on worse contracts, with much worse pay agreements."
+ trait_to_give = TRAIT_ROUND_ECONOMIC_SLUMP
+ blacklist = list(/datum/round_trait/economic_boom)
+
+/datum/round_trait/economic_slump/on_round_start()
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(marine_announcement), "Due to budgetary constraints, we have been forced to alter the contracts of USCM employees on board the [MAIN_SHIP_NAME]. This is non-negotiable.", "USCM Office of Fiscal Affairs"), 2 MINUTES)
+
+/datum/round_trait/temperature_change
+ name = "Temperature Change"
+ trait_type = ROUND_TRAIT_NEUTRAL
+ weight = 5
+ show_in_human_report = TRUE
+ human_report_message = "Location we're orbiting seems to have different conditions than we were anticipating. Very unusual."
+ show_in_xeno_report = TRUE
+ xeno_report_message = "The feel of this world has changed since we emerged. The metal contraptions of the tall's are no longer rattling."
+
+/datum/round_trait/temperature_change/New()
+ . = ..()
+
+ GLOB.temperature_change = rand(-10, 10)
+
+/datum/round_trait/wrong_tubes
+ name = "Mismatched Squads"
+ trait_type = ROUND_TRAIT_NEUTRAL
+ weight = 1
+ show_in_human_report = TRUE
+ human_report_message = "Cryogenics system's woken everyone up in the wrong pods for their squad. No biggie, looks like they've been given the right access."
+ trait_to_give = TRAIT_ROUND_WRONG_TUBES
+
+/datum/round_trait/wrong_tubes/New()
+ . = ..()
+
+ var/list/squads = list()
+
+ for(var/obj/effect/landmark/late_join/landmark as anything in subtypesof(/obj/effect/landmark/late_join))
+ var/squad_to_add = initial(landmark.squad)
+ squads |= squad_to_add
+
+ var/list/shuffled_squads = shuffle(squads.Copy())
+
+ var/list/squad_mappings = list()
+
+ for(var/i = 1 to length(squads))
+ squad_mappings[squads[i]] = shuffled_squads[i]
+
+ GLOB.squad_mappings = squad_mappings
diff --git a/code/datums/round_traits/xeno_round_traits.dm b/code/datums/round_traits/xeno_round_traits.dm
new file mode 100644
index 000000000000..1dffa882c709
--- /dev/null
+++ b/code/datums/round_traits/xeno_round_traits.dm
@@ -0,0 +1,30 @@
+/datum/xeno_round_traits/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "XenoRoundTraits")
+ ui.open()
+
+/datum/xeno_round_traits/ui_data(mob/user)
+ var/data = list()
+
+ data["traits"] = list()
+ for(var/datum/round_trait/trait as anything in SSround.round_traits)
+ if(!trait.show_in_xeno_report || !trait.xeno_report_message)
+ continue
+
+ var/trait_info = list()
+ trait_info["name"] = trait.name
+ trait_info["report"] = trait.xeno_report_message
+ data["traits"] += list(trait_info)
+
+ return data
+
+/datum/xeno_round_traits/ui_static_data(mob/user)
+ var/data = list()
+
+ data["groundmap_name"] = SSmapping.configs[GROUND_MAP].map_name
+
+ return data
+
+/datum/xeno_round_traits/ui_state(mob/user)
+ return GLOB.always_state
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 2c274665fa19..73fcd936fe25 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -105,6 +105,11 @@
if(is_mainship_level(z))
GLOB.ship_areas += src
+ if(!is_ground_level(z))
+ return
+
+ temperature += GLOB.temperature_change
+
/area/proc/initialize_power_and_lighting(override_power)
if(requires_power)
luminosity = 0
diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm
index 234902e11d22..74d0495ea4b0 100644
--- a/code/game/jobs/job/job.dm
+++ b/code/game/jobs/job/job.dm
@@ -300,6 +300,9 @@
pod.go_in_cryopod(human, TRUE)
break
+ if(HAS_TRAIT(SSround, TRAIT_ROUND_CRYOSLEEP_SICKNESS))
+ addtimer(human, CALLBACK(TYPE_PROC_REF(/mob/living/carbon/human, vomit_on_floor)), rand(1, 5) SECONDS)
+
human.sec_hud_set_ID()
human.hud_set_squad()
diff --git a/code/game/jobs/job/marine/squad/standard.dm b/code/game/jobs/job/marine/squad/standard.dm
index e2502576e5ea..5c6996f628c9 100644
--- a/code/game/jobs/job/marine/squad/standard.dm
+++ b/code/game/jobs/job/marine/squad/standard.dm
@@ -16,6 +16,17 @@
flags_startup_parameters = ROLE_ADD_TO_SQUAD
gear_preset = /datum/equipment_preset/wo/marine/pfc
+/obj/effect/landmark/start/marine/Initialize(mapload, ...)
+ . = ..()
+
+ if(!HAS_TRAIT(SSround, TRAIT_ROUND_WRONG_TUBES))
+ return
+
+ if(!squad)
+ return
+
+ squad = GLOB.squad_mappings[squad]
+
/obj/effect/landmark/start/marine
name = JOB_SQUAD_MARINE
icon_state = "marine_spawn"
diff --git a/code/game/jobs/job/marine/squads.dm b/code/game/jobs/job/marine/squads.dm
index fb85be012d30..10cfc7ca8513 100644
--- a/code/game/jobs/job/marine/squads.dm
+++ b/code/game/jobs/job/marine/squads.dm
@@ -494,7 +494,20 @@
marines_list += M
M.assigned_squad = src //Add them to the squad
- C.access += (src.access + extra_access) //Add their squad access to their ID
+
+ var/mapped_access
+ if(HAS_TRAIT(SSround, TRAIT_ROUND_WRONG_TUBES))
+ var/mapped_name
+ for(var/squad in GLOB.squad_mappings)
+ if(GLOB.squad_mappings[squad] == name)
+ mapped_name = squad
+ break
+ var/datum/squad/mapped_squad = get_squad_by_name(mapped_name)
+ mapped_access = mapped_squad.access
+ var/access_to_add = mapped_access ? mapped_access : access
+
+ C.access += (access_to_add + extra_access) //Add their squad access to their ID
+
C.assignment = "[name] [assignment]"
SEND_SIGNAL(M, COMSIG_SET_SQUAD)
diff --git a/code/game/jobs/role_authority.dm b/code/game/jobs/role_authority.dm
index d1934c597da7..5de36963ad39 100644
--- a/code/game/jobs/role_authority.dm
+++ b/code/game/jobs/role_authority.dm
@@ -574,6 +574,12 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou
var/obj/structure/machinery/cryopod/pod = locate() in get_step(H, cardinal)
if(pod)
pod.go_in_cryopod(H, silent = TRUE)
+ if(ishuman_strict(H))
+ if(HAS_TRAIT(SSround, TRAIT_ROUND_CRYOSLEEP_SICKNESS))
+ addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living/carbon/human, vomit_on_floor)), rand(1, 5) SECONDS)
+ if(HAS_TRAIT(SSround, TRAIT_ROUND_FLU_SEASON))
+ if(prob(33))
+ H.contract_disease(new /datum/disease/flu/cryo_flu)
break
H.sec_hud_set_ID()
diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm
index d2e50aee9042..b82daa527289 100644
--- a/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm
+++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_prep.dm
@@ -15,6 +15,32 @@
return GLOB.not_incapacitated_and_adjacent_strict_state
/obj/structure/machinery/cm_vending/sorted/cargo_guns/squad_prep/populate_product_list(scale)
+ if(HAS_TRAIT(SSround, TRAIT_ROUND_STANDARD_ISSUE))
+ listed_products = list(
+ list("PRIMARY FIREARMS", -1, null, null),
+ list("M41A Pulse Rifle MK2", round(scale * 30), /obj/item/weapon/gun/rifle/m41a, VENDOR_ITEM_RECOMMENDED),
+
+ list("PRIMARY AMMUNITION", -1, null, null),
+ list("M41A Magazine (10x24mm)", round(scale * 25), /obj/item/ammo_magazine/rifle, VENDOR_ITEM_REGULAR),
+
+ list("SIDEARMS", -1, null, null),
+ list("M4A3 Service Pistol", round(scale * 25), /obj/item/weapon/gun/pistol/m4a3, VENDOR_ITEM_REGULAR),
+
+ list("SIDEARM AMMUNITION", -1, null, null),
+ list("M4A3 Magazine (9mm)", round(scale * 25), /obj/item/ammo_magazine/pistol, VENDOR_ITEM_REGULAR),
+
+ list("ATTACHMENTS", -1, null, null),
+ list("M41A Folding Stock", round(scale * 10), /obj/item/attachable/stock/rifle/collapsible, VENDOR_ITEM_REGULAR),
+ list("Rail Flashlight", round(scale * 25), /obj/item/attachable/flashlight, VENDOR_ITEM_RECOMMENDED),
+ list("Underbarrel Flashlight Grip", round(scale * 10), /obj/item/attachable/flashlight/grip, VENDOR_ITEM_RECOMMENDED),
+ list("Underslung Grenade Launcher", round(scale * 25), /obj/item/attachable/attached_gun/grenade, VENDOR_ITEM_REGULAR), //They already get these as on-spawns, might as well formalize some spares.
+
+ list("UTILITIES", -1, null, null),
+ list("M5 Bayonet", round(scale * 25), /obj/item/attachable/bayonet, VENDOR_ITEM_REGULAR),
+ list("M94 Marking Flare Pack", round(scale * 10), /obj/item/storage/box/m94, VENDOR_ITEM_RECOMMENDED)
+ )
+ return
+
listed_products = list(
list("PRIMARY FIREARMS", -1, null, null),
list("M4RA Battle Rifle", round(scale * 10), /obj/item/weapon/gun/rifle/m4ra, VENDOR_ITEM_REGULAR),
diff --git a/code/game/objects/effects/landmarks/landmarks.dm b/code/game/objects/effects/landmarks/landmarks.dm
index 5f4a374ba31c..0ec097f46ae3 100644
--- a/code/game/objects/effects/landmarks/landmarks.dm
+++ b/code/game/objects/effects/landmarks/landmarks.dm
@@ -404,7 +404,12 @@
/obj/effect/landmark/late_join/Initialize(mapload, ...)
. = ..()
+
if(squad)
+
+ if(HAS_TRAIT(SSround, TRAIT_ROUND_WRONG_TUBES))
+ squad = GLOB.squad_mappings[squad]
+
LAZYADD(GLOB.latejoin_by_squad[squad], src)
else if(job)
LAZYADD(GLOB.latejoin_by_job[job], src)
diff --git a/code/game/objects/structures/pipes/pipes.dm b/code/game/objects/structures/pipes/pipes.dm
index 9f2b70c70661..ee18356a5d14 100644
--- a/code/game/objects/structures/pipes/pipes.dm
+++ b/code/game/objects/structures/pipes/pipes.dm
@@ -45,6 +45,14 @@
if(explodey)
GLOB.mainship_pipes += src
+ if(!HAS_TRAIT(SSround, TRAIT_ROUND_FAULTY_PIPING))
+ return
+
+ if(!prob(1))
+ return
+
+ cell_explosion(src, 40, 40, explosion_cause_data = create_cause_data("faulty pipes"))
+
/obj/structure/pipes/Destroy()
for(var/mob/living/M in src)
M.remove_ventcrawl()
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 368e2766ccfc..14375288b28d 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -131,6 +131,8 @@ var/list/admin_verbs_minor_event = list(
/client/proc/force_event,
/client/proc/toggle_events,
/client/proc/toggle_shipside_sd,
+ /datum/admins/proc/round_traits_panel,
+ /client/proc/shakeshipverb,
/client/proc/shakeshipverb,
/client/proc/adminpanelweapons,
/client/proc/adminpanelgq,
diff --git a/code/modules/almayer/machinery.dm b/code/modules/almayer/machinery.dm
index cb90db9e8535..6ea547be5f45 100644
--- a/code/modules/almayer/machinery.dm
+++ b/code/modules/almayer/machinery.dm
@@ -150,6 +150,43 @@
icon = 'icons/obj/structures/props/almayer_props.dmi'
icon_state = "sensor_comp1"
+/obj/structure/prop/almayer/computers/sensor_computer1/attack_hand(mob/user)
+ . = ..()
+
+ if(user.is_mob_incapacitated())
+ return
+
+ tgui_interact(user)
+
+/obj/structure/prop/almayer/computers/sensor_computer1/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "HumanEventsComputer")
+ ui.open()
+
+/obj/structure/prop/almayer/computers/sensor_computer1/ui_data(mob/user)
+ var/data = list()
+
+ data["traits"] = list()
+ for(var/datum/round_trait/trait as anything in SSround.round_traits)
+ if(!trait.show_in_human_report || !trait.human_report_message)
+ continue
+
+ var/trait_info = list()
+ trait_info["name"] = trait.name
+ trait_info["report"] = trait.human_report_message
+ data["traits"] += list(trait_info)
+
+ return data
+
+/obj/structure/prop/almayer/computers/sensor_computer1/ui_static_data(mob/user)
+ var/data = list()
+
+ data["shipmap_name"] = SSmapping.get_main_ship_name()
+ data["groundmap_name"] = SSmapping.configs[GROUND_MAP].map_name
+
+ return data
+
/obj/structure/prop/almayer/computers/sensor_computer2
name = "sensor computer"
desc = "The IBM series 10 computer retrofitted to work as a sensor computer for the ship. While somewhat dated it still serves its purpose."
diff --git a/code/modules/economy/Accounts.dm b/code/modules/economy/Accounts.dm
index 7e5bf22041a0..65cdd63ccd0e 100644
--- a/code/modules/economy/Accounts.dm
+++ b/code/modules/economy/Accounts.dm
@@ -26,6 +26,12 @@
M.remote_access_pin = rand(1111, 111111)
M.money = starting_funds * id_paygrade.pay_multiplier
+ if(HAS_TRAIT(SSround, TRAIT_ROUND_ECONOMIC_BOOM))
+ M.money *= 3
+
+ if(HAS_TRAIT(SSround, TRAIT_ROUND_ECONOMIC_SLUMP))
+ M.money *= 0.3
+
//create an entry in the account transaction log for when it was created
var/datum/transaction/T = new()
T.target_name = new_owner_name
diff --git a/code/modules/gear_presets/uscm.dm b/code/modules/gear_presets/uscm.dm
index 1c84e7cad4ba..b8547a6293e2 100644
--- a/code/modules/gear_presets/uscm.dm
+++ b/code/modules/gear_presets/uscm.dm
@@ -23,6 +23,26 @@
dress_shoes = list(/obj/item/clothing/shoes/dress)
var/auto_squad_name
+/datum/equipment_preset/uscm/New()
+ . = ..()
+
+ if(!HAS_TRAIT(SSround, TRAIT_ROUND_WRONG_TUBES))
+ return
+
+ var/static/list/squad_accesses = list(ACCESS_MARINE_ALPHA, ACCESS_MARINE_BRAVO, ACCESS_MARINE_CHARLIE, ACCESS_MARINE_DELTA)
+
+ var/has_squad_access = FALSE
+ for(var/squad_access in squad_accesses)
+ if(LAZYISIN(access, squad_access))
+ has_squad_access = TRUE
+ break
+
+ if(!has_squad_access)
+ return
+
+ for(var/squad_access in squad_accesses)
+ access |= squad_access
+
/datum/equipment_preset/uscm/load_status(mob/living/carbon/human/new_human)
new_human.nutrition = rand(NUTRITION_VERYLOW, NUTRITION_LOW)
diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm
index 4fe1be51bfff..c82278869e5e 100644
--- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm
+++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm
@@ -214,3 +214,6 @@
return
xenoSrc.overwatch(xenoTarget)
+
+ if("roundtraits")
+ GLOB.xeno_round_traits.tgui_interact(usr)
diff --git a/colonialmarines.dme b/colonialmarines.dme
index 42e7880bd313..147d47a0b4d0 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -89,6 +89,7 @@
#include "code\__DEFINES\radio.dm"
#include "code\__DEFINES\redis.dm"
#include "code\__DEFINES\regex.dm"
+#include "code\__DEFINES\round.dm"
#include "code\__DEFINES\sentry_laptop_configurations.dm"
#include "code\__DEFINES\shuttles.dm"
#include "code\__DEFINES\skills.dm"
@@ -302,6 +303,7 @@
#include "code\controllers\subsystem\processing\obj_tab_items.dm"
#include "code\controllers\subsystem\processing\objects.dm"
#include "code\controllers\subsystem\processing\processing.dm"
+#include "code\controllers\subsystem\processing\round.dm"
#include "code\controllers\subsystem\processing\shield_pillar.dm"
#include "code\controllers\subsystem\processing\slowobj.dm"
#include "code\datums\_atmos_setup.dm"
@@ -559,6 +561,12 @@
#include "code\datums\paygrades\factions\wy\goons.dm"
#include "code\datums\paygrades\factions\wy\pmc.dm"
#include "code\datums\paygrades\factions\wy\wy.dm"
+#include "code\datums\round_traits\_round_traits.dm"
+#include "code\datums\round_traits\admin_panel.dm"
+#include "code\datums\round_traits\human_negative.dm"
+#include "code\datums\round_traits\human_positive.dm"
+#include "code\datums\round_traits\neutral.dm"
+#include "code\datums\round_traits\xeno_round_traits.dm"
#include "code\datums\redis\redis_message.dm"
#include "code\datums\redis\callbacks\_redis_callback.dm"
#include "code\datums\redis\callbacks\asay.dm"
diff --git a/tgui/packages/tgui/interfaces/HiveStatus.js b/tgui/packages/tgui/interfaces/HiveStatus.js
index c621430a9f7c..872d89aff330 100644
--- a/tgui/packages/tgui/interfaces/HiveStatus.js
+++ b/tgui/packages/tgui/interfaces/HiveStatus.js
@@ -106,7 +106,7 @@ export const HiveStatus = (props, context) => {
};
const GeneralInformation = (props, context) => {
- const { data } = useBackend(context);
+ const { data, act } = useBackend(context);
const {
queen_location,
hive_location,
@@ -136,6 +136,12 @@ const GeneralInformation = (props, context) => {
Evilution: {evilution_level}
+
+
);
};
@@ -402,9 +408,9 @@ const StatusIcon = (props, context) => {
}
};
-const XenoCollapsible = (props, context) => {
+export const XenoCollapsible = (props, context) => {
const { data } = useBackend(context);
- const { title, children } = props;
+ const { title, children, closed } = props;
const { hive_color } = data;
return (
@@ -412,7 +418,7 @@ const XenoCollapsible = (props, context) => {
title={title}
backgroundColor={!!hive_color && hive_color}
color={!hive_color && 'xeno'}
- open>
+ open={!closed}>
{children}
);
diff --git a/tgui/packages/tgui/interfaces/HumanEventsComputer.tsx b/tgui/packages/tgui/interfaces/HumanEventsComputer.tsx
new file mode 100644
index 000000000000..6a439e40d53a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/HumanEventsComputer.tsx
@@ -0,0 +1,105 @@
+import { useBackend, useLocalState } from '../backend';
+import { Button, Section, Stack } from '../components';
+import { Window } from '../layouts';
+
+interface EventComputerData {
+ shipmap_name: string;
+ groundmap_name: string;
+ traits: TraitItem[];
+}
+
+interface TraitItem {
+ name: string;
+ report: string;
+}
+
+export const HumanEventsComputer = (props, context) => {
+ const { data } = useBackend(context);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const ShipInfo = (props, context) => {
+ const { data } = useBackend(context);
+ const { shipmap_name, groundmap_name } = data;
+ return (
+
+
+
+
+
+ Ship: {shipmap_name}
+
+
+ Orbiting: {groundmap_name}
+
+
+ Time: 12:07
+
+
+
+
+
+
+ User: Daniel Jimenez
+
+
+ Assignment: Executive Officer
+
+
+
+
+
+ );
+};
+
+const Notifications = (props, context) => {
+ const { data } = useBackend(context);
+ const traits = Array.from(data.traits);
+
+ const [currentNotification, setNotification] = useLocalState(
+ context,
+ 'notificationText',
+ traits[0] ? traits[0]?.name : ''
+ );
+
+ return (
+
+
+
+ {traits.map((val) => (
+
+ ))}
+
+
+
+
+ {
+ traits.find((element) => element.name === currentNotification)
+ ?.report
+ }
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/RoundTraitsPanel.tsx b/tgui/packages/tgui/interfaces/RoundTraitsPanel.tsx
new file mode 100644
index 000000000000..2184697c2b31
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RoundTraitsPanel.tsx
@@ -0,0 +1,250 @@
+import { filterMap } from 'common/collections';
+import { exhaustiveCheck } from 'common/exhaustive';
+import { BooleanLike } from 'common/react';
+import { useBackend, useLocalState } from '../backend';
+import { Box, Button, Divider, Dropdown, Stack, Tabs } from '../components';
+import { Window } from '../layouts';
+
+type CurrentRoundTrait = {
+ can_revert: BooleanLike;
+ name: string;
+ ref: string;
+};
+
+type ValidRoundTrait = {
+ name: string;
+ path: string;
+};
+
+type RoundTraitsData = {
+ current_traits: CurrentRoundTrait[];
+ future_round_traits?: ValidRoundTrait[];
+ too_late_to_revert: BooleanLike;
+ valid_round_traits: ValidRoundTrait[];
+};
+
+enum Tab {
+ SetupFutureRoundTraits,
+ ViewRoundTraits,
+}
+
+const FutureRoundTraitsPage = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { future_round_traits } = data;
+
+ const [selectedTrait, setSelectedTrait] = useLocalState(
+ context,
+ 'selectedFutureTrait',
+ null
+ );
+
+ const traitsByName = Object.fromEntries(
+ data.valid_round_traits.map((trait) => {
+ return [trait.name, trait.path];
+ })
+ );
+
+ const traitNames = Object.keys(traitsByName);
+ traitNames.sort();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.isArray(future_round_traits) ? (
+ future_round_traits.length > 0 ? (
+
+ {future_round_traits.map((trait) => (
+
+
+ {trait.name}
+
+
+
+
+
+
+ ))}
+
+ ) : (
+ <>
+ No round traits will run next round.
+
+
+
+
+ >
+ )
+ ) : (
+ <>
+ No future round traits are planned.
+
+
+
+
+ >
+ )}
+
+ );
+};
+
+const ViewRoundTraitsPage = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return data.current_traits.length > 0 ? (
+
+ {data.current_traits.map((roundTrait) => (
+
+
+ {roundTrait.name}
+
+
+
+ act('revert', {
+ ref: roundTrait.ref,
+ })
+ }
+ />
+
+
+
+ ))}
+
+ ) : (
+ There are no active round traits.
+ );
+};
+
+export const RoundTraitsPanel = (props, context) => {
+ const [currentTab, setCurrentTab] = useLocalState(
+ context,
+ 'round_traits_tab',
+ Tab.ViewRoundTraits
+ );
+
+ let currentPage;
+
+ switch (currentTab) {
+ case Tab.SetupFutureRoundTraits:
+ currentPage = ;
+ break;
+ case Tab.ViewRoundTraits:
+ currentPage = ;
+ break;
+ default:
+ exhaustiveCheck(currentTab);
+ }
+
+ return (
+
+
+
+ setCurrentTab(Tab.ViewRoundTraits)}>
+ View
+
+
+ setCurrentTab(Tab.SetupFutureRoundTraits)}>
+ Edit
+
+
+
+
+
+ {currentPage}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/XenoRoundTraits.tsx b/tgui/packages/tgui/interfaces/XenoRoundTraits.tsx
new file mode 100644
index 000000000000..52d4b1b92e98
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/XenoRoundTraits.tsx
@@ -0,0 +1,42 @@
+import { useBackend } from '../backend';
+import { Stack } from '../components';
+import { Window } from '../layouts';
+import { XenoCollapsible } from './HiveStatus';
+
+interface EventComputerData {
+ shipmap_name: string;
+ groundmap_name: string;
+ traits: TraitItem[];
+}
+
+interface TraitItem {
+ name: string;
+ report: string;
+}
+
+export const XenoRoundTraits = (props, context) => {
+ const { data } = useBackend(context);
+ return (
+
+
+
+
+ Proclamations from the Queen Mother
+
+ There {data.traits.length > 1 ? 'are' : 'is'} {data.traits.length}{' '}
+ Proclamation
+ {data.traits.length > 1 ? 's' : ''}
+
+
+
+ {data.traits.map((trait) => (
+
+ {trait.report}
+
+ ))}
+
+
+
+
+ );
+};