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} + + + ))} + + + +
+ { + 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} + + ))} + +
+
+
+ ); +};