From 706433304ca0df4ee2ef370b7f9eb0b87b39ee4f Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Fri, 14 Feb 2025 12:46:56 +0000 Subject: [PATCH 01/21] Removes Secondary & Final Objectives from Traitors (#89466) --- .../RandomRuins/SpaceRuins/garbagetruck2.dmm | 1 - code/__DEFINES/dcs/signals/signals_traitor.dm | 25 - code/__DEFINES/uplink.dm | 15 +- .../configuration/entries/game_options.dm | 5 - .../dynamic/dynamic_rulesets_latejoin.dm | 2 +- .../dynamic/dynamic_rulesets_midround.dm | 4 +- code/controllers/subsystem/traitor.dm | 50 -- code/datums/components/crafting/structures.dm | 14 - code/datums/components/uplink.dm | 100 +-- code/datums/mind/_mind.dm | 55 -- code/game/gamemodes/objective_items.dm | 48 ++ .../structures/syndicate_uplink_beacon.dm | 114 ---- code/modules/admin/antag_panel.dm | 4 - code/modules/admin/verbs/secrets.dm | 1 - code/modules/antagonists/traitor/README.md | 58 -- .../antagonists/traitor/balance_helper.dm | 109 ---- .../components/traitor_objective_helpers.dm | 54 -- .../traitor_objective_limit_per_time.dm | 41 -- .../traitor_objective_mind_tracker.dm | 40 -- .../antagonists/traitor/datum_traitor.dm | 91 +-- .../antagonists/traitor/objective_category.dm | 68 --- .../objectives/abstract/target_player.dm | 33 - .../traitor/objectives/assassination.dm | 205 ------- .../traitor/objectives/demoralise_assault.dm | 129 ---- .../traitor/objectives/destroy_heirloom.dm | 151 ----- .../traitor/objectives/destroy_item.dm | 109 ---- .../traitor/objectives/eyesnatching.dm | 240 -------- .../final_objective/battlecruiser.dm | 49 -- .../final_objective/final_objective.dm | 42 -- .../objectives/final_objective/infect_ai.dm | 56 -- .../final_objective/objective_dark_matteor.dm | 83 --- .../objectives/final_objective/romerol.dm | 46 -- .../final_objective/supermatter_cascade.dm | 57 -- .../traitor/objectives/hack_comm_console.dm | 53 -- .../antagonists/traitor/objectives/infect.dm | 176 ------ .../traitor/objectives/kidnapping.dm | 320 ---------- .../traitor/objectives/kill_pet.dm | 96 --- .../traitor/objectives/locate_weakpoint.dm | 280 --------- .../traitor/objectives/sabotage_machinery.dm | 240 -------- .../antagonists/traitor/objectives/steal.dm | 318 ---------- .../antagonists/traitor/traitor_objective.dm | 264 -------- .../antagonists/traitor/uplink_handler.dm | 155 +---- code/modules/events/stray_cargo.dm | 8 +- .../ruins/spaceruin_code/garbagetruck.dm | 103 ++++ .../computers/item/disks/virus_disk.dm | 3 - code/modules/paperwork/paper_cutter.dm | 9 +- code/modules/station_goals/meteor_shield.dm | 2 - code/modules/unit_tests/_unit_tests.dm | 1 - code/modules/unit_tests/objectives.dm | 49 -- code/modules/unit_tests/traitor.dm | 8 - code/modules/uplink/uplink_devices.dm | 28 - .../modules/uplink/uplink_items/contractor.dm | 2 +- code/modules/uplink/uplink_items/nukeops.dm | 3 +- tgstation.dme | 28 - .../tgui/interfaces/AntagInfoTraitor.tsx | 60 +- .../tgui/interfaces/TraitorObjectiveDebug.tsx | 428 ------------- .../interfaces/Uplink/ObjectiveElement.tsx | 41 ++ .../tgui/interfaces/Uplink/ObjectiveMenu.tsx | 569 ------------------ .../Uplink/PrimaryObjectiveMenu.tsx | 55 +- .../tgui/interfaces/Uplink/constants.ts | 7 - .../packages/tgui/interfaces/Uplink/index.tsx | 295 +++------ 61 files changed, 344 insertions(+), 5356 deletions(-) delete mode 100644 code/game/objects/structures/syndicate_uplink_beacon.dm delete mode 100644 code/modules/antagonists/traitor/README.md delete mode 100644 code/modules/antagonists/traitor/balance_helper.dm delete mode 100644 code/modules/antagonists/traitor/components/traitor_objective_helpers.dm delete mode 100644 code/modules/antagonists/traitor/components/traitor_objective_limit_per_time.dm delete mode 100644 code/modules/antagonists/traitor/components/traitor_objective_mind_tracker.dm delete mode 100644 code/modules/antagonists/traitor/objective_category.dm delete mode 100644 code/modules/antagonists/traitor/objectives/abstract/target_player.dm delete mode 100644 code/modules/antagonists/traitor/objectives/assassination.dm delete mode 100644 code/modules/antagonists/traitor/objectives/demoralise_assault.dm delete mode 100644 code/modules/antagonists/traitor/objectives/destroy_heirloom.dm delete mode 100644 code/modules/antagonists/traitor/objectives/destroy_item.dm delete mode 100644 code/modules/antagonists/traitor/objectives/eyesnatching.dm delete mode 100644 code/modules/antagonists/traitor/objectives/final_objective/battlecruiser.dm delete mode 100644 code/modules/antagonists/traitor/objectives/final_objective/final_objective.dm delete mode 100644 code/modules/antagonists/traitor/objectives/final_objective/infect_ai.dm delete mode 100644 code/modules/antagonists/traitor/objectives/final_objective/objective_dark_matteor.dm delete mode 100644 code/modules/antagonists/traitor/objectives/final_objective/romerol.dm delete mode 100644 code/modules/antagonists/traitor/objectives/final_objective/supermatter_cascade.dm delete mode 100644 code/modules/antagonists/traitor/objectives/hack_comm_console.dm delete mode 100644 code/modules/antagonists/traitor/objectives/infect.dm delete mode 100644 code/modules/antagonists/traitor/objectives/kidnapping.dm delete mode 100644 code/modules/antagonists/traitor/objectives/kill_pet.dm delete mode 100644 code/modules/antagonists/traitor/objectives/locate_weakpoint.dm delete mode 100644 code/modules/antagonists/traitor/objectives/sabotage_machinery.dm delete mode 100644 code/modules/antagonists/traitor/objectives/steal.dm delete mode 100644 code/modules/antagonists/traitor/traitor_objective.dm create mode 100644 code/modules/mapfluff/ruins/spaceruin_code/garbagetruck.dm delete mode 100644 code/modules/unit_tests/objectives.dm delete mode 100644 tgui/packages/tgui/interfaces/TraitorObjectiveDebug.tsx create mode 100644 tgui/packages/tgui/interfaces/Uplink/ObjectiveElement.tsx delete mode 100644 tgui/packages/tgui/interfaces/Uplink/ObjectiveMenu.tsx delete mode 100644 tgui/packages/tgui/interfaces/Uplink/constants.ts diff --git a/_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm b/_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm index 1a97e8073331..26756ca5da93 100644 --- a/_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm +++ b/_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm @@ -102,7 +102,6 @@ "kp" = ( /obj/structure/safe, /obj/item/stamp/syndicate, -/obj/item/traitor_bug, /obj/item/eyesnatcher, /obj/item/stack/spacecash/c1000, /turf/open/floor/plating, diff --git a/code/__DEFINES/dcs/signals/signals_traitor.dm b/code/__DEFINES/dcs/signals/signals_traitor.dm index 4290b25b8009..0f80221dfefd 100644 --- a/code/__DEFINES/dcs/signals/signals_traitor.dm +++ b/code/__DEFINES/dcs/signals/signals_traitor.dm @@ -1,30 +1,5 @@ -/// Called when the hack_comm_console objective is completed. -#define COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED "!traitor_objective_completed" - /// Called whenever the uplink handler receives any sort of update. Used by uplinks to update their UI. No arguments passed #define COMSIG_UPLINK_HANDLER_ON_UPDATE "uplink_handler_on_update" -/// Sent from the uplink handler when the traitor uses the syndicate uplink beacon to order a replacement uplink. -#define COMSIG_UPLINK_HANDLER_REPLACEMENT_ORDERED "uplink_handler_replacement_ordered" - -/// Called before the traitor objective is generated -#define COMSIG_TRAITOR_OBJECTIVE_PRE_GENERATE "traitor_objective_pre_generate" - #define COMPONENT_TRAITOR_OBJECTIVE_ABORT_GENERATION (1<<0) -/// Called whenever the traitor objective is completed -#define COMSIG_TRAITOR_OBJECTIVE_COMPLETED "traitor_objective_completed" -/// Called whenever the traitor objective is failed -#define COMSIG_TRAITOR_OBJECTIVE_FAILED "traitor_objective_failed" - -/// Called when a traitor bug is planted in an area -#define COMSIG_TRAITOR_BUG_PLANTED_GROUND "traitor_bug_planted_area" -/// Called when a traitor bug is planted -#define COMSIG_TRAITOR_BUG_PLANTED_OBJECT "traitor_bug_planted_object" -/// Called before a traitor bug is planted, where it can still be overrided -#define COMSIG_TRAITOR_BUG_PRE_PLANTED_OBJECT "traitor_bug_planted_pre_object" - #define COMPONENT_FORCE_PLACEMENT (1<<0) - #define COMPONENT_FORCE_FAIL_PLACEMENT (1<<1) - -/// Called when a machine a traitor has booby trapped triggers its payload -#define COMSIG_TRAITOR_MACHINE_TRAP_TRIGGERED "traitor_machine_trap_triggered" /// Called when a device a traitor has planted effects someone's mood. Pass the mind of the viewer. #define COMSIG_DEMORALISING_EVENT "traitor_demoralise_event" diff --git a/code/__DEFINES/uplink.dm b/code/__DEFINES/uplink.dm index 76c1f2265a59..3b3621f24c54 100644 --- a/code/__DEFINES/uplink.dm +++ b/code/__DEFINES/uplink.dm @@ -9,8 +9,19 @@ /// This item is purchasable to clown ops #define UPLINK_CLOWN_OPS (1 << 2) -/// This item is purchasable to infiltrators (midround traitors) -#define UPLINK_INFILTRATORS (1 << 3) +/// Can be randomly given to spies for their bounties +#define UPLINK_SPY (1 << 3) + +#define UPLINK_LONE_OP (1 << 4) + +/// A blanket define for an item being purchasable by all types of nukie +#define UPLINK_ALL_SYNDIE_OPS (UPLINK_NUKE_OPS | UPLINK_LONE_OP | UPLINK_CLOWN_OPS) + +/// A blanket define for an item being purchasable by all operatives that spawn at the nukie firebase +#define UPLINK_FIREBASE_OPS (UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) + +/// A define that excludes clown ops from the regular nukeop gear lineup +#define UPLINK_SERIOUS_OPS (UPLINK_NUKE_OPS | UPLINK_LONE_OP) /// Progression gets turned into a user-friendly form. This is just an abstract equation that makes progression not too large. #define DISPLAY_PROGRESSION(time) round(time/60, 0.01) diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 21fc2cb196af..31605037c84e 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -72,11 +72,6 @@ default = 1 min_val = 0.01 -/// Determines how many potential objectives a traitor can have. -/datum/config_entry/number/maximum_potential_objectives - default = 6 - min_val = 1 - /datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings default = 6 integer = FALSE diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm index d853876fed14..17b0d34a4a1d 100644 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm @@ -52,7 +52,7 @@ /datum/dynamic_ruleset/latejoin/infiltrator name = "Syndicate Infiltrator" - antag_datum = /datum/antagonist/traitor/infiltrator + antag_datum = /datum/antagonist/traitor antag_flag = ROLE_SYNDICATE_INFILTRATOR antag_flag_override = ROLE_TRAITOR protected_roles = list( diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm index 7d9ee99c136f..b74ebe47925e 100644 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm +++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm @@ -222,7 +222,7 @@ /datum/dynamic_ruleset/midround/from_living/autotraitor name = "Syndicate Sleeper Agent" midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/traitor/infiltrator/sleeper_agent + antag_datum = /datum/antagonist/traitor antag_flag = ROLE_SLEEPER_AGENT antag_flag_override = ROLE_TRAITOR protected_roles = list( @@ -260,7 +260,7 @@ var/mob/M = pick(candidates) assigned += M candidates -= M - var/datum/antagonist/traitor/infiltrator/sleeper_agent/newTraitor = new + var/datum/antagonist/traitor/newTraitor = new M.mind.add_antag_datum(newTraitor) message_admins("[ADMIN_LOOKUPFLW(M)] was selected by the [name] ruleset and has been made into a midround traitor.") log_dynamic("[key_name(M)] was selected by the [name] ruleset and has been made into a midround traitor.") diff --git a/code/controllers/subsystem/traitor.dm b/code/controllers/subsystem/traitor.dm index dad7284216dc..0097e23d1b90 100644 --- a/code/controllers/subsystem/traitor.dm +++ b/code/controllers/subsystem/traitor.dm @@ -13,15 +13,6 @@ SUBSYSTEM_DEF(traitor) /// A list of all uplink items var/list/uplink_items = list() - /// File to load configurations from. - var/configuration_path = "config/traitor_objective.json" - /// Global configuration data that gets applied to each objective when it is created. - /// Basic objective format - /// '/datum/traitor_objective/path/to/objective': { - /// "global_progression_influence_intensity": 0 - /// } - var/configuration_data = list() - /// The coefficient multiplied by the current_global_progression for new joining traitors to calculate their progression var/newjoin_progression_coeff = 1 /// The current progression that all traitors should be at in the round @@ -33,31 +24,10 @@ SUBSYSTEM_DEF(traitor) var/list/datum/uplink_handler/uplink_handlers = list() /// The current scaling per minute of progression. Has a maximum value of 1 MINUTES. var/current_progression_scaling = 1 MINUTES - /// Used to handle the probability of getting an objective. - var/datum/traitor_category_handler/category_handler - /// The current debug handler for objectives. Used for debugging objectives - var/datum/traitor_objective_debug/traitor_debug_panel - /// Used by the debug menu, decides whether newly created objectives should generate progression and telecrystals. Do not modify for non-debug purposes. - var/generate_objectives = TRUE - /// Objectives that have been completed by type. Used for limiting objectives. - var/list/taken_objectives_by_type = list() - /// A list of all existing objectives by type - var/list/all_objectives_by_type = list() /datum/controller/subsystem/traitor/Initialize() - category_handler = new() - traitor_debug_panel = new(category_handler) - for(var/theft_item in subtypesof(/datum/objective_item/steal)) new theft_item - - if(fexists(configuration_path)) - var/list/data = json_decode(file2text(file(configuration_path))) - for(var/typepath in data) - var/actual_typepath = text2path(typepath) - if(!actual_typepath) - log_world("[configuration_path] has an invalid type ([typepath]) that doesn't exist in the codebase! Please correct or remove [typepath]") - configuration_data[actual_typepath] = data[typepath] return SS_INIT_SUCCESS /datum/controller/subsystem/traitor/fire(resumed) @@ -87,7 +57,6 @@ SUBSYSTEM_DEF(traitor) var/amount_to_give = progression_scaling_delta + (progression_scaling_delta * deviance) amount_to_give = clamp(amount_to_give, 0, progression_scaling_delta * 2) handler.progression_points += amount_to_give - handler.update_objectives() handler.on_update() /datum/controller/subsystem/traitor/proc/register_uplink_handler(datum/uplink_handler/uplink_handler) @@ -101,22 +70,3 @@ SUBSYSTEM_DEF(traitor) /datum/controller/subsystem/traitor/proc/uplink_handler_deleted(datum/uplink_handler/uplink_handler) SIGNAL_HANDLER uplink_handlers -= uplink_handler - -/datum/controller/subsystem/traitor/proc/on_objective_taken(datum/traitor_objective/objective) - if(!istype(objective)) - return - - add_objective_to_list(objective, taken_objectives_by_type) - -/datum/controller/subsystem/traitor/proc/get_taken_count(datum/traitor_objective/objective_type) - return length(taken_objectives_by_type[objective_type]) - - -/datum/controller/subsystem/traitor/proc/add_objective_to_list(datum/traitor_objective/objective, list/objective_list) - var/datum/traitor_objective/current_type = objective.type - while(current_type != /datum/traitor_objective) - if(!objective_list[current_type]) - objective_list[current_type] = list(objective) - else - objective_list[current_type] += objective - current_type = type2parent(current_type) diff --git a/code/datums/components/crafting/structures.dm b/code/datums/components/crafting/structures.dm index c4a9b48ec36b..aadc142561d5 100644 --- a/code/datums/components/crafting/structures.dm +++ b/code/datums/components/crafting/structures.dm @@ -60,17 +60,3 @@ /obj/item/stack/cable_coil = 10, ) category = CAT_STRUCTURE - -/datum/crafting_recipe/syndicate_uplink_beacon - name = "Syndicate Uplink Beacon" - result = /obj/structure/syndicate_uplink_beacon - tool_behaviors = list(TOOL_SCREWDRIVER) - time = 6 SECONDS - reqs = list( - /obj/item/stack/sheet/iron = 5, - /obj/item/stack/cable_coil = 5, - /obj/item/beacon = 1, - /obj/item/stack/ore/bluespace_crystal = 1, - ) - category = CAT_STRUCTURE - crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm index 9f56bc3f6f68..64a930408d51 100644 --- a/code/datums/components/uplink.dm +++ b/code/datums/components/uplink.dm @@ -66,8 +66,6 @@ RegisterSignal(parent, COMSIG_RADIO_NEW_MESSAGE, PROC_REF(new_message)) else if(istype(parent, /obj/item/pen)) RegisterSignal(parent, COMSIG_PEN_ROTATED, PROC_REF(pen_rotation)) - else if(istype(parent, /obj/item/uplink/replacement)) - RegisterSignal(parent, COMSIG_MOVABLE_HEAR, PROC_REF(on_heard)) if(owner) src.owner = owner @@ -81,7 +79,6 @@ src.active = enabled if(!uplink_handler_override) uplink_handler = new() - uplink_handler.has_objectives = FALSE uplink_handler.uplink_flag = uplink_flag uplink_handler.telecrystals = starting_tc uplink_handler.has_progression = has_progression @@ -89,7 +86,6 @@ else uplink_handler = uplink_handler_override RegisterSignal(uplink_handler, COMSIG_UPLINK_HANDLER_ON_UPDATE, PROC_REF(handle_uplink_handler_update)) - RegisterSignal(uplink_handler, COMSIG_UPLINK_HANDLER_REPLACEMENT_ORDERED, PROC_REF(handle_uplink_replaced)) if(!lockable) active = TRUE locked = FALSE @@ -100,28 +96,6 @@ SIGNAL_HANDLER SStgui.update_uis(src) -/// When a new uplink is made via the syndicate beacon it locks all lockable uplinks and destroys replacement uplinks -/datum/component/uplink/proc/handle_uplink_replaced() - SIGNAL_HANDLER - if(lockable) - lock_uplink() - if(!istype(parent, /obj/item/uplink/replacement)) - return - var/obj/item/uplink_item = parent - do_sparks(number = 3, cardinal_only = FALSE, source = uplink_item) - uplink_item.visible_message(span_warning("The [uplink_item] suddenly combusts!"), vision_distance = COMBAT_MESSAGE_RANGE) - new /obj/effect/decal/cleanable/ash(get_turf(uplink_item)) - qdel(uplink_item) - -/// Adds telecrystals to the uplink. It is bad practice to use this outside of the component itself. -/datum/component/uplink/proc/add_telecrystals(telecrystals_added) - set_telecrystals(uplink_handler.telecrystals + telecrystals_added) - -/// Sets the telecrystals of the uplink. It is bad practice to use this outside of the component itself. -/datum/component/uplink/proc/set_telecrystals(new_telecrystal_amount) - uplink_handler.telecrystals = new_telecrystal_amount - uplink_handler.on_update() - /datum/component/uplink/InheritComponent(datum/component/uplink/uplink) lockable |= uplink.lockable active |= uplink.active @@ -195,12 +169,10 @@ data["telecrystals"] = uplink_handler.telecrystals data["progression_points"] = uplink_handler.progression_points data["current_expected_progression"] = SStraitor.current_global_progression - data["maximum_active_objectives"] = uplink_handler.maximum_active_objectives data["progression_scaling_deviance"] = SStraitor.progression_scaling_deviance data["current_progression_scaling"] = SStraitor.current_progression_scaling - data["maximum_potential_objectives"] = uplink_handler.maximum_potential_objectives - if(uplink_handler.has_objectives) + if(uplink_handler.primary_objectives) var/list/primary_objectives = list() for(var/datum/objective/task as anything in uplink_handler.primary_objectives) var/list/task_data = list() @@ -211,24 +183,6 @@ task_data["task_text"] = task.explanation_text primary_objectives += list(task_data) - var/list/potential_objectives = list() - for(var/index in 1 to uplink_handler.potential_objectives.len) - var/datum/traitor_objective/objective = uplink_handler.potential_objectives[index] - var/list/objective_data = objective.uplink_ui_data(user) - objective_data["id"] = index - potential_objectives += list(objective_data) - - var/list/active_objectives = list() - for(var/index in 1 to uplink_handler.active_objectives.len) - var/datum/traitor_objective/objective = uplink_handler.active_objectives[index] - var/list/objective_data = objective.uplink_ui_data(user) - objective_data["id"] = index - active_objectives += list(objective_data) - - data["primary_objectives"] = primary_objectives - data["potential_objectives"] = potential_objectives - data["active_objectives"] = active_objectives - data["completed_final_objective"] = uplink_handler.final_objective var/list/stock_list = uplink_handler.item_stock.Copy() var/list/extra_purchasable_stock = list() @@ -269,7 +223,6 @@ var/list/data = list() data["uplink_flag"] = uplink_handler.uplink_flag data["has_progression"] = uplink_handler.has_progression - data["has_objectives"] = uplink_handler.has_objectives data["lockable"] = lockable data["assigned_role"] = uplink_handler.assigned_role data["assigned_species"] = uplink_handler.assigned_species @@ -312,47 +265,9 @@ if("renegotiate_objectives") uplink_handler.replace_objectives?.Invoke() SStgui.update_uis(src) - - if(!uplink_handler.has_objectives) - return TRUE - - if(uplink_handler.owner?.current != ui.user || !uplink_handler.can_take_objectives) - return TRUE - - switch(action) - if("regenerate_objectives") - uplink_handler.generate_objectives() - return TRUE - - var/list/objectives - switch(action) - if("start_objective") - objectives = uplink_handler.potential_objectives - if("objective_act", "finish_objective", "objective_abort") - objectives = uplink_handler.active_objectives - - if(!objectives) - return - - var/objective_index = round(text2num(params["index"])) - if(objective_index < 1 || objective_index > length(objectives)) - return TRUE - var/datum/traitor_objective/objective = objectives[objective_index] - - // Objective actions - switch(action) - if("start_objective") - uplink_handler.take_objective(ui.user, objective) - if("objective_act") - uplink_handler.ui_objective_act(ui.user, objective, params["objective_action"]) - if("finish_objective") - if(!objective.finish_objective(ui.user)) - return - uplink_handler.complete_objective(objective) - if("objective_abort") - uplink_handler.abort_objective(objective) return TRUE + /// Proc that locks uplinks /datum/component/uplink/proc/lock_uplink() active = FALSE @@ -498,17 +413,6 @@ return returnable_code -/// Proc that unlocks a locked replacement uplink when it hears the unlock code from their datum -/datum/component/uplink/proc/on_heard(datum/source, list/hearing_args) - SIGNAL_HANDLER - if(!locked) - return - if(!findtext(hearing_args[HEARING_RAW_MESSAGE], unlock_code)) - return - var/atom/replacement_uplink = parent - locked = FALSE - replacement_uplink.balloon_alert_to_viewers("beep", vision_distance = COMBAT_MESSAGE_RANGE) - /datum/component/uplink/proc/failsafe(atom/source) if(!parent) return diff --git a/code/datums/mind/_mind.dm b/code/datums/mind/_mind.dm index adbe02b5a5c0..4f662d1df97d 100644 --- a/code/datums/mind/_mind.dm +++ b/code/datums/mind/_mind.dm @@ -406,41 +406,6 @@ message_admins("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.") log_admin("[key_name(usr)] has unemag'ed [ai]'s Cyborgs.") - else if(href_list["edit_obj_tc"]) - var/datum/traitor_objective/objective = locate(href_list["edit_obj_tc"]) - if(!istype(objective)) - return - var/telecrystal = input("Set new telecrystal reward for [objective.name]","Syndicate uplink", objective.telecrystal_reward) as null | num - if(isnull(telecrystal)) - return - objective.telecrystal_reward = telecrystal - message_admins("[key_name_admin(usr)] changed [objective]'s telecrystal reward count to [telecrystal].") - log_admin("[key_name(usr)] changed [objective]'s telecrystal reward count to [telecrystal].") - else if(href_list["edit_obj_pr"]) - var/datum/traitor_objective/objective = locate(href_list["edit_obj_pr"]) - if(!istype(objective)) - return - var/progression = input("Set new progression reward for [objective.name]","Syndicate uplink", objective.progression_reward) as null | num - if(isnull(progression)) - return - objective.progression_reward = progression - message_admins("[key_name_admin(usr)] changed [objective]'s progression reward count to [progression].") - log_admin("[key_name(usr)] changed [objective]'s progression reward count to [progression].") - else if(href_list["fail_objective"]) - var/datum/traitor_objective/objective = locate(href_list["fail_objective"]) - if(!istype(objective)) - return - var/performed = objective.objective_state == OBJECTIVE_STATE_INACTIVE? "skipped" : "failed" - message_admins("[key_name_admin(usr)] forcefully [performed] [objective].") - log_admin("[key_name(usr)] forcefully [performed] [objective].") - objective.fail_objective() - else if(href_list["succeed_objective"]) - var/datum/traitor_objective/objective = locate(href_list["succeed_objective"]) - if(!istype(objective)) - return - message_admins("[key_name_admin(usr)] forcefully succeeded [objective].") - log_admin("[key_name(usr)] forcefully succeeded [objective].") - objective.succeed_objective() else if (href_list["common"]) switch(href_list["common"]) if("undress") @@ -471,26 +436,6 @@ uplink.uplink_handler.progression_points = progression message_admins("[key_name_admin(usr)] changed [current]'s progression point count to [progression].") log_admin("[key_name(usr)] changed [current]'s progression point count to [progression].") - uplink.uplink_handler.update_objectives() - uplink.uplink_handler.generate_objectives() - if("give_objective") - if(!check_rights(R_FUN)) - return - var/datum/component/uplink/uplink = find_syndicate_uplink() - if(!uplink || !uplink.uplink_handler) - return - var/list/all_objectives = subtypesof(/datum/traitor_objective) - var/objective_typepath = tgui_input_list(usr, "Select objective", "Select objective", all_objectives) - if(!objective_typepath) - return - var/datum/traitor_objective/objective = uplink.uplink_handler.try_add_objective(objective_typepath, force = TRUE) - if(objective) - message_admins("[key_name_admin(usr)] gave [current] a traitor objective ([objective_typepath]).") - log_admin("[key_name(usr)] gave [current] a traitor objective ([objective_typepath]).") - else - to_chat(usr, span_warning("Failed to generate the objective!")) - message_admins("[key_name_admin(usr)] failed to give [current] a traitor objective ([objective_typepath]).") - log_admin("[key_name(usr)] failed to give [current] a traitor objective ([objective_typepath]).") if("uplink") var/datum/antagonist/traitor/traitor_datum = has_antag_datum(/datum/antagonist/traitor) if(!give_uplink(antag_datum = traitor_datum || null)) diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index e0fea12a0a88..69a1a2349612 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -1,7 +1,55 @@ +GLOBAL_DATUM_INIT(steal_item_handler, /datum/objective_item_handler, new()) + /proc/add_item_to_steal(source, type) GLOB.steal_item_handler.objectives_by_path[type] += source return type +/// Holds references to information about all of the items you might need to steal for objectives +/datum/objective_item_handler + var/list/list/objectives_by_path + var/generated_items = FALSE + +/datum/objective_item_handler/New() + . = ..() + objectives_by_path = list() + for(var/datum/objective_item/item as anything in subtypesof(/datum/objective_item)) + objectives_by_path[initial(item.targetitem)] = list() + RegisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(save_items)) + RegisterSignal(SSdcs, COMSIG_GLOB_NEW_ITEM, PROC_REF(new_item_created)) + +/datum/objective_item_handler/proc/new_item_created(datum/source, obj/item/item) + SIGNAL_HANDLER + if(HAS_TRAIT(item, TRAIT_ITEM_OBJECTIVE_BLOCKED)) + return + if(!generated_items) + item.add_stealing_item_objective() + return + var/typepath = item.add_stealing_item_objective() + if(typepath != null) + register_item(item, typepath) + +/// Registers all items that are potentially stealable and removes ones that aren't. +/// We still need to do things this way because on mapload, items may not be on the station until everything has finished loading. +/datum/objective_item_handler/proc/save_items() + SIGNAL_HANDLER + for(var/obj/item/typepath as anything in objectives_by_path) + var/list/obj_by_path_cache = objectives_by_path[typepath].Copy() + for(var/obj/item/object as anything in obj_by_path_cache) + register_item(object, typepath) + generated_items = TRUE + +/datum/objective_item_handler/proc/register_item(atom/object, typepath) + var/turf/place = get_turf(object) + if(!place || !is_station_level(place.z)) + objectives_by_path[typepath] -= object + return + RegisterSignal(object, COMSIG_QDELETING, PROC_REF(remove_item)) + +/datum/objective_item_handler/proc/remove_item(atom/source) + SIGNAL_HANDLER + for(var/typepath in objectives_by_path) + objectives_by_path[typepath] -= source + //Contains the target item datums for Steal objectives. /datum/objective_item /// How the item is described in the objective diff --git a/code/game/objects/structures/syndicate_uplink_beacon.dm b/code/game/objects/structures/syndicate_uplink_beacon.dm deleted file mode 100644 index abeb7e0d0808..000000000000 --- a/code/game/objects/structures/syndicate_uplink_beacon.dm +++ /dev/null @@ -1,114 +0,0 @@ -/// Device that traitors can craft in order to be sent a new, undisguised uplink -/obj/structure/syndicate_uplink_beacon - name = "suspicious beacon" - icon = 'icons/obj/machines/telecomms.dmi' - icon_state = "relay_traitor" - desc = "This ramshackle device seems capable of recieving and sending signals for some nefarious purpose." - density = TRUE - anchored = TRUE - /// Traitor's code that they speak into the radio - var/uplink_code = "" - /// weakref to person who is going to use the beacon to get a replacement uplink - var/datum/weakref/owner - /// while constructed the teleport beacon is still active - var/obj/item/beacon/teleport_beacon - /// Radio that the device needs to listen to the codeword from the traitor - var/obj/item/radio/listening_radio - /// prevents traitor from activating teleport_beacon proc too much in a small period of time - COOLDOWN_DECLARE(beacon_cooldown) - -/obj/structure/syndicate_uplink_beacon/Initialize(mapload) - . = ..() - register_context() - - var/static/list/tool_behaviors = list( - TOOL_SCREWDRIVER = list( - SCREENTIP_CONTEXT_RMB = "Deconstruct", - ), - ) - AddElement(/datum/element/contextual_screentip_tools, tool_behaviors) - listening_radio = new(src) - listening_radio.canhear_range = 0 - teleport_beacon = new(src) - -/obj/structure/syndicate_uplink_beacon/attack_hand(mob/living/user, list/modifiers) - if(!IS_TRAITOR(user)) - balloon_alert(user, "don't know how to use!") - return - if(IS_WEAKREF_OF(owner, user)) - balloon_alert(user, "already synchronized to you!") - return - if(owner != null) - balloon_alert(user, "already claimed!") - return - var/datum/looping_sound/typing/typing_sounds = new(src, start_immediately = TRUE) - balloon_alert(user, "synchronizing...") - if(!do_after(user = user, delay = 3 SECONDS, target = src, interaction_key = REF(src), hidden = TRUE)) - typing_sounds.stop() - return - typing_sounds.stop() - probe_traitor(user) - -/obj/structure/syndicate_uplink_beacon/screwdriver_act_secondary(mob/living/user, obj/item/tool) - tool.play_tool_sound(src) - balloon_alert(user, "deconstructing...") - if (!do_after(user, 5 SECONDS, target = src, hidden = TRUE)) - return FALSE - var/turf/beacon_tile = get_turf(src) - new /obj/item/stack/sheet/iron/five(beacon_tile) - new /obj/item/stack/cable_coil/five(beacon_tile) - teleport_beacon.forceMove(beacon_tile) - teleport_beacon = null - qdel(src) - return TRUE - -/obj/structure/syndicate_uplink_beacon/Destroy() - QDEL_NULL(listening_radio) - if(teleport_beacon) - QDEL_NULL(teleport_beacon) - return ..() - -/// Proc reads the user, sets radio to the correct frequency and starts to listen for the replacement uplink code -/obj/structure/syndicate_uplink_beacon/proc/probe_traitor(mob/living/user) - owner = WEAKREF(user) - var/datum/antagonist/traitor/traitor_datum = user.mind.has_antag_datum(/datum/antagonist/traitor) - - uplink_code = traitor_datum.replacement_uplink_code - listening_radio.set_frequency(traitor_datum.replacement_uplink_frequency) - become_hearing_sensitive() - -/obj/structure/syndicate_uplink_beacon/Hear(atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods, message_range) - if(ismob(speaker) || radio_freq != listening_radio.get_frequency()) - return - if(!findtext(raw_message, uplink_code)) - return - teleport_uplink() - -/// Proc uses owners uplink handler to create replacement uplink and then lock or destroy their other uplinks -/obj/structure/syndicate_uplink_beacon/proc/teleport_uplink() - if(!COOLDOWN_FINISHED(src, beacon_cooldown)) - return - COOLDOWN_START(src, beacon_cooldown, 10 MINUTES) - - var/mob/living/resolved_owner = owner.resolve() - if(isnull(resolved_owner)) - return - - var/datum/antagonist/traitor/traitor_datum = resolved_owner.mind.has_antag_datum(/datum/antagonist/traitor) - if(isnull(traitor_datum)) - return - - var/datum/uplink_handler/uplink_handler = traitor_datum.uplink_handler - - SEND_SIGNAL(uplink_handler, COMSIG_UPLINK_HANDLER_REPLACEMENT_ORDERED) - new /obj/item/uplink/replacement(get_turf(src), /*owner = */resolved_owner, /*tc_amount = */0, /*uplink_handler_override = */uplink_handler) - flick("relay_traitor_activate", src) - do_sparks(number = 5, cardinal_only = FALSE, source = src) - log_traitor("[key_name(resolved_owner)] acquired a replacement uplink via the syndicate uplink beacon.") - -// Adds screentips -/obj/structure/syndicate_uplink_beacon/add_context(atom/source, list/context, obj/item/held_item, mob/user) - if(held_item || !IS_TRAITOR(user)) - return NONE - context[SCREENTIP_CONTEXT_LMB] = "Synchronize with beacon" - return CONTEXTUAL_SCREENTIP_SET diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm index b1ce37fdec26..edb3b0831f8a 100644 --- a/code/modules/admin/antag_panel.dm +++ b/code/modules/admin/antag_panel.dm @@ -193,14 +193,10 @@ GLOBAL_VAR(antag_prototypes) var/uplink_info = "Uplink:" var/datum/component/uplink/U = find_syndicate_uplink() if(U) - if(!U.uplink_handler.has_objectives) - uplink_info += "take" if (check_rights(R_FUN, 0)) uplink_info += ", [U.uplink_handler.telecrystals] TC" if(U.uplink_handler.has_progression) uplink_info += ", [U.uplink_handler.progression_points] PR" - if(U.uplink_handler.has_objectives) - uplink_info += ", Force Give Objective" else uplink_info += ", [U.uplink_handler.telecrystals] TC" if(U.uplink_handler.has_progression) diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index 366e957f1bbb..89b89829b0f3 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -638,7 +638,6 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w assign_admin_objective_and_antag(player, antag_datum) var/datum/uplink_handler/uplink = antag_datum.uplink_handler uplink.has_progression = FALSE - uplink.has_objectives = FALSE if(ROLE_CHANGELING) var/datum/antagonist/changeling/antag_datum = new antag_datum.give_objectives = keep_generic_objecives diff --git a/code/modules/antagonists/traitor/README.md b/code/modules/antagonists/traitor/README.md deleted file mode 100644 index 360d1bd54ebf..000000000000 --- a/code/modules/antagonists/traitor/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Progression Traitor Balance Guide - -This guide will explain how the values for progression traitor works, how to balance progression traitors and what you should NOT do when balancing. -This guide will only explain progression values. - -## Definitions - -- Progression points OR Progression - A currency that controls what uplink items a player can purchase and what objectives they have accessible to them. Gained passively or by completing objectives and has diminishing returns as it strays from the expected progression -- Expected Progression - A global value that increments by a value of 1 minute every minute, representing the 'time' that a player should be at if they had not completed any objectives. -- Objectives - An activity or job that a player can take for rewards such as TC and progression points. -- Player - The user(s) that are playing as the antagonist in this new system. -- Expected deviance - The amount of deviance that can be expected from the minimum and maximum progressions. Usually calculated by `progression_scaling_deviance` + `progression_scaling_deviance` * `global_progression_deviance_required` (explained further down) - -## How it works - -This section will explain how the entire balance system works. This is an overview of the entire system. - -### Progression - -Progression points is passively given to a player, and are represented as minutes (or time values) in code. The round has its own 'expected progression', which is the progression value that you'd normally have if you hadn't completed any objectives whatsoever. This is the baseline progression that all players will be at unless they're a latejoiner, and it acts as the basis for determining how much progression points a player should get over time and the cost of objectives for a specific player, if they deviate too much from this value. The idea is that they will slowly drift back towards the expected progression if they do nothing and it becomes harder for them to progress as they deviate further from the expected progression. The amount that is passively given can also vary depending on how many players there are, so that at lower populations, expected progression rises more slowly. - -### Objectives - -Objectives are worth a certain amount of progression points, determined by the code. However, this can be scaled to be less if the player taking them is ahead of the expected progression. This scales exponentially, so that as a player deviates further from the expected progression, the reward diminishes exponentially, up to a reduced value of 90%. The similar thing happens in the opposite direction, with people who are lower than the expected progression getting more progression than usual for completing objectives. - -## How to balance - -### The traitor subsystem -- `newjoin_progression_coeff` - The coefficient multiplied by the expected progression for new joining traitors to calculate their starting progression, so that they don't start from scratch -- `progression_scaling_deviance` - The value that the entire system revolves around. This determines how your disadvantages are calculated, if you stray from the expected progression. Having a progression value that is `progression_scaling_deviance` minutes off of the expected progression means that you won't get any progression at all, and if objectives were configured to suit this, you'd have the highest reduction you can possibly get. From the expected progression to this value, it scales linearly and it also works in the opposite direction. -- `current_progression_scaling` - Defined at compile time, this determines how fast expected progression scales. So if you have it set to 0.5 MINUTES, it'll take twice as long to unlock uplink items and new objectives. -- `CONFIG:TRAITOR_IDEAL_PLAYER_COUNT` - The ideal player count before expected progression stops increasing. If the living player list gets below this value, the current progression scaling will be multiplied by player_count/traitor_ideal_player_count. In essence, this makes it so that progression scales more slowly when there isn't a lot of people alive. - -If you want to balance how fast the system progresses, you should look at modifying `current_progression_scaling`. If you want to balance how far someone should be allowed to deviate, you should look at modifying `progression-scaling-deviance` - -### Objectives -- `progression_minimum` - The minimum number of progression points required before this objective can show up as a potential objective -- `progression_maximum` - The maximum number of progression points before this objective stops showing up as a potential objective, used to prevent roundstart objectives from showing up during the late game. -- `progression_reward` - The progression reward you get from completing an objective. This is the base value, and can also be a two element list of numbers if you want it to be random. This value is then scaled depending on whether a player is ahead or behind the expected progression -- `global_progression_influence_intensity` - Determines how influential expected progression will affect the progression reward of this objective. Set to 0 to disable. -- `global_progression_deviance_required` - Determines how much deviance is required before the scaling kicks in, to give objectives more leeway so that at the `progression_scaling_deviance`, it doesn't scale to 90% immediately. -- `progression_cost_coeff_deviance` - This determines the randomness of the progression reward, to prevent all of the scaling from looking the same. Becomes a lot less significant as the scaling variable gets closer to 1. - -If you want to balance the expected timeframe an objective should be available, you should look at changing the `progression_minimum` or `progression_maximum`. If you want to balance how much objectives reward, you may want to look at modifying `progression_reward`. If you want to look at balancing the cost of an objective depending on the expected progression, you may want to look at `global_progression_influence_intensity`. If you want to look at decreasing or increasing the deviance allowed before objectives become worthless progression-wise, you may want to look at modifying `global_progression_deviance_required` - -### Uplink Items -- `progression_minimum` - The minimum number of progression points required to purchase this uplink item. - -## What NOT to do when balancing - -### Overcompensate - -You do not want to overcompensate variables such as `progression_minimum` and `progression_maximum`. Such values need to be an accurate representation of roughly around the time a player should unlock the objective or uplink item. progression_scaling_deviance is supposed to represents the limit that a casual player can be at before it becomes significantly harder for them to progress throughout. You should expect people to be within `progression_scaling_deviance` + `progression_scaling_deviance` * `global_progression_deviance_required`. (Assuming `progression_scaling_deviance` is 20 minutes and `progression_scaling_deviance_required` is 0.5, 20 + 0.5 * 20 = 30; this gives us a value of 30 minutes). This is the expected deviance. - -### Reward large amounts of progression points - -Progression points are passively gained, so rewarding large amounts of progression points will let people bypass the scaling as they'll immediately jump to an absurd value. A good rule of thumb is to always keep the reward within or below the expected deviance. - diff --git a/code/modules/antagonists/traitor/balance_helper.dm b/code/modules/antagonists/traitor/balance_helper.dm deleted file mode 100644 index b2a9661bfeb5..000000000000 --- a/code/modules/antagonists/traitor/balance_helper.dm +++ /dev/null @@ -1,109 +0,0 @@ -ADMIN_VERB(debug_traitor_objectives, R_DEBUG, "Debug Traitor Objectives", "Verify functionality of traitor goals.", ADMIN_CATEGORY_DEBUG) - SStraitor.traitor_debug_panel?.ui_interact(user.mob) - -/datum/traitor_objective_debug - var/list/all_objectives - -/datum/traitor_objective_debug/New(datum/traitor_category_handler/category_handler) - . = ..() - all_objectives = list() - for(var/datum/traitor_objective_category/category as anything in category_handler.all_categories) - var/list/generated_list = list() - var/list/current_list = category.objectives - for(var/value in category.objectives) - if(islist(value)) - generated_list += list(list( - "objectives" = recursive_list_generate(value), - "weight" = current_list[value] - )) - else - generated_list += list(generate_objective_data(value, current_list[value])) - all_objectives += list(list( - "name" = category.name, - "objectives" = generated_list, - "weight" = category.weight, - )) - -/datum/traitor_objective_debug/proc/recursive_list_generate(list/to_check) - var/list/generated_list = list() - for(var/value in to_check) - if(islist(value)) - generated_list += list(list( - "objectives" = recursive_list_generate(value), - "weight" = to_check[value] - )) - else - generated_list += list(generate_objective_data(value, to_check[value])) - return generated_list - -/datum/traitor_objective_debug/proc/generate_objective_data(datum/traitor_objective/objective_type, weight) - // Need to set this to false before we create the new objective to prevent init from fucking it up - SStraitor.generate_objectives = FALSE - var/datum/traitor_objective/objective = new objective_type() - var/list/return_data = list( - "name" = objective.name, - "description" = objective.description, - "progression_minimum" = objective.progression_minimum, - "progression_maximum" = objective.progression_maximum, - "global_progression" = objective.global_progression_deviance_required, - "global_progression_limit_coeff" = objective.global_progression_limit_coeff, - "global_progression_influence_intensity" = objective.global_progression_influence_intensity, - "progression_reward" = objective.progression_reward, - "telecrystal_reward" = objective.telecrystal_reward, - "telecrystal_penalty" = objective.telecrystal_penalty, - "weight" = weight, - "type" = objective.type, - ) - qdel(objective) - SStraitor.generate_objectives = TRUE - return return_data - -/datum/traitor_objective_debug/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "TraitorObjectiveDebug") - ui.open() - -/datum/traitor_objective_debug/ui_data(mob/user) - var/list/data = list() - data["current_progression"] = SStraitor.current_global_progression - var/list/handlers = SStraitor.uplink_handlers - var/list/handler_data = list() - for(var/datum/uplink_handler/handler as anything in handlers) - var/total_progression_from_objectives = 0 - for(var/datum/traitor_objective/objective as anything in handler.completed_objectives) - if(objective.objective_state != OBJECTIVE_STATE_COMPLETED) - continue - total_progression_from_objectives += objective.progression_reward - handler_data += list(list( - "player" = handler.owner?.key, - "progression_points" = handler.progression_points, - "total_progression_from_objectives" = total_progression_from_objectives - )) - data["player_data"] = handler_data - return data - -/datum/traitor_objective_debug/ui_static_data(mob/user) - var/list/data = list() - data["objective_data"] = all_objectives - data["progression_scaling_deviance"] = SStraitor.progression_scaling_deviance - return data - -/datum/traitor_objective_debug/ui_state(mob/user) - return GLOB.admin_state - -/datum/traitor_objective_debug/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if(.) - return - - switch(action) - if("set_current_expected_progression") - SStraitor.current_global_progression = text2num(params["new_expected_progression"]) - return TRUE - if("generate_json") - var/temp_file = file("data/TraitorObjectiveDownloadTempFile") - fdel(temp_file) - WRITE_FILE(temp_file, all_objectives) - DIRECT_OUTPUT(ui.user, ftp(temp_file, "TraitorObjectiveData.json")) - return TRUE diff --git a/code/modules/antagonists/traitor/components/traitor_objective_helpers.dm b/code/modules/antagonists/traitor/components/traitor_objective_helpers.dm deleted file mode 100644 index 6bf5ce5d117a..000000000000 --- a/code/modules/antagonists/traitor/components/traitor_objective_helpers.dm +++ /dev/null @@ -1,54 +0,0 @@ -/// Helper component that registers signals on an object -/// This is not necessary to use and gives little control over the conditions -/datum/component/traitor_objective_register - dupe_mode = COMPONENT_DUPE_ALLOWED - - /// The target to apply the succeed/fail signals onto - var/datum/target - /// Signals to listen out for to automatically succeed the objective - var/succeed_signals - /// Signals to listen out for to automatically fail the objective. - var/fail_signals - /// Whether failing has a penalty - var/penalty = 0 - -/datum/component/traitor_objective_register/Initialize(datum/target, succeed_signals, fail_signals, penalty) - . = ..() - if(!istype(parent, /datum/traitor_objective)) - return COMPONENT_INCOMPATIBLE - src.target = target - src.succeed_signals = succeed_signals - src.fail_signals = fail_signals - src.penalty = penalty - -/datum/component/traitor_objective_register/RegisterWithParent() - if(succeed_signals) - RegisterSignals(target, succeed_signals, PROC_REF(on_success)) - if(fail_signals) - RegisterSignals(target, fail_signals, PROC_REF(on_fail)) - RegisterSignals(parent, list(COMSIG_TRAITOR_OBJECTIVE_COMPLETED, COMSIG_TRAITOR_OBJECTIVE_FAILED), PROC_REF(delete_self)) - -/datum/component/traitor_objective_register/UnregisterFromParent() - if(target) - if(succeed_signals) - UnregisterSignal(target, succeed_signals) - if(fail_signals) - UnregisterSignal(target, fail_signals) - UnregisterSignal(parent, list( - COMSIG_TRAITOR_OBJECTIVE_COMPLETED, - COMSIG_TRAITOR_OBJECTIVE_FAILED - )) - -/datum/component/traitor_objective_register/proc/on_fail(datum/traitor_objective/source) - SIGNAL_HANDLER - var/datum/traitor_objective/objective = parent - objective.fail_objective(penalty) - -/datum/component/traitor_objective_register/proc/on_success() - SIGNAL_HANDLER - var/datum/traitor_objective/objective = parent - objective.succeed_objective() - -/datum/component/traitor_objective_register/proc/delete_self() - SIGNAL_HANDLER - qdel(src) diff --git a/code/modules/antagonists/traitor/components/traitor_objective_limit_per_time.dm b/code/modules/antagonists/traitor/components/traitor_objective_limit_per_time.dm deleted file mode 100644 index ce72e1227de0..000000000000 --- a/code/modules/antagonists/traitor/components/traitor_objective_limit_per_time.dm +++ /dev/null @@ -1,41 +0,0 @@ -/// Helper component to track events on -/datum/component/traitor_objective_limit_per_time - dupe_mode = COMPONENT_DUPE_HIGHLANDER - - /// The maximum time that an objective will be considered for. Set to -1 to accept any time. - var/time_period = 0 - /// The maximum amount of objectives that can be active or recently active at one time - var/maximum_objectives = 0 - /// The typepath which we check for - var/typepath - -/datum/component/traitor_objective_limit_per_time/Initialize(typepath, time_period, maximum_objectives) - . = ..() - if(!istype(parent, /datum/traitor_objective)) - return COMPONENT_INCOMPATIBLE - src.time_period = time_period - src.maximum_objectives = maximum_objectives - src.typepath = typepath - if(!typepath) - src.typepath = parent.type - -/datum/component/traitor_objective_limit_per_time/RegisterWithParent() - RegisterSignal(parent, COMSIG_TRAITOR_OBJECTIVE_PRE_GENERATE, PROC_REF(handle_generate)) - -/datum/component/traitor_objective_limit_per_time/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_TRAITOR_OBJECTIVE_PRE_GENERATE) - - -/datum/component/traitor_objective_limit_per_time/proc/handle_generate(datum/traitor_objective/source, datum/mind/owner, list/potential_duplicates) - SIGNAL_HANDLER - var/datum/uplink_handler/handler = source.handler - if(!handler) - return - var/count = 0 - for(var/datum/traitor_objective/objective as anything in handler.potential_duplicate_objectives[typepath]) - if(time_period != -1 && objective.objective_state != OBJECTIVE_STATE_INACTIVE && (world.time - objective.time_of_completion) > time_period) - continue - count++ - - if(count >= maximum_objectives) - return COMPONENT_TRAITOR_OBJECTIVE_ABORT_GENERATION diff --git a/code/modules/antagonists/traitor/components/traitor_objective_mind_tracker.dm b/code/modules/antagonists/traitor/components/traitor_objective_mind_tracker.dm deleted file mode 100644 index 01d1febb9ac7..000000000000 --- a/code/modules/antagonists/traitor/components/traitor_objective_mind_tracker.dm +++ /dev/null @@ -1,40 +0,0 @@ -/// Helper component to track events on -/datum/component/traitor_objective_mind_tracker - dupe_mode = COMPONENT_DUPE_ALLOWED - - /// The target to track - var/datum/mind/target - /// Signals to listen out for mapped to procs to call - var/list/signals - /// Current registered target - var/mob/current_registered_target - -/datum/component/traitor_objective_mind_tracker/Initialize(datum/target, signals) - . = ..() - if(!istype(parent, /datum/traitor_objective)) - return COMPONENT_INCOMPATIBLE - src.target = target - src.signals = signals - -/datum/component/traitor_objective_mind_tracker/RegisterWithParent() - RegisterSignal(target, COMSIG_MIND_TRANSFERRED, PROC_REF(handle_mind_transferred)) - RegisterSignal(target, COMSIG_QDELETING, PROC_REF(delete_self)) - RegisterSignals(parent, list(COMSIG_TRAITOR_OBJECTIVE_COMPLETED, COMSIG_TRAITOR_OBJECTIVE_FAILED), PROC_REF(delete_self)) - handle_mind_transferred(target) - -/datum/component/traitor_objective_mind_tracker/UnregisterFromParent() - UnregisterSignal(target, COMSIG_MIND_TRANSFERRED) - if(target.current) - parent.UnregisterSignal(target.current, signals) - -/datum/component/traitor_objective_mind_tracker/proc/handle_mind_transferred(datum/source, mob/previous_body) - SIGNAL_HANDLER - if(current_registered_target) - parent.UnregisterSignal(current_registered_target, signals) - - for(var/signal in signals) - parent.RegisterSignal(target.current, signal, signals[signal]) - -/datum/component/traitor_objective_mind_tracker/proc/delete_self() - SIGNAL_HANDLER - qdel(src) diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index c545d3dae532..119c54133042 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -22,15 +22,9 @@ var/uplink_flag_given = UPLINK_TRAITORS var/give_objectives = TRUE - /// Whether to give secondary objectives to the traitor, which aren't necessary but can be completed for a progression and TC boost. - var/give_secondary_objectives = TRUE var/should_give_codewords = TRUE ///give this traitor an uplink? var/give_uplink = TRUE - /// Code that allows traitor to get a replacement uplink - var/replacement_uplink_code = "" - /// Radio frequency that traitor must speak on to get a replacement uplink - var/replacement_uplink_frequency = "" ///if TRUE, this traitor will always get hijacking as their final objective var/is_hijacker = FALSE @@ -51,17 +45,6 @@ ///the final objective the traitor has to accomplish, be it escaping, hijacking, or just martyrdom. var/datum/objective/ending_objective -/datum/antagonist/traitor/infiltrator - // Used to denote traitors who have joined midround and therefore have no access to secondary objectives. - // Progression elements are best left to the roundstart antagonists - // There will still be a timelock on uplink items - name = "\improper Infiltrator" - give_secondary_objectives = FALSE - uplink_flag_given = UPLINK_TRAITORS | UPLINK_INFILTRATORS - -/datum/antagonist/traitor/infiltrator/sleeper_agent - name = "\improper Syndicate Sleeper Agent" - /datum/antagonist/traitor/New(give_objectives = TRUE) . = ..() src.give_objectives = give_objectives @@ -83,7 +66,6 @@ if(give_uplink) owner.give_uplink(silent = TRUE, antag_datum = src) - generate_replacement_codes() var/datum/component/uplink/uplink = owner.find_syndicate_uplink() uplink_ref = WEAKREF(uplink) @@ -97,10 +79,6 @@ uplink_handler.has_progression = TRUE SStraitor.register_uplink_handler(uplink_handler) - if(give_secondary_objectives) - uplink_handler.has_objectives = TRUE - uplink_handler.generate_objectives() - uplink_handler.can_replace_objectives = CALLBACK(src, PROC_REF(can_change_objectives)) uplink_handler.replace_objectives = CALLBACK(src, PROC_REF(submit_player_objective)) @@ -124,81 +102,21 @@ pick_employer() - owner.teach_crafting_recipe(/datum/crafting_recipe/syndicate_uplink_beacon) - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) - return ..() */ /datum/antagonist/traitor/on_removal() if(!isnull(uplink_handler)) - uplink_handler.has_objectives = FALSE uplink_handler.can_replace_objectives = null uplink_handler.replace_objectives = null + owner.take_uplink() + owner.special_role = null return ..() -/datum/antagonist/traitor/proc/traitor_objective_to_html(datum/traitor_objective/to_display) - var/string = "[to_display.name]" - if(to_display.objective_state == OBJECTIVE_STATE_ACTIVE || to_display.objective_state == OBJECTIVE_STATE_INACTIVE) - string += " [to_display.telecrystal_reward] TC" - string += " [to_display.progression_reward] PR" - else - string += ", [to_display.telecrystal_reward] TC" - string += ", [to_display.progression_reward] PR" - if(to_display.objective_state == OBJECTIVE_STATE_ACTIVE && !istype(to_display, /datum/traitor_objective/ultimate)) - string += " Fail this objective" - string += " Succeed this objective" - if(to_display.objective_state == OBJECTIVE_STATE_INACTIVE) - string += " Dispose of this objective" - - if(to_display.skipped) - string += " - Skipped" - else if(to_display.objective_state == OBJECTIVE_STATE_FAILED) - string += " - Failed" - else if(to_display.objective_state == OBJECTIVE_STATE_INVALID) - string += " - Invalidated" - else if(to_display.objective_state == OBJECTIVE_STATE_COMPLETED) - string += " - Succeeded" - - return string - -/datum/antagonist/traitor/antag_panel_objectives() - var/result = ..() - if(!uplink_handler) - return result - result += "Traitor specific objectives
" - result += "Concluded Objectives:
" - for(var/datum/traitor_objective/objective as anything in uplink_handler.completed_objectives) - result += "[traitor_objective_to_html(objective)]
" - if(!length(uplink_handler.completed_objectives)) - result += "EMPTY
" - result += "Ongoing Objectives:
" - for(var/datum/traitor_objective/objective as anything in uplink_handler.active_objectives) - result += "[traitor_objective_to_html(objective)]
" - if(!length(uplink_handler.active_objectives)) - result += "EMPTY
" - result += "Potential Objectives:
" - for(var/datum/traitor_objective/objective as anything in uplink_handler.potential_objectives) - result += "[traitor_objective_to_html(objective)]
" - if(!length(uplink_handler.potential_objectives)) - result += "EMPTY
" - result += "Force add objective
" - return result - /// Returns true if we're allowed to assign ourselves a new objective /datum/antagonist/traitor/proc/can_change_objectives() return can_assign_self_objectives -/// proc that generates the traitors replacement uplink code and radio frequency -/datum/antagonist/traitor/proc/generate_replacement_codes() - replacement_uplink_code = "[pick(GLOB.phonetic_alphabet)] [rand(10,99)]" - replacement_uplink_frequency = sanitize_frequency(rand(MIN_UNUSED_FREQ, MAX_FREQ), free = FALSE, syndie = FALSE) - -/datum/antagonist/traitor/on_removal() - owner.special_role = null - owner.forget_crafting_recipe(/datum/crafting_recipe/syndicate_uplink_beacon) - return ..() - /datum/antagonist/traitor/proc/pick_employer() var/faction = prob(75) ? FLAVOR_FACTION_SYNDICATE : FLAVOR_FACTION_NANOTRASEN var/list/possible_employers = list() @@ -313,8 +231,6 @@ data["theme"] = traitor_flavor["ui_theme"] data["code"] = uplink?.unlock_code data["failsafe_code"] = uplink?.failsafe_code - data["replacement_code"] = replacement_uplink_code - data["replacement_frequency"] = format_frequency(replacement_uplink_frequency) data["intro"] = traitor_flavor["introduction"] data["allies"] = traitor_flavor["allies"] data["goal"] = traitor_flavor["goal"] @@ -352,9 +268,6 @@ traitor_won = FALSE objectives_text += "
Objective #[count]: [objective.explanation_text] [objective.get_roundend_success_suffix()]" count++ - if(uplink_handler.final_objective) - objectives_text += "
[span_greentext("[traitor_won ? "Additionally" : "However"], the final objective \"[uplink_handler.final_objective]\" was completed!")]" - traitor_won = TRUE result += "
[owner.name] [traitor_flavor["roundend_report"]]" diff --git a/code/modules/antagonists/traitor/objective_category.dm b/code/modules/antagonists/traitor/objective_category.dm deleted file mode 100644 index 5484d2937985..000000000000 --- a/code/modules/antagonists/traitor/objective_category.dm +++ /dev/null @@ -1,68 +0,0 @@ -/// The traitor category handler. This is where the probability of all objectives are managed. -/datum/traitor_category_handler - var/list/datum/traitor_objective_category/all_categories = list() - -/datum/traitor_category_handler/New() - . = ..() - for(var/type in subtypesof(/datum/traitor_objective_category)) - var/datum/traitor_objective_category/category = new type() - if(length(category.objectives)) - all_categories += category - else - // Category should just get autoGC'd here if they don't have any length, this may not be necessary - qdel(category) - -/datum/traitor_category_handler/proc/objective_valid(datum/traitor_objective/objective_path, progression_points) - if(initial(objective_path.abstract_type) == objective_path) - return FALSE - if(progression_points < initial(objective_path.progression_minimum)) - return FALSE - if(progression_points > initial(objective_path.progression_maximum)) - return FALSE - return TRUE - -/datum/traitor_category_handler/proc/get_possible_objectives(progression_points) - var/list/valid_objectives = list() - for(var/datum/traitor_objective_category/category as anything in all_categories) - var/list/category_list = list() - for(var/value in category.objectives) - if(islist(value)) - var/list/objective_category = filter_invalid_objective_list(value, progression_points) - if(!length(objective_category)) - continue - category_list[objective_category] = category.objectives[value] - else - if(!objective_valid(value, progression_points)) - continue - category_list[value] = category.objectives[value] - if(!length(category_list)) - continue - valid_objectives[category_list] = category.weight - - return valid_objectives - -/datum/traitor_category_handler/proc/filter_invalid_objective_list(list/objectives, progression_points) - var/list/filtered_objectives = list() - for(var/value in objectives) - if(islist(value)) - var/list/result = filter_invalid_objective_list(value, progression_points) - if(!length(result)) - continue - filtered_objectives[result] = objectives[value] - else - if(!objective_valid(value, progression_points)) - continue - filtered_objectives[value] = objectives[value] - return filtered_objectives - -/// The objective category. -/// Used to group up entire objectives into 1 weight objects to prevent having a -/// higher chance of getting an objective due to an increased number of different objective subtypes. -/// These are nothing but informational holders and will have no other purpose. -/datum/traitor_objective_category - /// Name of the category, unused but may help in the future - var/name = "generic category" - /// Assoc list of objectives by type mapped to their weight. Can also contain lists of objectives mapped to weight - var/list/objectives = list() - /// The weight of the category. How likely this category is to be chosen. - var/weight = OBJECTIVE_WEIGHT_DEFAULT diff --git a/code/modules/antagonists/traitor/objectives/abstract/target_player.dm b/code/modules/antagonists/traitor/objectives/abstract/target_player.dm deleted file mode 100644 index c08f8cd46ee3..000000000000 --- a/code/modules/antagonists/traitor/objectives/abstract/target_player.dm +++ /dev/null @@ -1,33 +0,0 @@ -/** - * The point of this datum is to act as a means to group target player objectives - * Not all 'target player' objectives have to be under this subtype, it's only used if you don't want duplicates among the current - * children types under this type. - */ -/datum/traitor_objective/target_player - abstract_type = /datum/traitor_objective/target_player - - progression_minimum = 30 MINUTES - - // The code below is for limiting how often you can get this objective. You will get this objective at a maximum of maximum_objectives_in_period every objective_period - /// The objective period at which we consider if it is an 'objective'. Set to 0 to accept all objectives. - var/objective_period = 15 MINUTES - /// The maximum number of objectives we can get within this period. - var/maximum_objectives_in_period = 4 - - /// The target that we need to target. - var/mob/living/target - -/datum/traitor_objective/target_player/Destroy(force) - set_target(null) - return ..() - -/datum/traitor_objective/target_player/proc/set_target(mob/living/new_target) - if(target) - UnregisterSignal(target, COMSIG_QDELETING) - target = new_target - if(target) - RegisterSignal(target, COMSIG_QDELETING, PROC_REF(target_deleted)) - -/datum/traitor_objective/target_player/proc/target_deleted(datum/source) - SIGNAL_HANDLER - set_target(null) diff --git a/code/modules/antagonists/traitor/objectives/assassination.dm b/code/modules/antagonists/traitor/objectives/assassination.dm deleted file mode 100644 index 5d5cdbd1981f..000000000000 --- a/code/modules/antagonists/traitor/objectives/assassination.dm +++ /dev/null @@ -1,205 +0,0 @@ -/datum/traitor_objective_category/assassinate_kidnap - name = "Assassination/Kidnap" - objectives = list( - list( - /datum/traitor_objective/target_player/assassinate/calling_card = 1, - /datum/traitor_objective/target_player/assassinate/calling_card/heads_of_staff = 1, - ) = 1, - list( - list( - /datum/traitor_objective/target_player/kidnapping/common = 20, - /datum/traitor_objective/target_player/kidnapping/common/assistant = 1, - ) = 4, - /datum/traitor_objective/target_player/kidnapping/uncommon = 3, - /datum/traitor_objective/target_player/kidnapping/rare = 2, - /datum/traitor_objective/target_player/kidnapping/captain = 1 - ) = 1, - ) - -/datum/traitor_objective/target_player/assassinate - name = "Assassinate %TARGET% the %JOB TITLE%" - description = "Simply kill your target to accomplish this objective." - - abstract_type = /datum/traitor_objective/target_player/assassinate - - progression_minimum = 30 MINUTES - - /** - * Makes the objective only set heads as targets when true, and block them from being targets when false. - * This also blocks the objective from generating UNTIL the un-heads_of_staff version (WHICH SHOULD BE A DIRECT PARENT) is completed. - * example: calling card objective, you kill someone, you unlock the chance to roll a head of staff target version of calling card. - */ - var/heads_of_staff = FALSE - - duplicate_type = /datum/traitor_objective/target_player - -/datum/traitor_objective/target_player/assassinate/supported_configuration_changes() - . = ..() - . += NAMEOF(src, objective_period) - . += NAMEOF(src, maximum_objectives_in_period) - -/datum/traitor_objective/target_player/assassinate/calling_card - name = "Assassinate %TARGET% the %JOB TITLE%, and plant a calling card" - description = "Kill your target and plant a calling card in the pockets of your victim. If your calling card gets destroyed before you are able to plant it, this objective will fail." - progression_reward = 2 MINUTES - telecrystal_reward = list(1, 2) - - var/obj/item/paper/calling_card/card - -/datum/traitor_objective/target_player/assassinate/calling_card/heads_of_staff - progression_reward = 4 MINUTES - telecrystal_reward = list(2, 3) - - heads_of_staff = TRUE - -/datum/traitor_objective/target_player/assassinate/calling_card/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!card) - buttons += add_ui_button("", "Pressing this will materialize a calling card, which you must plant to succeed.", "paper-plane", "summon_card") - return buttons - -/datum/traitor_objective/target_player/assassinate/calling_card/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("summon_card") - if(card) - return - card = new(user.drop_location()) - user.put_in_hands(card) - card.balloon_alert(user, "the card materializes in your hand") - RegisterSignal(card, COMSIG_ITEM_EQUIPPED, PROC_REF(on_card_planted)) - AddComponent(/datum/component/traitor_objective_register, card, \ - succeed_signals = null, \ - fail_signals = list(COMSIG_QDELETING), \ - penalty = TRUE) - -/datum/traitor_objective/target_player/assassinate/calling_card/proc/on_card_planted(datum/source, mob/living/equipper, slot) - SIGNAL_HANDLER - if(equipper != target) - return //your target please - if(equipper.stat != DEAD) - return //kill them please - if(!(slot & (ITEM_SLOT_LPOCKET|ITEM_SLOT_RPOCKET))) - return //in their pockets please - succeed_objective() - -/datum/traitor_objective/target_player/assassinate/calling_card/ungenerate_objective() - . = ..() //unsets kill target - if(card) - UnregisterSignal(card, COMSIG_ITEM_EQUIPPED) - card = null - -/datum/traitor_objective/target_player/assassinate/calling_card/target_deleted() - //you cannot plant anything on someone who is gone gone, so even if this happens after you're still liable to fail - fail_objective(penalty_cost = telecrystal_penalty) - -/datum/traitor_objective/target_player/assassinate/New(datum/uplink_handler/handler) - . = ..() - AddComponent(/datum/component/traitor_objective_limit_per_time, \ - /datum/traitor_objective/target_player, \ - time_period = objective_period, \ - maximum_objectives = maximum_objectives_in_period \ - ) - -/datum/traitor_objective/target_player/assassinate/generate_objective(datum/mind/generating_for, list/possible_duplicates) - - var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things - for(var/datum/objective/task as anything in handler.primary_objectives) - if(!istype(task.target, /datum/mind)) - continue - already_targeting += task.target //Removing primary objective kill targets from the list - - var/parent_type = type2parent(type) - //don't roll head of staff types if you haven't completed the normal version - if(heads_of_staff && !handler.get_completion_count(parent_type)) - // Locked if they don't have any of the risky bug room objective completed - return FALSE - - var/list/possible_targets = list() - var/try_target_late_joiners = FALSE - if(generating_for.late_joiner) - try_target_late_joiners = TRUE - for(var/datum/mind/possible_target as anything in get_crewmember_minds()) - if(possible_target in already_targeting) - continue - var/target_area = get_area(possible_target.current) - if(possible_target == generating_for) - continue - if(!ishuman(possible_target.current)) - continue - if(possible_target.current.stat == DEAD) - continue - var/datum/antagonist/traitor/traitor = possible_target.has_antag_datum(/datum/antagonist/traitor) - if(traitor && traitor.uplink_handler.telecrystals >= 0) - continue - if(!HAS_TRAIT(SSstation, STATION_TRAIT_LATE_ARRIVALS) && istype(target_area, /area/shuttle/arrival)) - continue - //removes heads of staff from being targets from non heads of staff assassinations, and vice versa - if(heads_of_staff) - if(!(possible_target.assigned_role.job_flags & JOB_HEAD_OF_STAFF)) - continue - else - if((possible_target.assigned_role.job_flags & JOB_HEAD_OF_STAFF)) - continue - possible_targets += possible_target - for(var/datum/traitor_objective/target_player/objective as anything in possible_duplicates) - possible_targets -= objective.target - if(try_target_late_joiners) - var/list/all_possible_targets = possible_targets.Copy() - for(var/datum/mind/possible_target as anything in all_possible_targets) - if(!possible_target.late_joiner) - possible_targets -= possible_target - if(!possible_targets.len) - possible_targets = all_possible_targets - special_target_filter(possible_targets) - if(!possible_targets.len) - return FALSE //MISSION FAILED, WE'LL GET EM NEXT TIME - - var/datum/mind/target_mind = pick(possible_targets) - set_target(target_mind.current) - replace_in_name("%TARGET%", target.real_name) - replace_in_name("%JOB TITLE%", target_mind.assigned_role.title) - RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_target_death)) - return TRUE - -/datum/traitor_objective/target_player/assassinate/ungenerate_objective() - UnregisterSignal(target, COMSIG_LIVING_DEATH) - set_target(null) - -///proc for checking for special states that invalidate a target -/datum/traitor_objective/target_player/assassinate/proc/special_target_filter(list/possible_targets) - return - -/datum/traitor_objective/target_player/assassinate/target_deleted() - if(objective_state == OBJECTIVE_STATE_INACTIVE) - //don't take an objective target of someone who is already obliterated - fail_objective() - return ..() - -/datum/traitor_objective/target_player/assassinate/proc/on_target_death() - SIGNAL_HANDLER - if(objective_state == OBJECTIVE_STATE_INACTIVE) - //don't take an objective target of someone who is already dead - fail_objective() - -/obj/item/paper/calling_card - name = "calling card" - icon_state = "syndicate_calling_card" - color = "#ff5050" - show_written_words = FALSE - default_raw_text = {" - **Death to Nanotrasen.**

- - Only through the inviolable cooperation of corporations known as The Syndicate, can Nanotrasen and its autocratic tyrants be silenced. - The outcries of Nanotrasen's employees are squelched by the suffocating iron grip of their leaders. If you read this, and understand - why we fight, then you need only to look where Nanotrasen doesn't want you to find us to join our cause. Any number of our companies - may be fighting with your interests in mind.

- - SELF: They fight for the protection and freedom of silicon life all across the galaxy.

- - Tiger Cooperative: They fight for religious freedom and their righteous concoctions.

- - Waffle Corporation: They fight for the return of healthy corporate competition, snuffed out by Nanotrasen's monopoly.

- - Animal Rights Consortium: They fight for nature and the right for all biological life to exist. - "} diff --git a/code/modules/antagonists/traitor/objectives/demoralise_assault.dm b/code/modules/antagonists/traitor/objectives/demoralise_assault.dm deleted file mode 100644 index fe26864e4fc1..000000000000 --- a/code/modules/antagonists/traitor/objectives/demoralise_assault.dm +++ /dev/null @@ -1,129 +0,0 @@ -/datum/traitor_objective_category/demoralise - name = "Demoralise Crew" - objectives = list( - /datum/traitor_objective/target_player/assault = 1, - /datum/traitor_objective/destroy_item/demoralise = 1, - ) - weight = OBJECTIVE_WEIGHT_UNLIKELY - -/datum/traitor_objective/target_player/assault - name = "Assault %TARGET% the %JOB TITLE%" - description = "%TARGET% has been identified as a potential future agent. \ - Pick a fight and give them a good beating. \ - %COUNT% hits should reduce their morale and have them questioning their loyalties. \ - Try not to kill them just yet, we may want to recruit them in the future." - - abstract_type = /datum/traitor_objective/target_player - duplicate_type = /datum/traitor_objective/target_player - - progression_minimum = 0 MINUTES - progression_maximum = 30 MINUTES - progression_reward = list(4 MINUTES, 8 MINUTES) - telecrystal_reward = list(0, 1) - - /// Min attacks required to pass the objective. Picked at random between this and max. - var/min_attacks_required = 2 - /// Max attacks required to pass the objective. Picked at random between this and min. - var/max_attacks_required = 5 - /// The random number picked for the number of required attacks to pass this objective. - var/attacks_required = 0 - /// Total number of successful attacks recorded. - var/attacks_inflicted = 0 - -/datum/traitor_objective/target_player/assault/on_objective_taken(mob/user) - . = ..() - - target.AddElement(/datum/element/relay_attackers) - RegisterSignal(target, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) - -/datum/traitor_objective/target_player/assault/proc/on_attacked(mob/source, mob/living/attacker, attack_flags) - SIGNAL_HANDLER - - // Only care about attacks from the objective's owner. - if(attacker != handler.owner.current) - return - - // We want some sort of damaging attack to trigger this, rather than shoves and non-lethals. - if(!(attack_flags & ATTACKER_DAMAGING_ATTACK)) - return - - attacks_inflicted++ - - if(attacks_inflicted == attacks_required) - succeed_objective() - -/datum/traitor_objective/target_player/assault/ungenerate_objective() - UnregisterSignal(target, COMSIG_ATOM_WAS_ATTACKED) - UnregisterSignal(target, COMSIG_LIVING_DEATH) - set_target(null) - -/datum/traitor_objective/target_player/assault/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things - for(var/datum/objective/task as anything in handler.primary_objectives) - if(!istype(task.target, /datum/mind)) - continue - already_targeting += task.target //Removing primary objective kill targets from the list - - var/list/possible_targets = list() - - for(var/datum/mind/possible_target as anything in get_crewmember_minds()) - if(possible_target in already_targeting) - continue - - if(possible_target == generating_for) - continue - - if(!ishuman(possible_target.current)) - continue - - if(possible_target.current.stat == DEAD) - continue - - if(possible_target.has_antag_datum(/datum/antagonist/traitor)) - continue - - possible_targets += possible_target - - for(var/datum/traitor_objective/target_player/objective as anything in possible_duplicates) - possible_targets -= objective.target?.mind - - if(generating_for.late_joiner) - var/list/all_possible_targets = possible_targets.Copy() - for(var/datum/mind/possible_target as anything in all_possible_targets) - if(!possible_target.late_joiner) - possible_targets -= possible_target - if(!possible_targets.len) - possible_targets = all_possible_targets - - if(!possible_targets.len) - return FALSE - - var/datum/mind/target_mind = pick(possible_targets) - - set_target(target_mind.current) - replace_in_name("%TARGET%", target.real_name) - replace_in_name("%JOB TITLE%", target_mind.assigned_role.title) - - attacks_required = rand(min_attacks_required, max_attacks_required) - replace_in_name("%COUNT%", attacks_required) - - RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_target_death)) - - return TRUE - -/datum/traitor_objective/target_player/assault/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(attacks_required > attacks_inflicted) - buttons += add_ui_button("[attacks_required - attacks_inflicted]", "This tells you how many more times you have to attack the target player to succeed.", "hand-rock-o", "none") - return buttons - -/datum/traitor_objective/target_player/assault/target_deleted() - //don't take an objective target of someone who is already obliterated - fail_objective() - return ..() - -/datum/traitor_objective/target_player/assault/proc/on_target_death() - SIGNAL_HANDLER - - //don't take an objective target of someone who is already dead - fail_objective() diff --git a/code/modules/antagonists/traitor/objectives/destroy_heirloom.dm b/code/modules/antagonists/traitor/objectives/destroy_heirloom.dm deleted file mode 100644 index 0d62ecece5f2..000000000000 --- a/code/modules/antagonists/traitor/objectives/destroy_heirloom.dm +++ /dev/null @@ -1,151 +0,0 @@ -/datum/traitor_objective_category/destroy_heirloom - name = "Destroy Heirloom" - objectives = list( - list( - // There's about 16 jobs in common, so assistant has a 1/21 chance of getting chosen. - /datum/traitor_objective/destroy_heirloom/common = 20, - /datum/traitor_objective/destroy_heirloom/common/assistant = 1, - ) = 4, - /datum/traitor_objective/destroy_heirloom/uncommon = 3, - /datum/traitor_objective/destroy_heirloom/rare = 2, - /datum/traitor_objective/destroy_heirloom/captain = 1 - ) - -/datum/traitor_objective/destroy_heirloom - name = "Destroy %ITEM%, the family heirloom that belongs to %TARGET% the %JOB TITLE%" - description = "%TARGET% has been on our shitlist for a while and we want to show them we mean business. Find their %ITEM% and destroy it." - - abstract_type = /datum/traitor_objective/destroy_heirloom - - /// The jobs that this objective is targeting. - var/list/target_jobs - /// the item we need to destroy - var/obj/item/target_item - /// the owner of the item we need to destroy - var/datum/mind/target_mind - - // The code below is for limiting how often you can get this objective. You will get this objective at a maximum of maximum_objectives_in_period every objective_period - /// The objective period at which we consider if it is an 'objective'. Set to 0 to accept all objectives. - var/objective_period = 10 MINUTES - /// The maximum number of objectives that can be taken in this period. - var/maximum_objectives_in_period = 2 - - duplicate_type = /datum/traitor_objective/destroy_heirloom - -/datum/traitor_objective/destroy_heirloom/common - /// 30 minutes in, syndicate won't care about common heirlooms anymore - progression_minimum = 0 MINUTES - progression_maximum = 30 MINUTES - progression_reward = list(8 MINUTES, 12 MINUTES) - telecrystal_reward = list(1, 2) - target_jobs = list( - // Medical - /datum/job/doctor, - /datum/job/virologist, - /datum/job/paramedic, - /datum/job/psychologist, - /datum/job/chemist, - // Service - /datum/job/clown, - /datum/job/botanist, - /datum/job/janitor, - /datum/job/mime, - /datum/job/lawyer, - // Cargo - /datum/job/cargo_technician, - // Science - /datum/job/geneticist, - /datum/job/scientist, - /datum/job/roboticist, - // Engineering - /datum/job/station_engineer, - /datum/job/atmospheric_technician, - ) - -/// This is only for assistants, because the syndies are a lot less likely to give a shit about what an assistant does, so they're a lot less likely to appear -/datum/traitor_objective/destroy_heirloom/common/assistant - target_jobs = list( - /datum/job/assistant - ) - -/datum/traitor_objective/destroy_heirloom/uncommon - /// 45 minutes in, syndicate won't care about uncommon heirlooms anymore - progression_minimum = 0 MINUTES - progression_maximum = 45 MINUTES - progression_reward = list(8 MINUTES, 12 MINUTES) - telecrystal_reward = list(1, 2) - target_jobs = list( - // Cargo - /datum/job/shaft_miner, - // Service - /datum/job/chaplain, - /datum/job/bartender, - /datum/job/cook, - /datum/job/curator, - ) - -/datum/traitor_objective/destroy_heirloom/rare - progression_minimum = 15 MINUTES - /// 60 minutes in, syndicate won't care about rare heirlooms anymore - progression_maximum = 60 MINUTES - progression_reward = list(10 MINUTES, 14 MINUTES) - telecrystal_reward = list(2, 3) - target_jobs = list( - // Security - /datum/job/security_officer, - /datum/job/warden, - /datum/job/detective, - // Heads of staff - /datum/job/head_of_personnel, - /datum/job/chief_medical_officer, - /datum/job/research_director, - /datum/job/quartermaster, - ) - -/datum/traitor_objective/destroy_heirloom/captain - progression_minimum = 30 MINUTES - progression_reward = list(10 MINUTES, 14 MINUTES) - telecrystal_reward = 4 - target_jobs = list( - /datum/job/head_of_security, - /datum/job/captain - ) - -/datum/traitor_objective/destroy_heirloom/New(datum/uplink_handler/handler) - . = ..() - AddComponent(/datum/component/traitor_objective_limit_per_time, \ - /datum/traitor_objective/destroy_heirloom, \ - time_period = objective_period, \ - maximum_objectives = maximum_objectives_in_period \ - ) - -/datum/traitor_objective/destroy_heirloom/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/list/possible_targets = list() - for(var/datum/mind/possible_target as anything in get_crewmember_minds()) - if(possible_target == generating_for) - continue - if(!ishuman(possible_target.current)) - continue - var/datum/quirk/item_quirk/family_heirloom/quirk = locate() in possible_target.current.quirks - if(!quirk || !quirk.heirloom.resolve()) - continue - if(!(possible_target.assigned_role.type in target_jobs)) - continue - possible_targets += possible_target - for(var/datum/traitor_objective/destroy_heirloom/objective as anything in possible_duplicates) - possible_targets -= objective.target_mind - if(!length(possible_targets)) - return FALSE - target_mind = pick(possible_targets) - AddComponent(/datum/component/traitor_objective_register, target_mind.current, fail_signals = list(COMSIG_QDELETING)) - var/datum/quirk/item_quirk/family_heirloom/quirk = locate() in target_mind.current.quirks - target_item = quirk.heirloom.resolve() - AddComponent(/datum/component/traitor_objective_register, target_item, succeed_signals = list(COMSIG_QDELETING)) - replace_in_name("%TARGET%", target_mind.name) - replace_in_name("%JOB TITLE%", target_mind.assigned_role.title) - replace_in_name("%ITEM%", target_item.name) - return TRUE - -/datum/traitor_objective/destroy_heirloom/ungenerate_objective() - target_item = null - target_mind = null diff --git a/code/modules/antagonists/traitor/objectives/destroy_item.dm b/code/modules/antagonists/traitor/objectives/destroy_item.dm deleted file mode 100644 index ec247eac9edf..000000000000 --- a/code/modules/antagonists/traitor/objectives/destroy_item.dm +++ /dev/null @@ -1,109 +0,0 @@ -/datum/traitor_objective/destroy_item - name = "Steal %ITEM% and destroy it" - description = "Find %ITEM% and destroy it using any means necessary. We can't allow the crew to have %ITEM% as it conflicts with our interests." - - var/list/possible_items = list() - /// The current target item that we are stealing. - var/datum/objective_item/steal/target_item - /// Any special equipment that may be needed - var/list/special_equipment - /// Items that are currently tracked and will succeed this objective when destroyed. - var/list/tracked_items = list() - - abstract_type = /datum/traitor_objective/destroy_item - -/datum/traitor_objective/destroy_item/low_risk - progression_minimum = 10 MINUTES - progression_maximum = 35 MINUTES - progression_reward = list(5 MINUTES, 10 MINUTES) - telecrystal_reward = 1 - - possible_items = list( - /datum/objective_item/steal/traitor/bartender_shotgun, - /datum/objective_item/steal/traitor/fireaxe, - /datum/objective_item/steal/traitor/nullrod, - /datum/objective_item/steal/traitor/big_crowbar, - ) - -/datum/traitor_objective/destroy_item/very_risky - progression_minimum = 40 MINUTES - progression_reward = 15 MINUTES - telecrystal_reward = list(6, 9) - - possible_items = list( - /datum/objective_item/steal/blackbox, - ) - -/// Super early-game destroy objective intended to be items easily tided that the crew tends to value. -/datum/traitor_objective/destroy_item/demoralise - description = "Find %ITEM% and destroy it using any means necessary. \ - We believe this luxury item is important for crew morale. \ - Destruction of this item will help our recruitment efforts." - - progression_minimum = 0 MINUTES - progression_maximum = 10 MINUTES - progression_reward = list(4 MINUTES, 8 MINUTES) - telecrystal_reward = list(0, 1) - - possible_items = list( - /datum/objective_item/steal/traitor/rpd, - /datum/objective_item/steal/traitor/space_law, - /datum/objective_item/steal/traitor/granted_stamp, - /datum/objective_item/steal/traitor/denied_stamp, - /datum/objective_item/steal/traitor/lizard_plush, - /datum/objective_item/steal/traitor/moth_plush, - /datum/objective_item/steal/traitor/insuls, - ) - -/datum/traitor_objective/destroy_item/generate_objective(datum/mind/generating_for, list/possible_duplicates) - for(var/datum/traitor_objective/destroy_item/objective as anything in possible_duplicates) - possible_items -= objective.target_item.type - while(length(possible_items)) - var/datum/objective_item/steal/target = pick_n_take(possible_items) - target = new target() - if(!target.valid_objective_for(list(generating_for), require_owner = TRUE)) - qdel(target) - continue - target_item = target - break - if(!target_item) - return FALSE - if(target_item.exists_on_map) - var/list/items = GLOB.steal_item_handler.objectives_by_path[target_item.targetitem] - for(var/obj/item/item as anything in items) - AddComponent(/datum/component/traitor_objective_register, item, succeed_signals = list(COMSIG_QDELETING)) - tracked_items += item - if(length(target_item.special_equipment)) - special_equipment = target_item.special_equipment - replace_in_name("%ITEM%", target_item.name) - AddComponent(/datum/component/traitor_objective_mind_tracker, generating_for, \ - signals = list(COMSIG_MOB_EQUIPPED_ITEM = PROC_REF(on_item_pickup))) - return TRUE - -/datum/traitor_objective/destroy_item/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(special_equipment) - buttons += add_ui_button("", "Pressing this will summon any extra special equipment you may need for the mission.", "tools", "summon_gear") - return buttons - -/datum/traitor_objective/destroy_item/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("summon_gear") - if(!special_equipment) - return - for(var/item in special_equipment) - var/obj/item/new_item = new item(user.drop_location()) - user.put_in_hands(new_item) - user.balloon_alert(user, "the equipment materializes in your hand") - special_equipment = null - -/datum/traitor_objective/destroy_item/proc/on_item_pickup(datum/source, obj/item/item, slot) - SIGNAL_HANDLER - if(istype(item, target_item.targetitem) && !(item in tracked_items)) - AddComponent(/datum/component/traitor_objective_register, item, succeed_signals = list(COMSIG_QDELETING)) - tracked_items += item - -/datum/traitor_objective/destroy_item/ungenerate_objective() - tracked_items.Cut() - return ..() diff --git a/code/modules/antagonists/traitor/objectives/eyesnatching.dm b/code/modules/antagonists/traitor/objectives/eyesnatching.dm deleted file mode 100644 index 1b59b4cd48d6..000000000000 --- a/code/modules/antagonists/traitor/objectives/eyesnatching.dm +++ /dev/null @@ -1,240 +0,0 @@ -/datum/traitor_objective_category/eyesnatching - name = "Eyesnatching" - objectives = list( - /datum/traitor_objective/target_player/eyesnatching = 1, - /datum/traitor_objective/target_player/eyesnatching/heads = 1, - ) - weight = OBJECTIVE_WEIGHT_UNLIKELY - -/datum/traitor_objective/target_player/eyesnatching - name = "Steal the eyes of %TARGET% the %JOB TITLE%" - description = "%TARGET% messed with the wrong people. Steal their eyes to teach them a lesson. You will be provided an experimental eyesnatcher device to aid you in your mission." - - progression_minimum = 10 MINUTES - - progression_reward = list(4 MINUTES, 8 MINUTES) - telecrystal_reward = list(1, 2) - - /// If we're targeting heads of staff or not - var/heads_of_staff = FALSE - /// Have we already spawned an eyesnatcher - var/spawned_eyesnatcher = FALSE - - duplicate_type = /datum/traitor_objective/target_player - -/datum/traitor_objective/target_player/eyesnatching/supported_configuration_changes() - . = ..() - . += NAMEOF(src, objective_period) - . += NAMEOF(src, maximum_objectives_in_period) - -/datum/traitor_objective/target_player/eyesnatching/New(datum/uplink_handler/handler) - . = ..() - AddComponent(/datum/component/traitor_objective_limit_per_time, \ - /datum/traitor_objective/target_player, \ - time_period = objective_period, \ - maximum_objectives = maximum_objectives_in_period \ - ) - -/datum/traitor_objective/target_player/eyesnatching/heads - progression_reward = list(6 MINUTES, 12 MINUTES) - telecrystal_reward = list(2, 3) - - heads_of_staff = TRUE - -/datum/traitor_objective/target_player/eyesnatching/generate_objective(datum/mind/generating_for, list/possible_duplicates) - - var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things - for(var/datum/objective/task as anything in handler.primary_objectives) - if(!istype(task.target, /datum/mind)) - continue - already_targeting += task.target //Removing primary objective kill targets from the list - - var/list/possible_targets = list() - var/try_target_late_joiners = FALSE - if(generating_for.late_joiner) - try_target_late_joiners = TRUE - - for(var/datum/mind/possible_target as anything in get_crewmember_minds()) - if(possible_target == generating_for) - continue - - if(possible_target in already_targeting) - continue - - if(!ishuman(possible_target.current)) - continue - - if(possible_target.current.stat == DEAD) - continue - - if(possible_target.has_antag_datum(/datum/antagonist/traitor)) - continue - - if(!possible_target.assigned_role) - continue - - if(heads_of_staff) - if(!(possible_target.assigned_role.job_flags & JOB_HEAD_OF_STAFF)) - continue - else - if(possible_target.assigned_role.job_flags & JOB_HEAD_OF_STAFF) - continue - - var/mob/living/carbon/human/targets_current = possible_target.current - if(!targets_current.get_organ_by_type(/obj/item/organ/eyes)) - continue - - possible_targets += possible_target - - for(var/datum/traitor_objective/target_player/objective as anything in possible_duplicates) - possible_targets -= objective.target?.mind - - if(try_target_late_joiners) - var/list/all_possible_targets = possible_targets.Copy() - for(var/datum/mind/possible_target as anything in all_possible_targets) - if(!possible_target.late_joiner) - possible_targets -= possible_target - - if(!possible_targets.len) - possible_targets = all_possible_targets - - if(!possible_targets.len) - return FALSE //MISSION FAILED, WE'LL GET EM NEXT TIME - - var/datum/mind/target_mind = pick(possible_targets) - set_target(target_mind.current) - - replace_in_name("%TARGET%", target_mind.name) - replace_in_name("%JOB TITLE%", target_mind.assigned_role.title) - RegisterSignal(target, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(check_eye_removal)) - AddComponent(/datum/component/traitor_objective_register, target, fail_signals = list(COMSIG_QDELETING)) - return TRUE - -/datum/traitor_objective/target_player/eyesnatching/proc/check_eye_removal(datum/source, obj/item/organ/eyes/removed) - SIGNAL_HANDLER - - if(!istype(removed)) - return - - succeed_objective() - -/datum/traitor_objective/target_player/eyesnatching/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!spawned_eyesnatcher) - buttons += add_ui_button("", "Pressing this will materialize an eyesnatcher, which can be used on incapacitaded or restrained targets to forcefully remove their eyes.", "syringe", "eyesnatcher") - return buttons - -/datum/traitor_objective/target_player/eyesnatching/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("eyesnatcher") - if(spawned_eyesnatcher) - return - spawned_eyesnatcher = TRUE - var/obj/item/eyesnatcher/eyesnatcher = new(user.drop_location()) - user.put_in_hands(eyesnatcher) - eyesnatcher.balloon_alert(user, "the snatcher materializes in your hand") - -/obj/item/eyesnatcher - name = "portable eyeball extractor" - desc = "An overly complicated device that can pierce target's skull and extract their eyeballs if enough brute force is applied." - icon = 'icons/obj/medical/surgery_tools.dmi' - icon_state = "eyesnatcher" - base_icon_state = "eyesnatcher" - inhand_icon_state = "hypo" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - ///Whether it's been used to steal a pair of eyes already. - var/used = FALSE - -/obj/item/eyesnatcher/update_icon_state() - . = ..() - icon_state = "[base_icon_state][used ? "-used" : ""]" - -/obj/item/eyesnatcher/attack(mob/living/carbon/human/target, mob/living/user, params) - if(used || !istype(target) || !target.Adjacent(user)) //Works only once, no TK use - return ..() - - var/obj/item/organ/eyes/eyeballies = target.get_organ_slot(ORGAN_SLOT_EYES) - var/obj/item/bodypart/head/head = target.get_bodypart(BODY_ZONE_HEAD) - - if(!head || !eyeballies || target.is_eyes_covered()) - return ..() - var/eye_snatch_enthusiasm = 5 SECONDS - if(HAS_MIND_TRAIT(user, TRAIT_MORBID)) - eye_snatch_enthusiasm *= 0.7 - user.do_attack_animation(target, used_item = src) - target.visible_message( - span_warning("[user] presses [src] against [target]'s skull!"), - span_userdanger("[user] presses [src] against your skull!")) - if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) - return - - to_chat(target, span_userdanger("You feel something forcing its way into your skull!")) - balloon_alert(user, "applying pressure...") - if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) - return - - var/min_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = src) - var/max_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = src) - - target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(min_wound, max_wound + 10), attacking_item = src) - target.visible_message( - span_danger("[src] pierces through [target]'s skull, horribly mutilating their eyes!"), - span_userdanger("Something penetrates your skull, horribly mutilating your eyes! Holy fuck!"), - span_hear("You hear a sickening sound of metal piercing flesh!") - ) - eyeballies.apply_organ_damage(eyeballies.maxHealth) - target.emote("scream") - playsound(target, 'sound/effects/wounds/crackandbleed.ogg', 100) - log_combat(user, target, "cracked the skull of (eye snatching)", src) - - if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) - return - - if(!target.is_blind()) - to_chat(target, span_userdanger("You suddenly go blind!")) - if(prob(1)) - to_chat(target, span_notice("At least you got a new pirate-y look out of it...")) - var/obj/item/clothing/glasses/eyepatch/new_patch = new(target.loc) - target.equip_to_slot_if_possible(new_patch, ITEM_SLOT_EYES, disable_warning = TRUE) - - to_chat(user, span_notice("You successfully extract [target]'s eyeballs.")) - playsound(target, 'sound/surgery/retractor2.ogg', 100, TRUE) - playsound(target, 'sound/effects/pop.ogg', 100, TRAIT_MUTE) - eyeballies.Remove(target) - eyeballies.forceMove(get_turf(target)) - notify_ghosts( - "[target] has just had their eyes snatched!", - source = target, - header = "Ouch!", - ) - target.emote("scream") - if(prob(20)) - target.emote("cry") - used = TRUE - update_appearance(UPDATE_ICON) - -/obj/item/eyesnatcher/examine(mob/user) - . = ..() - if(used) - . += span_notice("It has been used up.") - -/obj/item/eyesnatcher/proc/eyeballs_exist(obj/item/organ/eyes/eyeballies, obj/item/bodypart/head/head, mob/living/carbon/human/target) - if(!eyeballies || QDELETED(eyeballies)) - return FALSE - if(!head || QDELETED(head)) - return FALSE - - if(eyeballies.owner != target) - return FALSE - var/obj/item/organ/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) - //got different eyes or doesn't own the head... somehow - if(head.owner != target || eyes != eyeballies) - return FALSE - - return TRUE diff --git a/code/modules/antagonists/traitor/objectives/final_objective/battlecruiser.dm b/code/modules/antagonists/traitor/objectives/final_objective/battlecruiser.dm deleted file mode 100644 index 7da84f600261..000000000000 --- a/code/modules/antagonists/traitor/objectives/final_objective/battlecruiser.dm +++ /dev/null @@ -1,49 +0,0 @@ -/datum/traitor_objective/ultimate/battlecruiser - name = "Reveal Station Coordinates to nearby Syndicate Battlecruiser" - description = "Use a special upload card on a communications console to send the coordinates \ - of the station to a nearby Battlecruiser. You may want to make your syndicate status known to \ - the battlecruiser crew when they arrive - their goal will be to destroy the station." - - /// Checks whether we have sent the card to the traitor yet. - var/sent_accesscard = FALSE - /// Battlecruiser team that we get assigned to - var/datum/team/battlecruiser/team - -/datum/traitor_objective/ultimate/battlecruiser/generate_objective(datum/mind/generating_for, list/possible_duplicates) - // There's no empty space to load a battlecruiser in... - if(SSmapping.is_planetary()) - return FALSE - - return TRUE - -/datum/traitor_objective/ultimate/battlecruiser/on_objective_taken(mob/user) - . = ..() - team = new() - var/obj/machinery/nuclearbomb/selfdestruct/nuke = locate() in SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/selfdestruct) - if(nuke.r_code == NUKE_CODE_UNSET) - nuke.r_code = random_nukecode() - team.nuke = nuke - team.update_objectives() - handler.owner.add_antag_datum(/datum/antagonist/battlecruiser/ally, team) - - -/datum/traitor_objective/ultimate/battlecruiser/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!sent_accesscard) - buttons += add_ui_button("", "Pressing this will materialize an upload card, which you can use on a communication console to contact the fleet.", "phone", "card") - return buttons - -/datum/traitor_objective/ultimate/battlecruiser/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("card") - if(sent_accesscard) - return - sent_accesscard = TRUE - var/obj/item/card/emag/battlecruiser/emag_card = new() - emag_card.team = team - podspawn(list( - "target" = get_turf(user), - "style" = /datum/pod_style/syndicate, - "spawn" = emag_card, - )) diff --git a/code/modules/antagonists/traitor/objectives/final_objective/final_objective.dm b/code/modules/antagonists/traitor/objectives/final_objective/final_objective.dm deleted file mode 100644 index 47b9b3b01674..000000000000 --- a/code/modules/antagonists/traitor/objectives/final_objective/final_objective.dm +++ /dev/null @@ -1,42 +0,0 @@ -/datum/traitor_objective_category/final_objective - name = "Final Objective" - objectives = list( - /datum/traitor_objective/ultimate/romerol = 1, - /datum/traitor_objective/ultimate/battlecruiser = 1, - /datum/traitor_objective/ultimate/space_dragon = 1, - /datum/traitor_objective/ultimate/supermatter_cascade = 1, - /datum/traitor_objective/ultimate/infect_ai = 1, - /datum/traitor_objective/ultimate/dark_matteor = 1, - ) - weight = 100 - -/datum/traitor_objective/ultimate - abstract_type = /datum/traitor_objective/ultimate - progression_minimum = 140 MINUTES - needs_reward = FALSE - - var/progression_points_in_objectives = 20 MINUTES - -/// Determines if this final objective can be taken. Should be put into every final objective's generate function. -/datum/traitor_objective/ultimate/can_generate_objective(generating_for, list/possible_duplicates) - if(handler.get_completion_progression(/datum/traitor_objective) < progression_points_in_objectives) - return FALSE - if(SStraitor.get_taken_count(type) > 0) // Prevents multiple people from ever getting the same final objective. - return FALSE - if(length(possible_duplicates) > 0) - return FALSE - return TRUE - -/datum/traitor_objective/ultimate/on_objective_taken(mob/user) - . = ..() - handler.maximum_potential_objectives = 0 - for(var/datum/traitor_objective/objective as anything in handler.potential_objectives) - if(objective == src) - continue - objective.fail_objective() - user.playsound_local(get_turf(user), 'sound/traitor/final_objective.ogg', vol = 100, vary = FALSE, channel = CHANNEL_TRAITOR) - handler.final_objective = name - -/datum/traitor_objective/ultimate/uplink_ui_data(mob/user) - . = ..() - .["final_objective"] = TRUE diff --git a/code/modules/antagonists/traitor/objectives/final_objective/infect_ai.dm b/code/modules/antagonists/traitor/objectives/final_objective/infect_ai.dm deleted file mode 100644 index d47a2c6aabc5..000000000000 --- a/code/modules/antagonists/traitor/objectives/final_objective/infect_ai.dm +++ /dev/null @@ -1,56 +0,0 @@ -/datum/traitor_objective/ultimate/infect_ai - name = "Infect the station AI with an experimental virus." - description = "Infect the station AI with an experimental virus. Go to %AREA% to receive an infected law upload board \ - and use it on the AI core or a law upload console." - - ///area type the objective owner must be in to receive the law upload module - var/area/board_area_pickup - ///checker on whether we have sent the law upload module - var/sent_board = FALSE - -/datum/traitor_objective/ultimate/infect_ai/can_generate_objective(generating_for, list/possible_duplicates) - . = ..() - if(!.) - return FALSE - - for(var/mob/living/silicon/ai/ai in GLOB.ai_list) - if(ai.stat == DEAD || ai.mind?.has_antag_datum(/datum/antagonist/malf_ai) || !is_station_level(ai.z)) - continue - return TRUE - - return FALSE - -/datum/traitor_objective/ultimate/infect_ai/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/list/possible_areas = GLOB.the_station_areas.Copy() - for(var/area/possible_area as anything in possible_areas) - //remove areas too close to the destination, too obvious for our poor shmuck, or just unfair - if(istype(possible_area, /area/station/hallway) || istype(possible_area, /area/station/security)) - possible_areas -= possible_area - if(!length(possible_areas)) - return FALSE - board_area_pickup = pick(possible_areas) - replace_in_name("%AREA%", initial(board_area_pickup.name)) - return TRUE - -/datum/traitor_objective/ultimate/infect_ai/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!sent_board) - buttons += add_ui_button("", "Pressing this will call down a pod with an infected law upload board.", "wifi", "upload_board") - return buttons - -/datum/traitor_objective/ultimate/infect_ai/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("upload_board") - if(sent_board) - return - var/area/delivery_area = get_area(user) - if(delivery_area.type != board_area_pickup) - to_chat(user, span_warning("You must be in [initial(board_area_pickup.name)] to receive the infected law upload board.")) - return - sent_board = TRUE - podspawn(list( - "target" = get_turf(user), - "style" = /datum/pod_style/syndicate, - "spawn" = /obj/item/ai_module/malf, - )) diff --git a/code/modules/antagonists/traitor/objectives/final_objective/objective_dark_matteor.dm b/code/modules/antagonists/traitor/objectives/final_objective/objective_dark_matteor.dm deleted file mode 100644 index b90fdba73d03..000000000000 --- a/code/modules/antagonists/traitor/objectives/final_objective/objective_dark_matteor.dm +++ /dev/null @@ -1,83 +0,0 @@ -/datum/traitor_objective/ultimate/dark_matteor - name = "Summon a dark matter singularity to consume the station." - description = "Go to %AREA%, and receive the smuggled satellites + emag. Set up and emag the satellites, \ - after enough have been recalibrated by the emag, IT COMES. Warning: The dark matter singularity will hunt all creatures, you included." - - //this is a prototype so this progression is for all basic level kill objectives - - ///area type the objective owner must be in to receive the satellites - var/area/satellites_spawnarea_type - ///checker on whether we have sent the satellites yet. - var/sent_satellites = FALSE - -/datum/traitor_objective/ultimate/dark_matteor/can_generate_objective(generating_for, list/possible_duplicates) - . = ..() - if(!.) - return FALSE - if(SSmapping.is_planetary()) - return FALSE //meteors can't spawn on planets - return TRUE - -/datum/traitor_objective/ultimate/dark_matteor/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/list/possible_areas = GLOB.the_station_areas.Copy() - for(var/area/possible_area as anything in possible_areas) - if(!ispath(possible_area, /area/station/maintenance/solars) && !ispath(possible_area, /area/station/solars)) - possible_areas -= possible_area - if(length(possible_areas) == 0) - return FALSE - satellites_spawnarea_type = pick(possible_areas) - replace_in_name("%AREA%", initial(satellites_spawnarea_type.name)) - return TRUE - -/datum/traitor_objective/ultimate/dark_matteor/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!sent_satellites) - buttons += add_ui_button("", "Pressing this will call down a pod with the smuggled satellites.", "satellite", "satellite") - return buttons - -/datum/traitor_objective/ultimate/dark_matteor/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("satellite") - if(sent_satellites) - return - var/area/delivery_area = get_area(user) - if(delivery_area.type != satellites_spawnarea_type) - to_chat(user, span_warning("You must be in [initial(satellites_spawnarea_type.name)] to receive the smuggled satellites.")) - return - sent_satellites = TRUE - podspawn(list( - "target" = get_turf(user), - "style" = /datum/pod_style/syndicate, - "spawn" = /obj/structure/closet/crate/engineering/smuggled_meteor_shields, - )) - -/obj/structure/closet/crate/engineering/smuggled_meteor_shields - -/obj/structure/closet/crate/engineering/smuggled_meteor_shields/PopulateContents() - ..() - for(var/i in 1 to 11) - new /obj/machinery/satellite/meteor_shield(src) - new /obj/item/card/emag/meteor_shield_recalibrator(src) - new /obj/item/paper/dark_matteor_summoning(src) - -/obj/item/paper/dark_matteor_summoning - name = "notes - dark matter meteor summoning" - default_raw_text = {" - Summoning a dark matter meteor.
-
-
- Operative, this crate contains 10+1 spare meteor shield satellites stolen from NT’s supply lines. Your mission is to - deploy them in space near the station and recalibrate them with the provided emag. Be careful: you need a 30 second - cooldown between each hack, and NT will detect your interference after seven recalibrations. That means you - have at least 5 minutes of work and 1 minute of resistance.
-
- This is a high-risk operation. You’ll need backup, fortification, and determination. The reward? - A spectacular dark matter singularity that will wipe out the station.
-
- **Death to Nanotrasen.** -"} - -/obj/item/card/emag/meteor_shield_recalibrator - name = "cryptographic satellite recalibrator" - desc = "It's a cryptographic sequencer that has been tuned to recalibrate meteor shields quicker and with less risk of frying them." diff --git a/code/modules/antagonists/traitor/objectives/final_objective/romerol.dm b/code/modules/antagonists/traitor/objectives/final_objective/romerol.dm deleted file mode 100644 index 09edc4b0c739..000000000000 --- a/code/modules/antagonists/traitor/objectives/final_objective/romerol.dm +++ /dev/null @@ -1,46 +0,0 @@ -/datum/traitor_objective/ultimate/romerol - name = "Spread the experimental bioterror agent Romerol by calling a droppod down at %AREA%" - description = "Go to %AREA%, and receive the bioterror agent. Spread it to the crew, \ - and watch then raise from the dead as mindless killing machines. Warning: The undead will attack you too." - - //this is a prototype so this progression is for all basic level kill objectives - - ///area type the objective owner must be in to receive the romerol - var/area/romerol_spawnarea_type - ///checker on whether we have sent the romerol yet. - var/sent_romerol = FALSE - -/datum/traitor_objective/ultimate/romerol/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/list/possible_areas = GLOB.the_station_areas.Copy() - for(var/area/possible_area as anything in possible_areas) - //remove areas too close to the destination, too obvious for our poor shmuck, or just unfair - if(ispath(possible_area, /area/station/hallway) || ispath(possible_area, /area/station/security)) - possible_areas -= possible_area - if(length(possible_areas) == 0) - return FALSE - romerol_spawnarea_type = pick(possible_areas) - replace_in_name("%AREA%", initial(romerol_spawnarea_type.name)) - return TRUE - -/datum/traitor_objective/ultimate/romerol/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!sent_romerol) - buttons += add_ui_button("", "Pressing this will call down a pod with the biohazard kit.", "biohazard", "romerol") - return buttons - -/datum/traitor_objective/ultimate/romerol/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("romerol") - if(sent_romerol) - return - var/area/delivery_area = get_area(user) - if(delivery_area.type != romerol_spawnarea_type) - to_chat(user, span_warning("You must be in [initial(romerol_spawnarea_type.name)] to receive the bioterror agent.")) - return - sent_romerol = TRUE - podspawn(list( - "target" = get_turf(user), - "style" = /datum/pod_style/syndicate, - "spawn" = /obj/item/storage/box/syndie_kit/romerol, - )) diff --git a/code/modules/antagonists/traitor/objectives/final_objective/supermatter_cascade.dm b/code/modules/antagonists/traitor/objectives/final_objective/supermatter_cascade.dm deleted file mode 100644 index 2e9396c90b07..000000000000 --- a/code/modules/antagonists/traitor/objectives/final_objective/supermatter_cascade.dm +++ /dev/null @@ -1,57 +0,0 @@ -/datum/traitor_objective/ultimate/supermatter_cascade - name = "Destroy the station by causing a crystallizing resonance cascade" - description = "Destroy the station by causing a supermatter cascade. Go to %AREA% to retrieve the destabilizing crystal \ - and use it on the supermatter." - - ///area type the objective owner must be in to receive the destabilizing crystal - var/area/dest_crystal_area_pickup - ///checker on whether we have sent the crystal yet. - var/sent_crystal = FALSE - -/datum/traitor_objective/ultimate/supermatter_cascade/can_generate_objective(generating_for, list/possible_duplicates) - . = ..() - if(!.) - return FALSE - - if(isnull(GLOB.main_supermatter_engine)) - return FALSE - var/obj/machinery/power/supermatter_crystal/engine/crystal = locate() in GLOB.main_supermatter_engine - if(!is_station_level(crystal.z) && !is_mining_level(crystal.z)) - return FALSE - - return TRUE - -/datum/traitor_objective/ultimate/supermatter_cascade/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/list/possible_areas = GLOB.the_station_areas.Copy() - for(var/area/possible_area as anything in possible_areas) - //remove areas too close to the destination, too obvious for our poor shmuck, or just unfair - if(ispath(possible_area, /area/station/hallway) || ispath(possible_area, /area/station/security)) - possible_areas -= possible_area - if(length(possible_areas) == 0) - return FALSE - dest_crystal_area_pickup = pick(possible_areas) - replace_in_name("%AREA%", initial(dest_crystal_area_pickup.name)) - return TRUE - -/datum/traitor_objective/ultimate/supermatter_cascade/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!sent_crystal) - buttons += add_ui_button("", "Pressing this will call down a pod with the supermatter cascade kit.", "biohazard", "destabilizing_crystal") - return buttons - -/datum/traitor_objective/ultimate/supermatter_cascade/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("destabilizing_crystal") - if(sent_crystal) - return - var/area/delivery_area = get_area(user) - if(delivery_area.type != dest_crystal_area_pickup) - to_chat(user, span_warning("You must be in [initial(dest_crystal_area_pickup.name)] to receive the supermatter cascade kit.")) - return - sent_crystal = TRUE - podspawn(list( - "target" = get_turf(user), - "style" = /datum/pod_style/syndicate, - "spawn" = /obj/item/destabilizing_crystal, - )) diff --git a/code/modules/antagonists/traitor/objectives/hack_comm_console.dm b/code/modules/antagonists/traitor/objectives/hack_comm_console.dm deleted file mode 100644 index 93323e4e15f0..000000000000 --- a/code/modules/antagonists/traitor/objectives/hack_comm_console.dm +++ /dev/null @@ -1,53 +0,0 @@ -/datum/traitor_objective_category/hack_comm_console - name = "Hack Communication Console" - objectives = list( - /datum/traitor_objective/hack_comm_console = 1, - ) - -/datum/traitor_objective/hack_comm_console - name = "Hack a communication console to summon an unknown threat to the station" - description = "Right click on a communication console to begin the hacking process. Once started, the AI will know that you are hacking a communication console, so be ready to run or have yourself disguised to prevent being caught. This objective will invalidate itself if another traitor completes it first." - - progression_minimum = 60 MINUTES - progression_reward = list(30 MINUTES, 40 MINUTES) - telecrystal_reward = list(7, 12) - - var/progression_objectives_minimum = 20 MINUTES - -/datum/traitor_objective/hack_comm_console/can_generate_objective(datum/mind/generating_for, list/possible_duplicates) - if(SStraitor.get_taken_count(/datum/traitor_objective/hack_comm_console) > 0) - return FALSE - if(handler.get_completion_progression(/datum/traitor_objective) < progression_objectives_minimum) - return FALSE - return TRUE - -/datum/traitor_objective/hack_comm_console/generate_objective(datum/mind/generating_for, list/possible_duplicates) - AddComponent(/datum/component/traitor_objective_mind_tracker, generating_for, \ - signals = list(COMSIG_LIVING_UNARMED_ATTACK = PROC_REF(on_unarmed_attack))) - RegisterSignal(SSdcs, COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED, PROC_REF(on_global_obj_completed)) - return TRUE - -/datum/traitor_objective/hack_comm_console/ungenerate_objective() - UnregisterSignal(SSdcs, COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED) - -/datum/traitor_objective/hack_comm_console/proc/on_global_obj_completed(datum/source, datum/traitor_objective/objective) - SIGNAL_HANDLER - if(istype(objective, /datum/traitor_objective/hack_comm_console)) - fail_objective() - -/datum/traitor_objective/hack_comm_console/proc/on_unarmed_attack(mob/user, obj/machinery/computer/communications/target, proximity_flag, modifiers) - SIGNAL_HANDLER - if(!proximity_flag) - return - if(!modifiers[RIGHT_CLICK]) - return - if(!istype(target)) - return - INVOKE_ASYNC(src, PROC_REF(begin_hack), user, target) - return COMPONENT_CANCEL_ATTACK_CHAIN - -/datum/traitor_objective/hack_comm_console/proc/begin_hack(mob/user, obj/machinery/computer/communications/target) - if(!target.try_hack_console(user)) - return - - succeed_objective() diff --git a/code/modules/antagonists/traitor/objectives/infect.dm b/code/modules/antagonists/traitor/objectives/infect.dm deleted file mode 100644 index 459e8f54a0d7..000000000000 --- a/code/modules/antagonists/traitor/objectives/infect.dm +++ /dev/null @@ -1,176 +0,0 @@ -/datum/traitor_objective_category/infect - name = "Infect with Disease" - objectives = list( - /datum/traitor_objective/target_player/infect = 1, - ) - -/datum/traitor_objective/target_player/infect - name = "Infect %TARGET% the %JOB TITLE%" - description = "Infect your target with the experimental Hereditary Manifold Sickness." - - progression_minimum = 30 MINUTES - - progression_reward = list(8 MINUTES, 14 MINUTES) - telecrystal_reward = 1 - - duplicate_type = /datum/traitor_objective/target_player/infect - - /// if TRUE, can only target heads of staff - /// if FALSE, CANNOT target heads of staff - var/heads_of_staff = FALSE - /// if TRUE, the injector item has been bestowed upon the player - var/injector_given = FALSE - -/datum/traitor_objective/target_player/infect/supported_configuration_changes() - . = ..() - . += NAMEOF(src, objective_period) - . += NAMEOF(src, maximum_objectives_in_period) - -/datum/traitor_objective/target_player/infect/can_generate_objective(generating_for, list/possible_duplicates) - if(length(possible_duplicates) > 0) - return FALSE - return ..() - -/datum/traitor_objective/target_player/infect/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!injector_given) - buttons += add_ui_button("", "Pressing this will materialize a EHMS autoinjector into your hand, which you must inject into the target to succeed.", "syringe", "summon_pen") - return buttons - -/datum/traitor_objective/target_player/infect/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("summon_pen") - if(injector_given) - return - injector_given = TRUE - var/obj/item/reagent_containers/hypospray/medipen/manifoldinjector/ehms = new(user.drop_location()) - user.put_in_hands(ehms) - ehms.balloon_alert(user, "the injector materializes in your hand") - RegisterSignal(ehms, COMSIG_EHMS_INJECTOR_INJECTED, PROC_REF(on_injected)) - AddComponent(/datum/component/traitor_objective_register, ehms, \ - succeed_signals = null, \ - fail_signals = list(COMSIG_QDELETING), \ - penalty = TRUE) - -/datum/traitor_objective/target_player/infect/proc/on_injected(datum/source, mob/living/user, mob/living/injected) - SIGNAL_HANDLER - if(injected != target) - fail_objective() - return - if(injected == target) - succeed_objective() - return - -/datum/traitor_objective/target_player/infect/generate_objective(datum/mind/generating_for, list/possible_duplicates) - - var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things - for(var/datum/objective/task as anything in handler.primary_objectives) - if(!istype(task.target, /datum/mind)) - continue - already_targeting += task.target //Removing primary objective kill targets from the list - - var/parent_type = type2parent(type) - //don't roll head of staff types if you haven't completed the normal version - if(heads_of_staff && !handler.get_completion_count(parent_type)) - // Locked if they don't have any of the risky bug room objective completed - return FALSE - - var/list/possible_targets = list() - var/try_target_late_joiners = FALSE - if(generating_for.late_joiner) - try_target_late_joiners = TRUE - for(var/datum/mind/possible_target as anything in get_crewmember_minds()) - if(possible_target in already_targeting) - continue - var/target_area = get_area(possible_target.current) - if(possible_target == generating_for) - continue - if(!ishuman(possible_target.current)) - continue - if(possible_target.current.stat == DEAD) - continue - var/datum/antagonist/traitor/traitor = possible_target.has_antag_datum(/datum/antagonist/traitor) - if(traitor && traitor.uplink_handler.telecrystals >= 0) - continue - var/mob/living/carbon/human/targets_current = possible_target.current - var/datum/disease/chronic_illness/illness = locate() in targets_current.diseases - if(illness) - continue - if(!HAS_TRAIT(SSstation, STATION_TRAIT_LATE_ARRIVALS) && istype(target_area, /area/shuttle/arrival)) - continue - //removes heads of staff from being targets from non heads of staff assassinations, and vice versa - if(heads_of_staff) - if(!(possible_target.assigned_role.job_flags & JOB_HEAD_OF_STAFF)) - continue - else - if((possible_target.assigned_role.job_flags & JOB_HEAD_OF_STAFF)) - continue - possible_targets += possible_target - for(var/datum/traitor_objective/target_player/objective as anything in possible_duplicates) - possible_targets -= objective.target - if(try_target_late_joiners) - var/list/all_possible_targets = possible_targets.Copy() - for(var/datum/mind/possible_target as anything in all_possible_targets) - if(!possible_target.late_joiner) - possible_targets -= possible_target - if(!possible_targets.len) - possible_targets = all_possible_targets - special_target_filter(possible_targets) - if(!possible_targets.len) - return FALSE //MISSION FAILED, WE'LL GET EM NEXT TIME - - var/datum/mind/target_mind = pick(possible_targets) - set_target(target_mind.current) - replace_in_name("%TARGET%", target.real_name) - replace_in_name("%JOB TITLE%", target_mind.assigned_role.title) - RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_target_death)) - return TRUE - -/datum/traitor_objective/target_player/infect/ungenerate_objective() - UnregisterSignal(target, COMSIG_LIVING_DEATH) - set_target(null) - -///proc for checking for special states that invalidate a target -/datum/traitor_objective/target_player/infect/proc/special_target_filter(list/possible_targets) - return - -/datum/traitor_objective/target_player/infect/target_deleted() - if(objective_state == OBJECTIVE_STATE_INACTIVE) - //don't take an objective target of someone who is already obliterated - fail_objective() - return ..() - -/datum/traitor_objective/target_player/infect/proc/on_target_death() - SIGNAL_HANDLER - if(objective_state == OBJECTIVE_STATE_INACTIVE) - //don't take an objective target of someone who is already dead - fail_objective() - -/obj/item/reagent_containers/hypospray/medipen/manifoldinjector - name = "EHMS autoinjector" - desc = "Experimental Hereditary Manifold Sickness autoinjector." - icon_state = "tbpen" - inhand_icon_state = "tbpen" - base_icon_state = "tbpen" - volume = 30 - amount_per_transfer_from_this = 30 - list_reagents = list(/datum/reagent/medicine/sansufentanyl = 20) - stealthy = TRUE - //Was the injector used on someone yet? - var/used = FALSE - -/obj/item/reagent_containers/hypospray/medipen/manifoldinjector/attack(mob/living/affected_mob, mob/living/carbon/human/user) - if(used) - return ..() - to_chat(affected_mob, span_warning("You feel someone try to inject you with something.")) - balloon_alert(user, "injecting...") - log_combat(user, affected_mob, "attempted to inject", src) - if(!do_after(user, 1.5 SECONDS, hidden = TRUE)) - balloon_alert(user, "interrupted!") - return - var/datum/disease/chronic_illness/hms = new /datum/disease/chronic_illness() - affected_mob.ForceContractDisease(hms) - used = TRUE - inject(affected_mob, user) - SEND_SIGNAL(src, COMSIG_EHMS_INJECTOR_INJECTED, user, affected_mob) diff --git a/code/modules/antagonists/traitor/objectives/kidnapping.dm b/code/modules/antagonists/traitor/objectives/kidnapping.dm deleted file mode 100644 index 3e41b7f6916e..000000000000 --- a/code/modules/antagonists/traitor/objectives/kidnapping.dm +++ /dev/null @@ -1,320 +0,0 @@ -/datum/traitor_objective/target_player/kidnapping - name = "Kidnap %TARGET% the %JOB TITLE% and deliver them to %AREA%" - description = "%TARGET% holds extremely important information regarding secret NT projects - and you'll need to kidnap and deliver them to %AREA%, where our transport pod will be waiting. \ - If %TARGET% is delivered alive, you will be rewarded with an additional %TC% telecrystals." - - abstract_type = /datum/traitor_objective/target_player/kidnapping - - /// The jobs that this objective is targeting. - var/list/target_jobs - /// Area that the target needs to be delivered to - var/area/dropoff_area - /// Have we called the pod yet? - var/pod_called = FALSE - /// How much TC do we get from sending the target alive - var/alive_bonus = 0 - /// All stripped targets belongings (weakrefs) - var/list/target_belongings = list() - - duplicate_type = /datum/traitor_objective/target_player - -/datum/traitor_objective/target_player/kidnapping/supported_configuration_changes() - . = ..() - . += NAMEOF(src, objective_period) - . += NAMEOF(src, maximum_objectives_in_period) - -/datum/traitor_objective/target_player/kidnapping/New(datum/uplink_handler/handler) - . = ..() - AddComponent(/datum/component/traitor_objective_limit_per_time, \ - /datum/traitor_objective/target_player, \ - time_period = objective_period, \ - maximum_objectives = maximum_objectives_in_period \ - ) - -/datum/traitor_objective/target_player/kidnapping/common - progression_minimum = 0 MINUTES - progression_maximum = 30 MINUTES - progression_reward = list(2 MINUTES, 4 MINUTES) - telecrystal_reward = list(1, 2) - target_jobs = list( - // Cargo - /datum/job/cargo_technician, - // Engineering - /datum/job/atmospheric_technician, - /datum/job/station_engineer, - // Medical - /datum/job/chemist, - /datum/job/doctor, - /datum/job/psychologist, - /datum/job/virologist, - // Science - /datum/job/geneticist, - /datum/job/roboticist, - /datum/job/scientist, - // Service - /datum/job/bartender, - /datum/job/botanist, - /datum/job/chaplain, - /datum/job/clown, - /datum/job/curator, - /datum/job/janitor, - /datum/job/lawyer, - /datum/job/mime, - ) - alive_bonus = 2 - -/datum/traitor_objective/target_player/kidnapping/common/assistant - progression_minimum = 0 MINUTES - progression_maximum = 15 MINUTES - target_jobs = list( - /datum/job/assistant - ) - -/datum/traitor_objective/target_player/kidnapping/uncommon //Hard to fish out targets - progression_minimum = 0 MINUTES - progression_maximum = 45 MINUTES - progression_reward = list(4 MINUTES, 8 MINUTES) - telecrystal_reward = list(1, 2) - - target_jobs = list( - // Cargo - /datum/job/shaft_miner, - // Medical - /datum/job/paramedic, - // Service - /datum/job/cook, - ) - alive_bonus = 3 - -/datum/traitor_objective/target_player/kidnapping/rare - progression_minimum = 15 MINUTES - progression_maximum = 60 MINUTES - progression_reward = list(8 MINUTES, 12 MINUTES) - telecrystal_reward = list(2, 3) - target_jobs = list( - // Heads of staff - /datum/job/chief_engineer, - /datum/job/chief_medical_officer, - /datum/job/head_of_personnel, - /datum/job/research_director, - /datum/job/quartermaster, - // Security - /datum/job/detective, - /datum/job/security_officer, - /datum/job/warden, - ) - alive_bonus = 4 - -/datum/traitor_objective/target_player/kidnapping/captain - progression_minimum = 30 MINUTES - progression_reward = list(12 MINUTES, 16 MINUTES) - telecrystal_reward = list(2, 3) - target_jobs = list( - /datum/job/captain, - /datum/job/head_of_security, - ) - alive_bonus = 5 - -/datum/traitor_objective/target_player/kidnapping/generate_objective(datum/mind/generating_for, list/possible_duplicates) - - var/list/already_targeting = list() //List of minds we're already targeting. The possible_duplicates is a list of objectives, so let's not mix things - for(var/datum/objective/task as anything in handler.primary_objectives) - if(!istype(task.target, /datum/mind)) - continue - already_targeting += task.target //Removing primary objective kill targets from the list - - var/list/possible_targets = list() - for(var/datum/mind/possible_target as anything in get_crewmember_minds()) - if(possible_target == generating_for) - continue - - if(possible_target in already_targeting) - continue - - if(!ishuman(possible_target.current)) - continue - - if(possible_target.current.stat == DEAD) - continue - - if(HAS_TRAIT(possible_target, TRAIT_HAS_BEEN_KIDNAPPED)) - continue - - if(possible_target.has_antag_datum(/datum/antagonist/traitor)) - continue - - if(!(possible_target.assigned_role.type in target_jobs)) - continue - - possible_targets += possible_target - - for(var/datum/traitor_objective/target_player/objective as anything in possible_duplicates) - if(!objective.target) //the old objective was already completed. - continue - possible_targets -= objective.target.mind - - if(!length(possible_targets)) - return FALSE - - var/datum/mind/target_mind = pick(possible_targets) - set_target(target_mind.current) - AddComponent(/datum/component/traitor_objective_register, target, fail_signals = list(COMSIG_QDELETING)) - var/list/possible_areas = GLOB.the_station_areas.Copy() - for(var/area/possible_area as anything in possible_areas) - if(ispath(possible_area, /area/station/hallway) || ispath(possible_area, /area/station/security) || initial(possible_area.outdoors)) - possible_areas -= possible_area - - dropoff_area = pick(possible_areas) - replace_in_name("%TARGET%", target_mind.name) - replace_in_name("%JOB TITLE%", target_mind.assigned_role.title) - replace_in_name("%AREA%", initial(dropoff_area.name)) - replace_in_name("%TC%", alive_bonus) - return TRUE - -/datum/traitor_objective/target_player/kidnapping/ungenerate_objective() - set_target(null) - dropoff_area = null - -/datum/traitor_objective/target_player/kidnapping/on_objective_taken(mob/user) - . = ..() - INVOKE_ASYNC(src, PROC_REF(generate_holding_area)) - -/datum/traitor_objective/target_player/kidnapping/proc/generate_holding_area() - // Let's load in the holding facility ahead of time - // even if they fail the objective it's better to get done now rather than later - SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY) - -/datum/traitor_objective/target_player/kidnapping/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!pod_called) - buttons += add_ui_button("Call Extraction Pod", "Pressing this will call down an extraction pod.", "rocket", "call_pod") - return buttons - -/datum/traitor_objective/target_player/kidnapping/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("call_pod") - if(pod_called) - return - var/area/user_area = get_area(user) - var/area/target_area = get_area(target) - - if(user_area.type != dropoff_area) - to_chat(user, span_warning("You must be in [initial(dropoff_area.name)] to call the extraction pod.")) - return - - if(target_area.type != dropoff_area) - to_chat(user, span_warning("[target.real_name] must be in [initial(dropoff_area.name)] for you to call the extraction pod.")) - return - - call_pod(user) - -/datum/traitor_objective/target_player/kidnapping/proc/call_pod(mob/living/user) - pod_called = TRUE - var/obj/structure/closet/supplypod/extractionpod/new_pod = new() - RegisterSignal(new_pod, COMSIG_ATOM_ENTERED, PROC_REF(enter_check)) - new /obj/effect/pod_landingzone(get_turf(user), new_pod) - -/datum/traitor_objective/target_player/kidnapping/proc/enter_check(obj/structure/closet/supplypod/extractionpod/source, entered_atom) - if(!istype(source)) - CRASH("Kidnapping objective's enter_check called with source being not an extraction pod: [source ? source.type : "N/A"]") - - if(!ishuman(entered_atom)) - return - - var/mob/living/carbon/human/sent_mob = entered_atom - - if(sent_mob.mind) - ADD_TRAIT(sent_mob.mind, TRAIT_HAS_BEEN_KIDNAPPED, TRAIT_GENERIC) - - for(var/obj/item/belonging in sent_mob.gather_belongings()) - if(belonging == sent_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) || belonging == sent_mob.get_item_by_slot(ITEM_SLOT_FEET)) - continue - - var/unequipped = sent_mob.transferItemToLoc(belonging) - if (!unequipped) - continue - target_belongings.Add(WEAKREF(belonging)) - - var/datum/bank_account/cargo_account = SSeconomy.get_dep_account(ACCOUNT_CAR) - - if(cargo_account) //Just in case - cargo_account.adjust_money(-min(rand(1000, 3000), cargo_account.account_balance)) //Not so much, especially for competent cargo. Plus this can't be mass-triggered like it has been done with contractors - - priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. As is policy we've taken a portion of the station's funds to offset the overall cost.", "Nanotrasen Asset Protection", has_important_message = TRUE) - - addtimer(CALLBACK(src, PROC_REF(handle_target), sent_mob), 1.5 SECONDS) - - if(sent_mob != target) - fail_objective(penalty_cost = telecrystal_penalty) - source.startExitSequence(source) - return - - if(sent_mob.stat != DEAD) - telecrystal_reward += alive_bonus - - succeed_objective() - source.startExitSequence(source) - -/datum/traitor_objective/target_player/kidnapping/proc/handle_target(mob/living/carbon/human/sent_mob) - addtimer(CALLBACK(src, PROC_REF(return_target), sent_mob), 3 MINUTES) - if(sent_mob.stat == DEAD) - return - - sent_mob.flash_act() - sent_mob.adjust_confusion(10 SECONDS) - sent_mob.adjust_dizzy(10 SECONDS) - sent_mob.set_eye_blur_if_lower(100 SECONDS) - sent_mob.dna.species.give_important_for_life(sent_mob) // so plasmamen do not get left for dead - to_chat(sent_mob, span_hypnophrase(span_reallybig("A million voices echo in your head... \"Your mind held many valuable secrets - \ - we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \ - so it's only a matter of time before we ship you back...\""))) - -/datum/traitor_objective/target_player/kidnapping/proc/return_target(mob/living/carbon/human/sent_mob) - if(!sent_mob || QDELETED(sent_mob)) //suicided and qdeleted themselves - return - - var/list/possible_turfs = list() - - for(var/turf/open/open_turf in dropoff_area.get_turfs_from_all_zlevels()) - if(open_turf.is_blocked_turf() || isspaceturf(open_turf)) - continue - possible_turfs += open_turf - - if(!LAZYLEN(possible_turfs)) - var/turf/new_turf = get_safe_random_station_turf() - if(!new_turf) //SOMEHOW - to_chat(sent_mob, span_hypnophrase(span_reallybig("A million voices echo in your head... \"Seems where you got sent here from won't \ - be able to handle our pod... You will die here instead.\""))) - if (sent_mob.can_heartattack()) - sent_mob.set_heartattack(TRUE) - return - - possible_turfs += new_turf - - var/obj/structure/closet/supplypod/return_pod = new() - return_pod.bluespace = TRUE - return_pod.explosionSize = list(0,0,0,0) - return_pod.style = /datum/pod_style/syndicate // Non-module change : removed upstream - - do_sparks(8, FALSE, sent_mob) - sent_mob.visible_message(span_notice("[sent_mob] vanishes!")) - for(var/obj/item/belonging in sent_mob.gather_belongings()) - if(belonging == sent_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) || belonging == sent_mob.get_item_by_slot(ITEM_SLOT_FEET)) - continue - sent_mob.dropItemToGround(belonging) // No souvenirs, except shoes and t-shirts - - for(var/datum/weakref/belonging_ref in target_belongings) - var/obj/item/belonging = belonging_ref.resolve() - if(!belonging) - continue - belonging.forceMove(return_pod) - - sent_mob.forceMove(return_pod) - sent_mob.flash_act() - sent_mob.adjust_confusion(10 SECONDS) - sent_mob.adjust_dizzy(10 SECONDS) - sent_mob.set_eye_blur_if_lower(100 SECONDS) - sent_mob.dna.species.give_important_for_life(sent_mob) // so plasmamen do not get left for dead - - new /obj/effect/pod_landingzone(pick(possible_turfs), return_pod) diff --git a/code/modules/antagonists/traitor/objectives/kill_pet.dm b/code/modules/antagonists/traitor/objectives/kill_pet.dm deleted file mode 100644 index 21bf06eb3868..000000000000 --- a/code/modules/antagonists/traitor/objectives/kill_pet.dm +++ /dev/null @@ -1,96 +0,0 @@ -/datum/traitor_objective_category/kill_pet - name = "Kill Pet" - objectives = list( - /datum/traitor_objective/kill_pet/high_risk = 1, - list( - /datum/traitor_objective/kill_pet = 2, - /datum/traitor_objective/kill_pet/medium_risk = 1, - ) = 4, - ) - -/datum/traitor_objective/kill_pet - name = "Kill the %DEPARTMENT HEAD%'s beloved %PET%" - description = "The %DEPARTMENT HEAD% has particularly annoyed us by sending us spam emails and we want their %PET% dead to show them what happens when they cross us. " - - progression_minimum = 0 MINUTES - telecrystal_reward = list(1, 2) - progression_reward = list(3 MINUTES, 6 MINUTES) - - /// Possible heads mapped to their pet type. Can be a list of possible pets - var/list/possible_heads = list( - JOB_HEAD_OF_PERSONNEL = list( - /mob/living/basic/pet/dog/corgi/ian, - /mob/living/basic/pet/dog/corgi/puppy/ian - ), - JOB_CAPTAIN = /mob/living/basic/pet/fox/renault, - JOB_CHIEF_MEDICAL_OFFICER = /mob/living/basic/pet/cat/runtime, - JOB_CHIEF_ENGINEER = /mob/living/basic/parrot/poly, - JOB_QUARTERMASTER = list( - /mob/living/basic/gorilla/cargorilla, - /mob/living/basic/sloth/citrus, - /mob/living/basic/sloth/paperwork, - ) - ) - /// The head that we are targeting - var/datum/job/target - /// Whether or not we only take from the traitor's own department head or not. - var/limited_to_department_head = TRUE - /// The actual pet that needs to be killed - var/mob/living/target_pet - - duplicate_type = /datum/traitor_objective/kill_pet - -/datum/traitor_objective/kill_pet/medium_risk - progression_minimum = 10 MINUTES - progression_reward = list(5 MINUTES, 8 MINUTES) - limited_to_department_head = FALSE - -/datum/traitor_objective/kill_pet/high_risk - progression_minimum = 25 MINUTES - progression_reward = list(14 MINUTES, 18 MINUTES) - telecrystal_reward = list(2, 3) - - limited_to_department_head = FALSE - possible_heads = list( - JOB_HEAD_OF_SECURITY = list( - /mob/living/basic/carp/pet/lia, - /mob/living/basic/spider/giant/sgt_araneus, - ), - JOB_WARDEN = list( - /mob/living/basic/pet/dog/pug/mcgriff - ) - ) - -/datum/traitor_objective/kill_pet/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/datum/job/role = generating_for.assigned_role - for(var/datum/traitor_objective/kill_pet/objective as anything in possible_duplicates) - possible_heads -= objective.target.title - if(limited_to_department_head) - possible_heads = possible_heads & role.department_head - possible_heads -= role.title - - if(!length(possible_heads)) - return FALSE - target = SSjob.name_occupations[pick(possible_heads)] - var/pet_type = possible_heads[target.title] - if(islist(pet_type)) - for(var/type in pet_type) - target_pet = locate(type) in GLOB.mob_living_list - if(target_pet) - break - else - target_pet = locate(pet_type) in GLOB.mob_living_list - if(!target_pet) - return FALSE - if(target_pet.stat == DEAD) - return FALSE - AddComponent(/datum/component/traitor_objective_register, target_pet, \ - succeed_signals = list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) - replace_in_name("%DEPARTMENT HEAD%", target.title) - replace_in_name("%PET%", target_pet.name) - return TRUE - -/datum/traitor_objective/kill_pet/ungenerate_objective() - if(target_pet) - UnregisterSignal(target_pet, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH)) - target_pet = null diff --git a/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm b/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm deleted file mode 100644 index b08ae515a646..000000000000 --- a/code/modules/antagonists/traitor/objectives/locate_weakpoint.dm +++ /dev/null @@ -1,280 +0,0 @@ -/datum/traitor_objective_category/locate_weakpoint - name = "Locate And Destroy Weakpoint" - objectives = list( - /datum/traitor_objective/locate_weakpoint = 1, - ) - weight = OBJECTIVE_WEIGHT_UNLIKELY - -/datum/traitor_objective/locate_weakpoint - name = "Triangulate station's structural weakpoint and detonate an explosive charge nearby." - description = "You will be given a handheld device that you'll need to use in %AREA1% and %AREA2% in order to triangulate the station's structural weakpoint and detonate an explosive charge there. Warning: Once you start scanning either one of the areas, station's AI will be alerted." - - progression_minimum = 45 MINUTES - progression_reward = list(15 MINUTES, 20 MINUTES) - telecrystal_reward = list(3, 5) - - var/progression_objectives_minimum = 20 MINUTES - - /// Have we sent a weakpoint locator yet? - var/locator_sent = FALSE - /// Have we sent a bomb yet? - var/bomb_sent = FALSE - /// Have we located the weakpoint yet? - var/weakpoint_found = FALSE - /// Areas that need to be scanned - var/list/area/scan_areas - /// Weakpoint where the bomb should be planted - var/area/weakpoint_area - -/datum/traitor_objective/locate_weakpoint/can_generate_objective(datum/mind/generating_for, list/possible_duplicates) - if(handler.get_completion_progression(/datum/traitor_objective) < progression_objectives_minimum) - return FALSE - if(SStraitor.get_taken_count(/datum/traitor_objective/locate_weakpoint) > 0) - return FALSE - return TRUE - -/datum/traitor_objective/locate_weakpoint/generate_objective(datum/mind/generating_for, list/possible_duplicates) - scan_areas = list() - /// List of high-security areas that we pick required ones from - var/list/allowed_areas = typecacheof(list(/area/station/command, - /area/station/comms, - /area/station/engineering, - /area/station/science, - /area/station/security, - )) - - var/list/blacklisted_areas = typecacheof(list(/area/station/engineering/hallway, - /area/station/engineering/lobby, - /area/station/engineering/storage, - /area/station/science/lobby, - /area/station/science/ordnance/bomb, - /area/station/security/prison, - )) - - var/list/possible_areas = GLOB.the_station_areas.Copy() - for(var/area/possible_area as anything in possible_areas) - if(!is_type_in_typecache(possible_area, allowed_areas) || initial(possible_area.outdoors) || is_type_in_typecache(possible_area, blacklisted_areas)) - possible_areas -= possible_area - - for(var/i in 1 to 2) - scan_areas[pick_n_take(possible_areas)] = TRUE - weakpoint_area = pick_n_take(possible_areas) - - var/area/scan_area1 = scan_areas[1] - var/area/scan_area2 = scan_areas[2] - replace_in_name("%AREA1%", initial(scan_area1.name)) - replace_in_name("%AREA2%", initial(scan_area2.name)) - RegisterSignal(SSdcs, COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED, PROC_REF(on_global_obj_completed)) - return TRUE - -/datum/traitor_objective/locate_weakpoint/ungenerate_objective() - UnregisterSignal(SSdcs, COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED) - -/datum/traitor_objective/locate_weakpoint/on_objective_taken(mob/user) - . = ..() - - // We don't want multiple people being able to take weakpoint objectives if they get one available at the same time - for(var/datum/traitor_objective/locate_weakpoint/other_objective as anything in SStraitor.all_objectives_by_type[/datum/traitor_objective/locate_weakpoint]) - if(other_objective != src) - other_objective.fail_objective() - - -/datum/traitor_objective/locate_weakpoint/proc/on_global_obj_completed(datum/source, datum/traitor_objective/objective) - SIGNAL_HANDLER - if(istype(objective, /datum/traitor_objective/locate_weakpoint)) - fail_objective() - -/datum/traitor_objective/locate_weakpoint/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!locator_sent) - buttons += add_ui_button("", "Pressing this will materialize a weakpoint locator in your hand.", "globe", "locator") - if(weakpoint_found && !bomb_sent) - buttons += add_ui_button("", "Pressing this will materialize an ES8 explosive charge in your hand.", "bomb", "shatter_charge") - return buttons - -/datum/traitor_objective/locate_weakpoint/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("locator") - if(locator_sent) - return - locator_sent = TRUE - var/obj/item/weakpoint_locator/locator = new(user.drop_location(), src) - user.put_in_hands(locator) - locator.balloon_alert(user, "the weakpoint locator materializes in your hand") - - if("shatter_charge") - if(bomb_sent) - return - bomb_sent = TRUE - var/obj/item/grenade/c4/es8/bomb = new(user.drop_location(), src) - user.put_in_hands(bomb) - bomb.balloon_alert(user, "the ES8 charge materializes in your hand") - -/datum/traitor_objective/locate_weakpoint/proc/weakpoint_located() - description = "Structural weakpoint has been located in %AREA%. Detonate an ES8 explosive charge there to create a shockwave that will severely damage the station." - replace_in_name("%AREA%", initial(weakpoint_area.name)) - weakpoint_found = TRUE - -/datum/traitor_objective/locate_weakpoint/proc/create_shockwave(center_x, center_y, center_z) - var/turf/epicenter = locate(center_x, center_y, center_z) - var/lowpop = (length(GLOB.clients) <= CONFIG_GET(number/minimal_access_threshold)) - if(lowpop) - explosion(epicenter, devastation_range = 2, heavy_impact_range = 4, light_impact_range = 6, explosion_cause = src) - else - explosion(epicenter, devastation_range = 3, heavy_impact_range = 6, light_impact_range = 9, explosion_cause = src) - priority_announce( - "Attention crew, it appears that a high-power explosive charge has been detonated in your station's weakpoint, causing severe structural damage.", - "[command_name()] High-Priority Update" - ) - - succeed_objective() - -/obj/item/weakpoint_locator - name = "structural weakpoint locator" - desc = "A device that can triangulate station's structural weakpoint. It has to be used in %AREA1% and %AREA2% in order to triangulate the weakpoint. Warning: station's AI will be notified as soon as the process starts!" - icon = 'icons/obj/antags/syndicate_tools.dmi' - icon_state = "weakpoint_locator" - inhand_icon_state = "weakpoint_locator" - lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - var/datum/weakref/objective_weakref - -/obj/item/weakpoint_locator/Initialize(mapload, datum/traitor_objective/locate_weakpoint/objective) - . = ..() - objective_weakref = WEAKREF(objective) - if(!objective) - return - var/area/area1 = objective.scan_areas[1] - var/area/area2 = objective.scan_areas[2] - desc = replacetext(desc, "%AREA1%", initial(area1.name)) - desc = replacetext(desc, "%AREA2%", initial(area2.name)) - -/obj/item/weakpoint_locator/Destroy(force) - objective_weakref = null - return ..() - -/obj/item/weakpoint_locator/attack_self(mob/living/user, modifiers) - . = ..() - if(!istype(user) || loc != user || !user.mind) //No TK cheese - return - - var/datum/traitor_objective/locate_weakpoint/objective = objective_weakref.resolve() - - if(!objective || objective.objective_state == OBJECTIVE_STATE_INACTIVE) - to_chat(user, span_warning("Your time to use [src] has not come yet.")) - return - - if(objective.handler.owner != user.mind) - to_chat(user, span_warning("You have zero clue how to use [src].")) - return - - var/area/user_area = get_area(user) - if(!(user_area.type in objective.scan_areas)) - balloon_alert(user, "invalid area!") - playsound(user, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - return - - if(!objective.scan_areas[user_area.type]) - balloon_alert(user, "already scanned here!") - playsound(user, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - return - - user.visible_message(span_danger("[user] presses a few buttons on [src] and it starts ominously beeping!"), span_notice("You activate [src] and start scanning the area. Do not exit [get_area_name(user, TRUE)] until the scan finishes!")) - playsound(user, 'sound/machines/triple_beep.ogg', 30, TRUE) - var/alertstr = span_userdanger("Network Alert: Station network probing attempt detected[user_area?" in [get_area_name(user, TRUE)]":". Unable to pinpoint location"].") - for(var/mob/living/silicon/ai/ai_player in GLOB.player_list) - to_chat(ai_player, alertstr) - - if(!do_after(user, 30 SECONDS, src, IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE | IGNORE_HELD_ITEM | IGNORE_INCAPACITATED | IGNORE_SLOWDOWNS, extra_checks = CALLBACK(src, PROC_REF(scan_checks), user, user_area, objective), hidden = TRUE)) - playsound(user, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - return - - playsound(user, 'sound/machines/ding.ogg', 100, TRUE) - objective.scan_areas[user_area.type] = FALSE - for(var/area/scan_area as anything in objective.scan_areas) - if(objective.scan_areas[scan_area]) - say("Next scanning location is [initial(scan_area.name)]") - return - - to_chat(user, span_notice("Scan finished. Structural weakpoint located in [initial(objective.weakpoint_area.name)].")) - objective.weakpoint_located() - -/obj/item/weakpoint_locator/proc/scan_checks(mob/living/user, area/user_area, datum/traitor_objective/locate_weakpoint/parent_objective) - if(get_area(user) != user_area) - return FALSE - - if(parent_objective.objective_state != OBJECTIVE_STATE_ACTIVE) - return FALSE - - var/atom/current_loc = loc - while(!isturf(current_loc) && !ismob(current_loc)) - current_loc = current_loc.loc - - if(current_loc != user) - return FALSE - - return TRUE - -/obj/item/grenade/c4/es8 - name = "ES8 explosive charge" - desc = "A high-power explosive charge designed to create a shockwave in a structural weakpoint of the station." - - icon_state = "plasticx40" - inhand_icon_state = "plasticx4" - worn_icon_state = "x4" - - boom_sizes = list(3, 6, 9) - - /// Weakref to user's objective - var/datum/weakref/objective_weakref - -/obj/item/grenade/c4/es8/Initialize(mapload, objective) - . = ..() - objective_weakref = WEAKREF(objective) - -/obj/item/grenade/c4/es8/Destroy() - objective_weakref = null - return ..() - -/obj/item/grenade/c4/es8/plant_c4(atom/bomb_target, mob/living/user) - if(!IS_TRAITOR(user)) - to_chat(user, span_warning("You can't seem to find a way to detonate the charge.")) - return FALSE - - var/datum/traitor_objective/locate_weakpoint/objective = objective_weakref.resolve() - if(!objective || objective.objective_state == OBJECTIVE_STATE_INACTIVE || objective.handler.owner != user.mind) - to_chat(user, span_warning("You don't think it would be wise to use [src].")) - return FALSE - - var/area/target_area = get_area(bomb_target) - if (target_area.type != objective.weakpoint_area) - to_chat(user, span_warning("[src] can only be detonated in [initial(objective.weakpoint_area.name)].")) - return FALSE - - if(!isfloorturf(bomb_target) && !iswallturf(bomb_target)) - to_chat(user, span_warning("[src] can only be planted on a wall or the floor!")) - return FALSE - - return ..() - -/obj/item/grenade/c4/es8/detonate(mob/living/lanced_by) - var/area/target_area = get_area(target) - var/datum/traitor_objective/locate_weakpoint/objective = objective_weakref.resolve() - - if(!objective) - return - - if (target_area.type != objective.weakpoint_area) - var/obj/item/grenade/c4/es8/new_bomb = new(target.drop_location()) - new_bomb.balloon_alert_to_viewers("invalid location!") - target.cut_overlay(plastic_overlay, TRUE) - qdel(src) - return - - objective.create_shockwave(target.x, target.y, target.z) - return ..() diff --git a/code/modules/antagonists/traitor/objectives/sabotage_machinery.dm b/code/modules/antagonists/traitor/objectives/sabotage_machinery.dm deleted file mode 100644 index 5a31c4c17397..000000000000 --- a/code/modules/antagonists/traitor/objectives/sabotage_machinery.dm +++ /dev/null @@ -1,240 +0,0 @@ -/// Datum which manages references to things we are instructed to destroy -GLOBAL_DATUM_INIT(objective_machine_handler, /datum/objective_target_machine_handler, new()) - -/// Marks a machine as a possible traitor sabotage target -/proc/add_sabotage_machine(source, typepath) - LAZYADD(GLOB.objective_machine_handler.machine_instances_by_path[typepath], source) - return typepath - -/// Traitor objective to destroy a machine the crew cares about -/datum/traitor_objective_category/sabotage_machinery - name = "Sabotage Worksite" - objectives = list( - /datum/traitor_objective/sabotage_machinery/trap = 1, - /datum/traitor_objective/sabotage_machinery/destroy = 1, - ) - -/datum/traitor_objective/sabotage_machinery - name = "Sabotage the %MACHINE%" - description = "Abstract objective holder which shouldn't appear in your uplink." - abstract_type = /datum/traitor_objective/sabotage_machinery - - /// The maximum amount of this type of objective a traitor can have, set to 0 for no limit. - var/maximum_allowed = 0 - /// The possible target machinery and the jobs tied to each one. - var/list/applicable_jobs = list() - /// The chosen job. Used to check for duplicates - var/chosen_job - -/datum/traitor_objective/sabotage_machinery/can_generate_objective(datum/mind/generating_for, list/possible_duplicates) - if(!maximum_allowed) - return TRUE - if(length(possible_duplicates) >= maximum_allowed) - return FALSE - return TRUE - -/datum/traitor_objective/sabotage_machinery/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/list/possible_jobs = applicable_jobs.Copy() - for(var/datum/traitor_objective/sabotage_machinery/objective as anything in possible_duplicates) - possible_jobs -= objective.chosen_job - for(var/available_job in possible_jobs) - var/job_machine_path = possible_jobs[available_job] - if (!length(GLOB.objective_machine_handler.machine_instances_by_path[job_machine_path])) - possible_jobs -= available_job - if(!length(possible_jobs)) - return FALSE - - chosen_job = pick(possible_jobs) - var/list/obj/machinery/possible_machines = GLOB.objective_machine_handler.machine_instances_by_path[possible_jobs[chosen_job]] - for(var/obj/machinery/machine as anything in possible_machines) - prepare_machine(machine) - - replace_in_name("%JOB%", LOWER_TEXT(chosen_job)) - replace_in_name("%MACHINE%", possible_machines[1].name) - return TRUE - -/// Marks a given machine as our target -/datum/traitor_objective/sabotage_machinery/proc/prepare_machine(obj/machinery/machine) - AddComponent(/datum/component/traitor_objective_register, machine, succeed_signals = list(COMSIG_QDELETING)) - -// Destroy machines which are in annoying locations, are annoying when destroyed, and aren't directly interacted with -/datum/traitor_objective/sabotage_machinery/destroy - name = "Destroy the %MACHINE%" - description = "Destroy the %MACHINE% to cause disarray and disrupt the operations of the %JOB%'s department." - - progression_reward = list(5 MINUTES, 10 MINUTES) - telecrystal_reward = list(3, 4) - - progression_minimum = 15 MINUTES - progression_maximum = 30 MINUTES - - applicable_jobs = list( - JOB_STATION_ENGINEER = /obj/machinery/telecomms/hub, - JOB_SCIENTIST = /obj/machinery/rnd/server, - ) - -// Rig machines which are in public locations to explode when interacted with -/datum/traitor_objective/sabotage_machinery/trap - name = "Sabotage the %MACHINE%" - description = "Destroy the %MACHINE% to cause disarray and disrupt the operations of the %JOB%'s department. If you can get another crew member to destroy the machine using the provided booby trap, you will be rewarded with an additional %PROGRESSION% reputation and %TC% telecrystals." - - progression_reward = list(2 MINUTES, 4 MINUTES) - telecrystal_reward = 0 // Only from completing the bonus objective - - progression_minimum = 0 MINUTES - progression_maximum = 10 MINUTES - - maximum_allowed = 2 - applicable_jobs = list( - JOB_CHIEF_ENGINEER = /obj/machinery/rnd/production/protolathe/department/engineering, - JOB_CHIEF_MEDICAL_OFFICER = /obj/machinery/rnd/production/techfab/department/medical, - JOB_HEAD_OF_PERSONNEL = /obj/machinery/rnd/production/techfab/department/service, - JOB_QUARTERMASTER = /obj/machinery/rnd/production/techfab/department/cargo, - JOB_RESEARCH_DIRECTOR = /obj/machinery/rnd/production/protolathe/department/science, - JOB_SHAFT_MINER = /obj/machinery/mineral/ore_redemption, - ) - - /// Bonus reward to grant if you booby trap successfully - var/bonus_tc = 2 - /// Bonus progression to grant if you booby trap successfully - var/bonus_progression = 5 MINUTES - /// Have we given out a traitor trap item? - var/traitor_trapper_given = FALSE - -/datum/traitor_objective/sabotage_machinery/trap/generate_objective(datum/mind/generating_for, list/possible_duplicates) - . = ..() - if (!.) - return FALSE - - replace_in_name("%TC%", bonus_tc) - replace_in_name("%PROGRESSION%", DISPLAY_PROGRESSION(bonus_progression)) - return TRUE - -/datum/traitor_objective/sabotage_machinery/trap/prepare_machine(obj/machinery/machine) - RegisterSignal(machine, COMSIG_TRAITOR_MACHINE_TRAP_TRIGGERED, PROC_REF(sabotage_success)) - return ..() - -/// Called when you successfully proc the booby trap, gives a bonus reward -/datum/traitor_objective/sabotage_machinery/trap/proc/sabotage_success(obj/machinery/machine) - progression_reward += bonus_progression - telecrystal_reward += bonus_tc - succeed_objective() - -/datum/traitor_objective/sabotage_machinery/trap/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!traitor_trapper_given) - buttons += add_ui_button("", "Pressing this will materialize an explosive trap in your hand, which you can conceal within the target machine", "wifi", "summon_gear") - return buttons - -/datum/traitor_objective/sabotage_machinery/trap/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("summon_gear") - if(traitor_trapper_given) - return - traitor_trapper_given = TRUE - var/obj/item/traitor_machine_trapper/tool = new(user.drop_location()) - user.put_in_hands(tool) - tool.balloon_alert(user, "a booby trap materializes in your hand") - tool.target_machine_path = applicable_jobs[chosen_job] - -/// Item which you use on a machine to cause it to explode next time someone interacts with it -/obj/item/traitor_machine_trapper - name = "suspicious device" - desc = "It looks dangerous." - icon = 'icons/obj/weapons/grenade.dmi' - icon_state = "boobytrap" - - /// Light explosion range, to hurt the person using the machine - var/explosion_range = 3 - /// The type of object on which this can be planted on. - var/obj/machinery/target_machine_path - /// The time it takes to deploy the bomb. - var/deploy_time = 10 SECONDS - -/obj/item/traitor_machine_trapper/examine(mob/user) - . = ..() - if(!IS_TRAITOR(user)) - return - if(target_machine_path) - . += span_notice("This device must be placed by clicking on a [initial(target_machine_path.name)] with it. It can be removed with a screwdriver.") - . += span_notice("Remember, you may leave behind fingerprints on the device. Wear gloves when handling it to be safe!") - -/obj/item/traitor_machine_trapper/pre_attack(atom/target, mob/living/user, params) - . = ..() - if (. || !istype(target, target_machine_path)) - return - balloon_alert(user, "planting device...") - if(!do_after(user, delay = deploy_time, target = src, interaction_key = DOAFTER_SOURCE_PLANTING_DEVICE, hidden = TRUE)) - return TRUE - target.AddComponent(\ - /datum/component/interaction_booby_trap,\ - additional_triggers = list(COMSIG_ORM_COLLECTED_ORE),\ - on_triggered_callback = CALLBACK(src, PROC_REF(on_triggered)),\ - on_defused_callback = CALLBACK(src, PROC_REF(on_defused)),\ - ) - RegisterSignal(target, COMSIG_QDELETING, GLOBAL_PROC_REF(qdel), src) - moveToNullspace() - return TRUE - -/// Called when applied trap is triggered, mark success -/obj/item/traitor_machine_trapper/proc/on_triggered(atom/machine) - SEND_SIGNAL(machine, COMSIG_TRAITOR_MACHINE_TRAP_TRIGGERED) - qdel(src) - -/// Called when applied trap has been defused, retrieve this item from nullspace -/obj/item/traitor_machine_trapper/proc/on_defused(atom/machine, mob/defuser, obj/item/tool) - UnregisterSignal(machine, COMSIG_QDELETING) - playsound(machine, 'sound/effects/structure_stress/pop3.ogg', 100, vary = TRUE) - forceMove(get_turf(machine)) - visible_message(span_warning("A [src] falls out from the [machine]!")) - -/// Datum which manages references to things we are instructed to destroy -/datum/objective_target_machine_handler - /// Existing instances of machines organised by typepath - var/list/machine_instances_by_path = list() - -/datum/objective_target_machine_handler/New() - . = ..() - RegisterSignal(SSdcs, COMSIG_GLOB_NEW_MACHINE, PROC_REF(on_machine_created)) - RegisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(finalise_valid_targets)) - -/// Adds a newly created machine to our list of machines, if we need it -/datum/objective_target_machine_handler/proc/on_machine_created(datum/source, obj/machinery/new_machine) - SIGNAL_HANDLER - new_machine.add_as_sabotage_target() - -/// Confirm that everything added to the list is a valid target, then prevent new targets from being added -/datum/objective_target_machine_handler/proc/finalise_valid_targets() - SIGNAL_HANDLER - for (var/machine_type in machine_instances_by_path) - for (var/obj/machinery/machine as anything in machine_instances_by_path[machine_type]) - var/turf/place = get_turf(machine) - if(!place || !is_station_level(place.z)) - machine_instances_by_path[machine_type] -= machine - continue - RegisterSignal(machine, COMSIG_QDELETING, PROC_REF(machine_destroyed)) - UnregisterSignal(SSdcs, COMSIG_GLOB_NEW_MACHINE) - -/datum/objective_target_machine_handler/proc/machine_destroyed(atom/machine) - SIGNAL_HANDLER - // Sadly can't do a direct typepath association because of some map helper subtypes - for (var/machine_type in machine_instances_by_path) - machine_instances_by_path[machine_type] -= machine - -// Mark valid machines as targets, add a new entry here if you add a new potential target - -/obj/machinery/telecomms/hub/add_as_sabotage_target() - return add_sabotage_machine(src, /obj/machinery/telecomms/hub) // Not always our specific type because of map helper subtypes - -/obj/machinery/rnd/server/add_as_sabotage_target() - return add_sabotage_machine(src, type) - -/obj/machinery/rnd/production/protolathe/department/add_as_sabotage_target() - return add_sabotage_machine(src, type) - -/obj/machinery/rnd/production/techfab/department/add_as_sabotage_target() - return add_sabotage_machine(src, type) - -/obj/machinery/mineral/ore_redemption/add_as_sabotage_target() - return add_sabotage_machine(src, type) diff --git a/code/modules/antagonists/traitor/objectives/steal.dm b/code/modules/antagonists/traitor/objectives/steal.dm deleted file mode 100644 index 69a665bc7c12..000000000000 --- a/code/modules/antagonists/traitor/objectives/steal.dm +++ /dev/null @@ -1,318 +0,0 @@ -/datum/traitor_objective_category/steal_item - name = "Steal Item" - objectives = list( - list( - /datum/traitor_objective/steal_item/low_risk = 1, - /datum/traitor_objective/destroy_item/low_risk = 1, - ) = 1, - /datum/traitor_objective/steal_item/somewhat_risky = 1, - list( - /datum/traitor_objective/destroy_item/very_risky = 1, - /datum/traitor_objective/steal_item/very_risky = 1, - ) = 1, - /datum/traitor_objective/steal_item/most_risky = 1 - ) - -GLOBAL_DATUM_INIT(steal_item_handler, /datum/objective_item_handler, new()) - -/datum/objective_item_handler - var/list/list/objectives_by_path - var/generated_items = FALSE - -/datum/objective_item_handler/New() - . = ..() - objectives_by_path = list() - for(var/datum/objective_item/item as anything in subtypesof(/datum/objective_item)) - objectives_by_path[initial(item.targetitem)] = list() - RegisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(save_items)) - RegisterSignal(SSdcs, COMSIG_GLOB_NEW_ITEM, PROC_REF(new_item_created)) - -/datum/objective_item_handler/proc/new_item_created(datum/source, obj/item/item) - SIGNAL_HANDLER - if(!generated_items) - item.add_stealing_item_objective() - return - var/typepath = item.add_stealing_item_objective() - if(typepath != null) - register_item(item, typepath) - -/// Registers all items that are potentially stealable and removes ones that aren't. -/// We still need to do things this way because on mapload, items may not be on the station until everything has finished loading. -/datum/objective_item_handler/proc/save_items() - SIGNAL_HANDLER - for(var/obj/item/typepath as anything in objectives_by_path) - var/list/obj_by_path_cache = objectives_by_path[typepath].Copy() - for(var/obj/item/object as anything in obj_by_path_cache) - register_item(object, typepath) - generated_items = TRUE - -/datum/objective_item_handler/proc/register_item(atom/object, typepath) - var/turf/place = get_turf(object) - if(!place || !is_station_level(place.z)) - objectives_by_path[typepath] -= object - return - RegisterSignal(object, COMSIG_QDELETING, PROC_REF(remove_item)) - -/datum/objective_item_handler/proc/remove_item(atom/source) - SIGNAL_HANDLER - for(var/typepath in objectives_by_path) - objectives_by_path[typepath] -= source - -/datum/traitor_objective/steal_item - name = "Steal %ITEM% and place a schematics scanner on it." - description = "Use the button below to materialize the schematic scanner within your hand, where you'll then be able to place it on the item. Additionally, you can keep it near you and let it scan for %TIME% minutes, and you will be rewarded with %PROGRESSION% reputation and %TC% telecrystals." - - progression_minimum = 20 MINUTES - - var/list/possible_items = list() - /// The current target item that we are stealing. - var/datum/objective_item/steal/target_item - /// A list of 2 elements, which contain the range that the time will be in. Represented in minutes. - var/hold_time_required = list(5, 15) - /// The current time fulfilled around the item - var/time_fulfilled = 0 - /// The maximum distance between the bug and the objective taker for time to count as fulfilled - var/max_distance = 4 - /// The bug that will be put onto the item - var/obj/item/traitor_bug/bug - /// Any special equipment that may be needed - var/list/special_equipment - /// Telecrystal reward increase per unit of time. - var/minutes_per_telecrystal = 3 - - /// Extra TC given for holding the item for the required duration of time. - var/extra_tc = 0 - /// Extra progression given for holding the item for the required duration of time. - var/extra_progression = 0 - - abstract_type = /datum/traitor_objective/steal_item - -/datum/traitor_objective/steal_item/low_risk - progression_minimum = 10 MINUTES - progression_maximum = 35 MINUTES - progression_reward = list(5 MINUTES, 10 MINUTES) - telecrystal_reward = 0 - minutes_per_telecrystal = 6 - - possible_items = list( - /datum/objective_item/steal/traitor/cargo_budget, - /datum/objective_item/steal/traitor/clown_shoes, - /datum/objective_item/steal/traitor/lawyers_badge, - /datum/objective_item/steal/traitor/chef_moustache, - /datum/objective_item/steal/traitor/pka, - ) - -/datum/traitor_objective/steal_item/somewhat_risky - progression_minimum = 20 MINUTES - progression_maximum = 50 MINUTES - progression_reward = 10 MINUTES - telecrystal_reward = 2 - - possible_items = list( - /datum/objective_item/steal/traitor/chief_engineer_belt - ) - -/datum/traitor_objective/steal_item/very_risky - progression_minimum = 30 MINUTES - progression_reward = 15 MINUTES - telecrystal_reward = 3 - - possible_items = list( - /datum/objective_item/steal/traitor/det_revolver, - ) - -/datum/traitor_objective/steal_item/most_risky - progression_minimum = 50 MINUTES - progression_reward = 20 MINUTES - telecrystal_reward = 5 - - possible_items = list( - /datum/objective_item/steal/traitor/captain_modsuit, - /datum/objective_item/steal/traitor/captain_spare, - ) - -/datum/traitor_objective/steal_item/most_risky/generate_objective(datum/mind/generating_for, list/possible_duplicates) - if(!handler.get_completion_count(/datum/traitor_objective/steal_item/very_risky)) - return FALSE - return ..() - -/datum/traitor_objective/steal_item/generate_objective(datum/mind/generating_for, list/possible_duplicates) - for(var/datum/traitor_objective/steal_item/objective as anything in possible_duplicates) - possible_items -= objective.target_item.type - while(length(possible_items)) - var/datum/objective_item/steal/target = pick_n_take(possible_items) - target = new target() - if(!target.valid_objective_for(list(generating_for), require_owner = TRUE)) - qdel(target) - continue - target_item = target - break - if(!target_item) - return FALSE - if(length(target_item.special_equipment)) - special_equipment = target_item.special_equipment - hold_time_required = rand(hold_time_required[1], hold_time_required[2]) - extra_progression += hold_time_required * (1 MINUTES) - extra_tc += round(hold_time_required / max(minutes_per_telecrystal, 0.1)) - replace_in_name("%ITEM%", target_item.name) - replace_in_name("%TIME%", hold_time_required) - replace_in_name("%TC%", extra_tc) - replace_in_name("%PROGRESSION%", DISPLAY_PROGRESSION(extra_progression)) - return TRUE - -/datum/traitor_objective/steal_item/ungenerate_objective() - STOP_PROCESSING(SSprocessing, src) - if(bug) - UnregisterSignal(bug, list(COMSIG_TRAITOR_BUG_PLANTED_OBJECT, COMSIG_TRAITOR_BUG_PRE_PLANTED_OBJECT)) - bug = null - -/datum/traitor_objective/steal_item/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(special_equipment) - buttons += add_ui_button("", "Pressing this will summon any extra special equipment you may need for the mission.", "tools", "summon_gear") - if(!bug) - buttons += add_ui_button("", "Pressing this will materialize a scanner in your hand, which you can place on the target item", "wifi", "summon_bug") - else if(bug.planted_on) - buttons += add_ui_button("[DisplayTimeText(time_fulfilled)]", "This tells you how much time you have spent around the target item after the scanner has been planted.", "clock", "none") - buttons += add_ui_button("Skip Time", "Pressing this will succeed the mission. You will not get the extra TC and progression.", "forward", "cash_out") - return buttons - -/datum/traitor_objective/steal_item/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("summon_bug") - if(bug) - return - bug = new(user.drop_location()) - user.put_in_hands(bug) - bug.balloon_alert(user, "the scanner materializes in your hand") - bug.target_object_type = target_item.targetitem - AddComponent(/datum/component/traitor_objective_register, bug, \ - fail_signals = list(COMSIG_QDELETING), \ - penalty = telecrystal_penalty) - RegisterSignal(bug, COMSIG_TRAITOR_BUG_PLANTED_OBJECT, PROC_REF(on_bug_planted)) - RegisterSignal(bug, COMSIG_TRAITOR_BUG_PRE_PLANTED_OBJECT, PROC_REF(handle_special_case)) - if("summon_gear") - if(!special_equipment) - return - for(var/item in special_equipment) - var/obj/item/new_item = new item(user.drop_location()) - user.put_in_hands(new_item) - user.balloon_alert(user, "the equipment materializes in your hand") - special_equipment = null - if("cash_out") - if(!bug.planted_on) - return - succeed_objective() - -/datum/traitor_objective/steal_item/process(seconds_per_tick) - var/mob/owner = handler.owner?.current - if(objective_state != OBJECTIVE_STATE_ACTIVE || !bug.planted_on) - return PROCESS_KILL - if(!owner) - fail_objective() - return PROCESS_KILL - if(get_dist(get_turf(owner), get_turf(bug)) > max_distance) - return - time_fulfilled += seconds_per_tick * (1 SECONDS) - if(time_fulfilled >= hold_time_required * (1 MINUTES)) - progression_reward += extra_progression - telecrystal_reward += extra_tc - succeed_objective() - return PROCESS_KILL - handler.on_update() - -/datum/traitor_objective/steal_item/proc/handle_special_case(obj/item/source, obj/item/target) - SIGNAL_HANDLER - if(istype(target, target_item.targetitem)) - if(!target_item.check_special_completion(target)) - return COMPONENT_FORCE_FAIL_PLACEMENT - return - - var/found = FALSE - for(var/typepath in target_item.valid_containers) - if(istype(target, typepath)) - found = TRUE - break - - if(!found) - return - - var/found_item = locate(target_item.targetitem) in target - if(!found_item || !target_item.check_special_completion(found_item)) - return COMPONENT_FORCE_FAIL_PLACEMENT - return COMPONENT_FORCE_PLACEMENT - -/datum/traitor_objective/steal_item/proc/on_bug_planted(obj/item/source, obj/item/location) - SIGNAL_HANDLER - if(objective_state == OBJECTIVE_STATE_ACTIVE) - START_PROCESSING(SSprocessing, src) - -/obj/item/traitor_bug - name = "suspicious device" - desc = "It looks dangerous." - item_flags = NOBLUDGEON - - icon = 'icons/obj/antags/syndicate_tools.dmi' - icon_state = "bug" - - /// The object on which this bug can be planted on. Has to be a type. - var/obj/target_object_type - /// The object this bug is currently planted on. - var/obj/planted_on - /// The time it takes to place this bug. - var/deploy_time = 10 SECONDS - -/obj/item/traitor_bug/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_EXAMINE_SKIP, INNATE_TRAIT) - -/obj/item/traitor_bug/examine(mob/user) - . = ..() - if(planted_on) - return - - if(IS_TRAITOR(user)) - if(target_object_type) - . += span_notice("This device must be placed by clicking on the [initial(target_object_type.name)] with it.") - . += span_notice("Remember, you may leave behind fingerprints or fibers on the device. Use soap or similar to scrub it clean to be safe!") - -/obj/item/traitor_bug/interact_with_atom(atom/movable/target, mob/living/user, list/modifiers) - if(!target_object_type || !ismovable(target)) - return NONE - if(SHOULD_SKIP_INTERACTION(target, src, user)) - return NONE - var/result = SEND_SIGNAL(src, COMSIG_TRAITOR_BUG_PRE_PLANTED_OBJECT, target) - if(!(result & COMPONENT_FORCE_PLACEMENT)) - if(result & COMPONENT_FORCE_FAIL_PLACEMENT || !istype(target, target_object_type)) - balloon_alert(user, "you can't attach this onto here!") - return ITEM_INTERACT_BLOCKING - if(!do_after(user, deploy_time, src, hidden = TRUE)) - return ITEM_INTERACT_BLOCKING - if(planted_on) - return ITEM_INTERACT_BLOCKING - forceMove(target) - target.vis_contents += src - vis_flags |= VIS_INHERIT_PLANE - planted_on = target - RegisterSignal(planted_on, COMSIG_QDELETING, PROC_REF(handle_planted_on_deletion)) - SEND_SIGNAL(src, COMSIG_TRAITOR_BUG_PLANTED_OBJECT, target) - return ITEM_INTERACT_SUCCESS - -/obj/item/traitor_bug/proc/handle_planted_on_deletion() - planted_on = null - -/obj/item/traitor_bug/Destroy() - if(planted_on) - vis_flags &= ~VIS_INHERIT_PLANE - planted_on.vis_contents -= src - return ..() - -/obj/item/traitor_bug/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - if(planted_on) - vis_flags &= ~VIS_INHERIT_PLANE - planted_on.vis_contents -= src - anchored = FALSE - UnregisterSignal(planted_on, COMSIG_QDELETING) - planted_on = null diff --git a/code/modules/antagonists/traitor/traitor_objective.dm b/code/modules/antagonists/traitor/traitor_objective.dm deleted file mode 100644 index d60820c3fcee..000000000000 --- a/code/modules/antagonists/traitor/traitor_objective.dm +++ /dev/null @@ -1,264 +0,0 @@ -/// A traitor objective. Traitor objectives should not be deleted after they have been created and established, only failed. -/// If a traitor objective needs to be removed from the failed/completed objective list of their handler, then you are doing something wrong -/// and you should reconsider. When an objective is failed/completed, that is final and the only way you can change that is by refactoring the code. -/datum/traitor_objective - /// The name of the traitor objective - var/name = "traitor objective" - /// The description of the traitor objective - var/description = "this is a traitor objective" - /// The uplink handler holder to give the progression and telecrystals to. - var/datum/uplink_handler/handler - /// The minimum required progression points for this objective - var/progression_minimum = null - /// The maximum progression before this objective cannot appear anymore - var/progression_maximum = INFINITY - /// The progression that is rewarded from completing this traitor objective. Can either be a list of list(min, max) or a direct value - var/progression_reward = 0 MINUTES - /// The telecrystals that are rewarded from completing this traitor objective. Can either be a list of list(min,max) or a direct value - var/telecrystal_reward = 0 - /// TC penalty for failing an objective or cancelling it - var/telecrystal_penalty = 1 - /// The time at which this objective was first created - var/time_of_creation = 0 - /// The time at which this objective was completed - var/time_of_completion = 0 - /// The current state of this objective - var/objective_state = OBJECTIVE_STATE_INACTIVE - /// Whether this objective was forced upon by an admin. Won't get autocleared by the traitor subsystem if progression surpasses an amount - var/forced = FALSE - /// Whether this objective was skipped by going from an inactive state to a failed state. - var/skipped = FALSE - - /// Determines how influential global progression will affect this objective. Set to 0 to disable. - var/global_progression_influence_intensity = 0.1 - /// Determines how great the deviance has to be before progression starts to get reduced. - var/global_progression_deviance_required = 1 - /// Determines the minimum and maximum progression this objective can be worth as a result of being influenced by global progression - /// Should only be smaller than or equal to 1 - var/global_progression_limit_coeff = 0.6 - /// The deviance coefficient used to determine the randomness of the progression rewards. - var/progression_cost_coeff_deviance = 0.05 - /// This gets added onto the coeff when calculating the updated progression cost. Used for variability and a slight bit of randomness - var/progression_cost_coeff = 0 - /// The percentage that this objective has been increased or decreased by as a result of progression. Used by the UI - var/original_progression = 0 - /// Abstract type that won't be included as a possible objective - var/abstract_type = /datum/traitor_objective - /// The duplicate type that will be used to check for duplicates. - /// If undefined, this will either take from the abstract type or the type of the objective itself - var/duplicate_type = null - /// Used only in unit testing. Can be used to explicitly skip the progression_reward and telecrystal_reward check for non-abstract objectives. - /// Useful for final objectives as they don't need a reward. - var/needs_reward = TRUE - -/// Returns a list of variables that can be changed by config, allows for balance through configuration. -/// It is not recommended to finetweak any values of objectives on your server. -/datum/traitor_objective/proc/supported_configuration_changes() - return list( - NAMEOF(src, global_progression_influence_intensity), - NAMEOF(src, global_progression_deviance_required), - NAMEOF(src, global_progression_limit_coeff) - ) - -/// Replaces a word in the name of the proc. Also does it for the description -/datum/traitor_objective/proc/replace_in_name(replace, word) - name = replacetext(name, replace, word) - description = replacetext(description, replace, word) - -/datum/traitor_objective/New(datum/uplink_handler/handler) - . = ..() - src.handler = handler - src.time_of_creation = world.time - apply_configuration() - if(SStraitor.generate_objectives) - if(islist(telecrystal_reward)) - telecrystal_reward = rand(telecrystal_reward[1], telecrystal_reward[2]) - if(islist(progression_reward)) - progression_reward = rand(progression_reward[1], progression_reward[2]) - else - if(!islist(telecrystal_reward)) - telecrystal_reward = list(telecrystal_reward, telecrystal_reward) - if(!islist(progression_reward)) - progression_reward = list(progression_reward, progression_reward) - progression_cost_coeff = (rand()*2 - 1) * progression_cost_coeff_deviance - -/datum/traitor_objective/proc/apply_configuration() - if(!length(SStraitor.configuration_data)) - return - var/datum/traitor_objective/current_type = type - var/list/types = list() - while(current_type != /datum/traitor_objective) - types += current_type - current_type = type2parent(current_type) - types += /datum/traitor_objective - // Reverse the list direction - reverse_range(types) - var/list/supported_configurations = supported_configuration_changes() - for(var/typepath in types) - if(!(typepath in SStraitor.configuration_data)) - continue - var/list/changes = SStraitor.configuration_data[typepath] - for(var/variable in changes) - if(!(variable in supported_configurations)) - continue - vars[variable] = changes[variable] - - -/// Updates the progression reward, scaling it depending on their current progression compared against the global progression -/datum/traitor_objective/proc/update_progression_reward() - if(!SStraitor.generate_objectives) - return - progression_reward = original_progression - if(global_progression_influence_intensity <= 0) - return - var/minimum_progression = progression_reward * global_progression_limit_coeff - var/maximum_progression = progression_reward * (2-global_progression_limit_coeff) - var/deviance = (SStraitor.current_global_progression - handler.progression_points) / SStraitor.progression_scaling_deviance - if(abs(deviance) < global_progression_deviance_required) - return - if(abs(deviance) == deviance) // If it is positive - deviance = deviance - global_progression_deviance_required - else - deviance = deviance + global_progression_deviance_required - var/coeff = NUM_E ** (global_progression_influence_intensity * abs(deviance)) - 1 - if(abs(deviance) != deviance) - coeff *= -1 - - // This has less of an effect as the coeff gets nearer to -1. Is linear - coeff += progression_cost_coeff * min(max(1 - abs(coeff), 1), 0) - - - progression_reward = clamp( - progression_reward + progression_reward * coeff, - minimum_progression, - maximum_progression - ) - -/datum/traitor_objective/Destroy(force) - handler = null - return ..() - -/// Called whenever the objective is about to be generated. Bypassed by forcefully adding objectives. -/// Returning false or true will do the same as the generate_objective proc. -/datum/traitor_objective/proc/can_generate_objective(datum/mind/generating_for, list/possible_duplicates) - return TRUE - -/// Called when the objective should be generated. Should return if the objective has been successfully generated. -/// If false is returned, the objective will be removed as a potential objective for the traitor it is being generated for. -/// This is only temporary, it will run the proc again when objectives are generated for the traitor again. -/datum/traitor_objective/proc/generate_objective(datum/mind/generating_for, list/possible_duplicates) - return FALSE - -/// Used to clean up signals and stop listening to states. -/datum/traitor_objective/proc/ungenerate_objective() - return - -/datum/traitor_objective/proc/get_log_data() - return list( - "type" = type, - "owner" = handler.owner.key, - "name" = name, - "description" = description, - "telecrystal_reward" = telecrystal_reward, - "progression_reward" = progression_reward, - "original_progression" = original_progression, - "objective_state" = objective_state, - "forced" = forced, - "time_of_creation" = time_of_creation, - ) - -/// Converts the type into a useful debug string to be used for logging and debug display. -/datum/traitor_objective/proc/to_debug_string() - return "[type] (Name: [name], TC: [telecrystal_reward], Progression: [progression_reward], Time of creation: [time_of_creation])" - -/datum/traitor_objective/proc/save_objective() - SSblackbox.record_feedback("associative", "traitor_objective", 1, get_log_data()) - -/// Used to handle cleaning up the objective. -/datum/traitor_objective/proc/handle_cleanup() - time_of_completion = world.time - ungenerate_objective() - if(objective_state == OBJECTIVE_STATE_INACTIVE) - skipped = TRUE - handler.complete_objective(src) // Remove this objective immediately, no reason to keep it around. It isn't even active - -/// Used to fail objectives. Players can clear completed objectives in the UI -/datum/traitor_objective/proc/fail_objective(penalty_cost = 0, trigger_update = TRUE) - // Don't let players succeed already succeeded/failed objectives - if(objective_state != OBJECTIVE_STATE_INACTIVE && objective_state != OBJECTIVE_STATE_ACTIVE) - return - SEND_SIGNAL(src, COMSIG_TRAITOR_OBJECTIVE_FAILED) - handle_cleanup() - log_traitor("[key_name(handler.owner)] [objective_state == OBJECTIVE_STATE_INACTIVE? "missed" : "failed"] [to_debug_string()]") - if(penalty_cost) - handler.telecrystals -= penalty_cost - objective_state = OBJECTIVE_STATE_FAILED - else - objective_state = OBJECTIVE_STATE_INVALID - save_objective() - if(trigger_update) - handler.on_update() // Trigger an update to the UI - -/// Used to succeed objectives. Allows the player to cash it out in the UI. -/datum/traitor_objective/proc/succeed_objective() - // Don't let players succeed already succeeded/failed objectives - if(objective_state != OBJECTIVE_STATE_INACTIVE && objective_state != OBJECTIVE_STATE_ACTIVE) - return - SEND_SIGNAL(src, COMSIG_TRAITOR_OBJECTIVE_COMPLETED) - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED, src) - handle_cleanup() - log_traitor("[key_name(handler.owner)] [objective_state == OBJECTIVE_STATE_INACTIVE? "missed" : "completed"] [to_debug_string()]") - objective_state = OBJECTIVE_STATE_COMPLETED - save_objective() - handler.on_update() // Trigger an update to the UI - -/// Called by player input, do not call directly. Validates whether the objective is finished and pays out the handler if it is. -/datum/traitor_objective/proc/finish_objective(mob/user) - switch(objective_state) - if(OBJECTIVE_STATE_FAILED, OBJECTIVE_STATE_INVALID) - user.playsound_local(get_turf(user), 'sound/traitor/objective_failed.ogg', vol = 100, vary = FALSE, channel = CHANNEL_TRAITOR) - return TRUE - if(OBJECTIVE_STATE_COMPLETED) - user.playsound_local(get_turf(user), 'sound/traitor/objective_success.ogg', vol = 100, vary = FALSE, channel = CHANNEL_TRAITOR) - completion_payout() - return TRUE - return FALSE - -/// Called when rewards should be given to the user. -/datum/traitor_objective/proc/completion_payout() - handler.progression_points += progression_reward - handler.telecrystals += telecrystal_reward - -/// Used for sending data to the uplink UI -/datum/traitor_objective/proc/uplink_ui_data(mob/user) - return list( - "name" = name, - "description" = description, - "progression_minimum" = progression_minimum, - "progression_reward" = progression_reward, - "telecrystal_reward" = telecrystal_reward, - "ui_buttons" = generate_ui_buttons(user), - "objective_state" = objective_state, - "original_progression" = original_progression, - "telecrystal_penalty" = telecrystal_penalty, - ) - -/datum/traitor_objective/proc/on_objective_taken(mob/user) - SStraitor.on_objective_taken(src) - log_traitor("[key_name(handler.owner)] has taken an objective: [to_debug_string()]") - -/// Used for generating the UI buttons for the UI. Use ui_perform_action to respond to clicks. -/datum/traitor_objective/proc/generate_ui_buttons(mob/user) - return - -/datum/traitor_objective/proc/add_ui_button(name, tooltip, icon, action) - return list(list( - "name" = name, - "tooltip" = tooltip, - "icon" = icon, - "action" = action, - )) - -/// Return TRUE to trigger a UI update -/datum/traitor_objective/proc/ui_perform_action(mob/user, action) - return TRUE diff --git a/code/modules/antagonists/traitor/uplink_handler.dm b/code/modules/antagonists/traitor/uplink_handler.dm index 78e6e2da41f7..2801ef29aad1 100644 --- a/code/modules/antagonists/traitor/uplink_handler.dm +++ b/code/modules/antagonists/traitor/uplink_handler.dm @@ -20,24 +20,6 @@ var/list/item_stock = list(UPLINK_SHARED_STOCK_KITS = 1 , UPLINK_SHARED_STOCK_SURPLUS = 1) /// Extra stuff that can be purchased by an uplink, regardless of flag. var/list/extra_purchasable = list() - /// Whether this uplink handler has objectives. - var/has_objectives = TRUE - /// Whether this uplink handler can TAKE objectives. - var/can_take_objectives = TRUE - /// The maximum number of objectives that can be taken - var/maximum_active_objectives = 2 - /// The maximum number of potential objectives that can exist. - var/maximum_potential_objectives = 6 - /// Current objectives taken - var/list/active_objectives = list() - /// Potential objectives that can be taken - var/list/potential_objectives = list() - /// Objectives that have been completed. - var/list/completed_objectives = list() - /// All objectives assigned by type to handle any duplicates - var/list/potential_duplicate_objectives = list() - /// Text of the final objective, once assigned. Used for uplink data and traitor greentext. Empty string means not yet reached. - var/final_objective = "" /// Objectives that must be completed for traitor greentext. Set by the traitor datum. var/list/primary_objectives /// The role that this uplink handler is associated to. @@ -55,10 +37,6 @@ ///Reference to a contractor hub that the infiltrator can run, if they purchase it. var/datum/contractor_hub/contractor_hub -/datum/uplink_handler/New() - . = ..() - maximum_potential_objectives = CONFIG_GET(number/maximum_potential_objectives) - /datum/uplink_handler/Destroy(force) can_replace_objectives = null replace_objectives = null @@ -140,132 +118,11 @@ on_update() return TRUE +///Helper to add telecrystals to the uplink handler, calling set_telecrystals. +/datum/uplink_handler/proc/add_telecrystals(amount) + set_telecrystals(telecrystals + amount) -/// Generates objectives for this uplink handler -/datum/uplink_handler/proc/generate_objectives() - var/potential_objectives_left = maximum_potential_objectives - (length(potential_objectives) + length(active_objectives)) - var/list/objectives = SStraitor.category_handler.get_possible_objectives(progression_points) - if(!length(objectives)) - return - while(length(objectives) && potential_objectives_left > 0) - var/objective_typepath = pick_weight(objectives) - var/list/target_list = objectives - while(islist(objective_typepath)) - if(!length(objective_typepath)) - // Need to wrap this in a list or else it list unrolls and the list doesn't actually get removed. - // Thank you byond, very cool! - target_list -= list(objective_typepath) - break - target_list = objective_typepath - objective_typepath = pick_weight(objective_typepath) - if(islist(objective_typepath) || !objective_typepath) - continue - if(!try_add_objective(objective_typepath)) - target_list -= objective_typepath - continue - potential_objectives_left-- - on_update() - -/datum/uplink_handler/proc/try_add_objective(datum/traitor_objective/objective_typepath, force = FALSE) - var/datum/traitor_objective/objective = new objective_typepath(src) - var/duplicate_typepath = objective.duplicate_type - if(!duplicate_typepath) - if(objective.abstract_type != /datum/traitor_objective) - duplicate_typepath = objective.abstract_type - else - duplicate_typepath = objective_typepath - - if(!force && !objective.can_generate_objective(owner, potential_duplicate_objectives[duplicate_typepath])) - qdel(objective) - return - - var/should_abort = SEND_SIGNAL(objective, COMSIG_TRAITOR_OBJECTIVE_PRE_GENERATE, owner, potential_duplicate_objectives[duplicate_typepath]) & COMPONENT_TRAITOR_OBJECTIVE_ABORT_GENERATION - if(should_abort || !objective.generate_objective(owner, potential_duplicate_objectives[duplicate_typepath])) - qdel(objective) - return - if(!handle_duplicate(objective)) - qdel(objective) - return - objective.forced = force - log_traitor("[key_name(owner)] has received a potential objective: [objective.to_debug_string()] | Forced: [force]") - objective.original_progression = objective.progression_reward - objective.update_progression_reward() - potential_objectives += objective - SStraitor.add_objective_to_list(objective, SStraitor.all_objectives_by_type) - return objective - -/datum/uplink_handler/proc/handle_duplicate(datum/traitor_objective/potential_duplicate) - if(!istype(potential_duplicate)) - return FALSE - - var/datum/traitor_objective/current_type = potential_duplicate.type - var/list/added_types = list() - while(current_type != /datum/traitor_objective) - if(!potential_duplicate_objectives[current_type]) - potential_duplicate_objectives[current_type] = list(potential_duplicate) - else - potential_duplicate_objectives[current_type] += potential_duplicate - - added_types += current_type - current_type = type2parent(current_type) - return TRUE - -/datum/uplink_handler/proc/get_completion_count(datum/traitor_objective/type) - var/amount_completed = 0 - for(var/datum/traitor_objective/objective as anything in potential_duplicate_objectives[type]) - if(objective.objective_state == OBJECTIVE_STATE_COMPLETED) - amount_completed += 1 - return amount_completed - -/datum/uplink_handler/proc/get_completion_progression(datum/traitor_objective/type) - var/total_progression = 0 - for(var/datum/traitor_objective/objective as anything in completed_objectives) - if(objective.objective_state == OBJECTIVE_STATE_COMPLETED) - total_progression += objective.progression_reward - return total_progression - -/// Used to complete objectives, failed or successful. -/datum/uplink_handler/proc/complete_objective(datum/traitor_objective/to_remove) - if(to_remove in completed_objectives) - return - - potential_objectives -= to_remove - active_objectives -= to_remove - completed_objectives += to_remove - update_objectives() - generate_objectives() - -/// Updates the objectives on the uplink and deletes -/datum/uplink_handler/proc/update_objectives() - var/list/objectives_copy = potential_objectives + active_objectives - for(var/datum/traitor_objective/objective as anything in objectives_copy) - if(progression_points > objective.progression_maximum && !objective.forced && objective.objective_state != OBJECTIVE_STATE_ACTIVE) - objective.fail_objective(trigger_update = FALSE) - continue - objective.update_progression_reward() - -/datum/uplink_handler/proc/abort_objective(datum/traitor_objective/to_abort) - if(istype(to_abort, /datum/traitor_objective/ultimate)) - return - if(to_abort.objective_state != OBJECTIVE_STATE_ACTIVE) - return - to_abort.fail_objective(penalty_cost = to_abort.telecrystal_penalty) - -/datum/uplink_handler/proc/take_objective(mob/user, datum/traitor_objective/to_take) - if(!(to_take in potential_objectives)) - return - - user.playsound_local(get_turf(user), 'sound/traitor/objective_taken.ogg', vol = 100, vary = FALSE, channel = CHANNEL_TRAITOR) - to_take.on_objective_taken(user) - to_take.objective_state = OBJECTIVE_STATE_ACTIVE - potential_objectives -= to_take - active_objectives += to_take +///Sets how many telecrystals the uplink handler has, then updates the UI for any players watching. +/datum/uplink_handler/proc/set_telecrystals(amount) + telecrystals = amount on_update() - -/datum/uplink_handler/proc/ui_objective_act(mob/user, datum/traitor_objective/to_act_on, action) - if(!(to_act_on in active_objectives)) - return - if(to_act_on.objective_state != OBJECTIVE_STATE_ACTIVE) - return - - to_act_on.ui_perform_action(user, action) diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm index fee767142c40..2dc2a32d1259 100644 --- a/code/modules/events/stray_cargo.dm +++ b/code/modules/events/stray_cargo.dm @@ -160,7 +160,13 @@ var/pack_telecrystals = tgui_input_number(usr, "Please input crate's value in telecrystals.", "Set Telecrystals.", 30) if(isnull(pack_telecrystals)) return ADMIN_CANCEL_EVENT - var/list/possible_uplinks = list("Traitor" = UPLINK_TRAITORS, "Nuke Op" = UPLINK_NUKE_OPS, "Clown Op" = UPLINK_CLOWN_OPS) + var/list/possible_uplinks = list( + "Traitor" = UPLINK_TRAITORS, + "Nuke Op" = UPLINK_NUKE_OPS, + "Clown Op" = UPLINK_CLOWN_OPS, + "Lone Op" = UPLINK_LONE_OP, + //"Spy" = UPLINK_SPY, + ) var/uplink_type = tgui_input_list(usr, "Choose uplink to draw items from.", "Choose uplink type.", possible_uplinks) var/selection if(!isnull(uplink_type)) diff --git a/code/modules/mapfluff/ruins/spaceruin_code/garbagetruck.dm b/code/modules/mapfluff/ruins/spaceruin_code/garbagetruck.dm new file mode 100644 index 000000000000..e48bcfd0cebf --- /dev/null +++ b/code/modules/mapfluff/ruins/spaceruin_code/garbagetruck.dm @@ -0,0 +1,103 @@ +/obj/item/eyesnatcher + name = "portable eyeball extractor" + desc = "An overly complicated device that can pierce target's skull and extract their eyeballs if enough brute force is applied." + icon = 'icons/obj/medical/surgery_tools.dmi' + icon_state = "eyesnatcher" + base_icon_state = "eyesnatcher" + inhand_icon_state = "hypo" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + ///Whether it's been used to steal a pair of eyes already. + var/used = FALSE + +/obj/item/eyesnatcher/update_icon_state() + . = ..() + icon_state = "[base_icon_state][used ? "-used" : ""]" + +/obj/item/eyesnatcher/attack(mob/living/carbon/human/target, mob/living/user, params) + if(used || !istype(target) || !target.Adjacent(user)) //Works only once, no TK use + return ..() + + var/obj/item/organ/eyes/eyeballies = target.get_organ_slot(ORGAN_SLOT_EYES) + var/obj/item/bodypart/head/head = target.get_bodypart(BODY_ZONE_HEAD) + + if(!head || !eyeballies || target.is_eyes_covered()) + return ..() + var/eye_snatch_enthusiasm = 5 SECONDS + if(HAS_MIND_TRAIT(user, TRAIT_MORBID)) + eye_snatch_enthusiasm *= 0.7 + user.do_attack_animation(target, used_item = src) + target.visible_message( + span_warning("[user] presses [src] against [target]'s skull!"), + span_userdanger("[user] presses [src] against your skull!")) + if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) + return + + to_chat(target, span_userdanger("You feel something forcing its way into your skull!")) + balloon_alert(user, "applying pressure...") + if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) + return + + var/min_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = src) + var/max_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = src) + + target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(min_wound, max_wound + 10), attacking_item = src) + target.visible_message( + span_danger("[src] pierces through [target]'s skull, horribly mutilating their eyes!"), + span_userdanger("Something penetrates your skull, horribly mutilating your eyes! Holy fuck!"), + span_hear("You hear a sickening sound of metal piercing flesh!") + ) + eyeballies.apply_organ_damage(eyeballies.maxHealth) + target.emote("scream") + playsound(target, 'sound/effects/wounds/crackandbleed.ogg', 100) + log_combat(user, target, "cracked the skull of (eye snatching)", src) + + if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) + return + + if(!target.is_blind()) + to_chat(target, span_userdanger("You suddenly go blind!")) + if(prob(1)) + to_chat(target, span_notice("At least you got a new pirate-y look out of it...")) + var/obj/item/clothing/glasses/eyepatch/new_patch = new(target.loc) + target.equip_to_slot_if_possible(new_patch, ITEM_SLOT_EYES, disable_warning = TRUE) + + to_chat(user, span_notice("You successfully extract [target]'s eyeballs.")) + playsound(target, 'sound/items/handling/surgery/retractor2.ogg', 100, TRUE) + playsound(target, 'sound/effects/pop.ogg', 100, TRAIT_MUTE) + eyeballies.Remove(target) + eyeballies.forceMove(get_turf(target)) + notify_ghosts( + "[target] has just had their eyes snatched!", + source = target, + header = "Ouch!", + ) + target.emote("scream") + if(prob(20)) + target.emote("cry") + used = TRUE + update_appearance(UPDATE_ICON) + +/obj/item/eyesnatcher/examine(mob/user) + . = ..() + if(used) + . += span_notice("It has been used up.") + +/obj/item/eyesnatcher/proc/eyeballs_exist(obj/item/organ/eyes/eyeballies, obj/item/bodypart/head/head, mob/living/carbon/human/target) + if(!eyeballies || QDELETED(eyeballies)) + return FALSE + if(!head || QDELETED(head)) + return FALSE + + if(eyeballies.owner != target) + return FALSE + var/obj/item/organ/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) + //got different eyes or doesn't own the head... somehow + if(head.owner != target || eyes != eyeballies) + return FALSE + + return TRUE diff --git a/code/modules/modular_computers/computers/item/disks/virus_disk.dm b/code/modules/modular_computers/computers/item/disks/virus_disk.dm index e3eac7736f50..8ddb3c4fca39 100644 --- a/code/modules/modular_computers/computers/item/disks/virus_disk.dm +++ b/code/modules/modular_computers/computers/item/disks/virus_disk.dm @@ -147,11 +147,8 @@ target_mind = pick(backup_players) hidden_uplink = target.AddComponent(/datum/component/uplink, target_mind, enabled = TRUE, starting_tc = telecrystals, has_progression = TRUE) hidden_uplink.unlock_code = unlock_code - hidden_uplink.uplink_handler.has_objectives = TRUE hidden_uplink.uplink_handler.owner = target_mind - hidden_uplink.uplink_handler.can_take_objectives = FALSE hidden_uplink.uplink_handler.progression_points = min(SStraitor.current_global_progression, current_progression) - hidden_uplink.uplink_handler.generate_objectives() SStraitor.register_uplink_handler(hidden_uplink.uplink_handler) else hidden_uplink.add_telecrystals(telecrystals) diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm index 1315ca3a81d2..0a6edf893be3 100644 --- a/code/modules/paperwork/paper_cutter.dm +++ b/code/modules/paperwork/paper_cutter.dm @@ -113,9 +113,12 @@ /obj/item/papercutter/attackby(obj/item/inserted_item, mob/user, params) if(istype(inserted_item, /obj/item/paper)) if(is_type_in_list(inserted_item, list( - /obj/item/paper/paperslip, /obj/item/paper/report, /obj/item/paper/fake_report, - /obj/item/paper/calling_card, /obj/item/paper/pamphlet, /obj/item/paper/holy_writ) - )) + /obj/item/paper/fake_report, + /obj/item/paper/holy_writ, + /obj/item/paper/pamphlet, + /obj/item/paper/paperslip, + /obj/item/paper/report, + ))) balloon_alert(user, "won't fit!") return if(stored_paper) diff --git a/code/modules/station_goals/meteor_shield.dm b/code/modules/station_goals/meteor_shield.dm index 84a61395a4b9..6c3b68ba264a 100644 --- a/code/modules/station_goals/meteor_shield.dm +++ b/code/modules/station_goals/meteor_shield.dm @@ -135,8 +135,6 @@ to_chat(user, span_warning("The last satellite emagged needs [DisplayTimeText(COOLDOWN_TIMELEFT(src, shared_emag_cooldown))] to recalibrate first. Emagging another so soon could damage the satellite network.")) return FALSE var/cooldown_applied = METEOR_SHIELD_EMAG_COOLDOWN - if(istype(emag_card, /obj/item/card/emag/meteor_shield_recalibrator)) - cooldown_applied /= 3 COOLDOWN_START(src, shared_emag_cooldown, cooldown_applied) obj_flags |= EMAGGED to_chat(user, span_notice("You access the satellite's debug mode and it begins emitting a strange signal, increasing the chance of meteor strikes.")) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 1f7e96231d68..26d842a62008 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -216,7 +216,6 @@ #include "mutant_organs.dm" #include "novaflower_burn.dm" #include "nuke_cinematic.dm" -#include "objectives.dm" #include "omnitools.dm" #include "operating_table.dm" #include "orderable_items.dm" diff --git a/code/modules/unit_tests/objectives.dm b/code/modules/unit_tests/objectives.dm deleted file mode 100644 index 30263c0f0786..000000000000 --- a/code/modules/unit_tests/objectives.dm +++ /dev/null @@ -1,49 +0,0 @@ -/datum/unit_test/objectives_category/Run() - var/datum/traitor_category_handler/category_handler = allocate(/datum/traitor_category_handler) - var/list/objectives_that_exist = list() - for(var/datum/traitor_objective_category/category as anything in category_handler.all_categories) - for(var/value in category.objectives) - TEST_ASSERT(isnum(category.objectives[value]), "[category.type] does not have a valid format for its objectives as an objective category! ([value] requires a weight to be assigned to it)") - if(islist(value)) - recursive_check_list(category.type, value, objectives_that_exist) - else - objectives_that_exist += value - - SStraitor.generate_objectives = FALSE - for(var/datum/traitor_objective/objective_typepath as anything in subtypesof(/datum/traitor_objective)) - var/datum/traitor_objective/objective = allocate(objective_typepath) - if(objective.abstract_type == objective_typepath) - // In this case, we don't want abstract types to define values that should be defined on non-abstract types - // Nor do we want abstract types to appear in the pool of traitor objectives. - if(objective_typepath in objectives_that_exist) - TEST_FAIL("[objective_typepath] is in a traitor category and is an abstract type! Please remove it from the [/datum/traitor_objective_category].") - // Since we didn't generate the objective, the rewards are going to be in list form: (min, max) - if(!reward_is_zero(objective.progression_reward)) - TEST_FAIL("[objective_typepath] has set a progression reward as an abstract type! Please define progression rewards on non-abstract types rather than abstract types.") - // Since we didn't generate the objective, the rewards are going to be in list form: (min, max) - if(!reward_is_zero(objective.telecrystal_reward)) - TEST_FAIL("[objective_typepath] has set a telecrystal reward as an abstract type! Please define telecrystal rewards on non-abstract types rather than abstract types.") - continue - if(!(objective_typepath in objectives_that_exist)) - TEST_FAIL("[objective_typepath] is not in a traitor category and isn't an abstract type! Place it into a [/datum/traitor_objective_category] or remove it from code.") - if(objective.progression_minimum == null) - TEST_FAIL("[objective_typepath] has not defined a minimum progression level and isn't an abstract type! Please define the progression minimum variable on the datum") - if(objective.needs_reward && reward_is_zero(objective.progression_reward) && reward_is_zero(objective.telecrystal_reward)) - TEST_FAIL("[objective_typepath] has not set either a progression reward or a telecrystal reward! Please set either a telecrystal or progression reward for this objective.") - -/// Returns whether the reward specified (in format (min, max)) is zero or not. -/datum/unit_test/objectives_category/proc/reward_is_zero(list/reward) - return (reward[1] == 0 && reward[2] == 0) - -/datum/unit_test/objectives_category/Destroy() - SStraitor.generate_objectives = TRUE - return ..() - - -/datum/unit_test/objectives_category/proc/recursive_check_list(base_type, list/to_check, list/to_add_to) - for(var/value in to_check) - TEST_ASSERT(isnum(to_check[value]), "[base_type] does not have a valid format for its objectives as an objective category! ([value] requires a weight to be assigned to it)") - if(islist(value)) - recursive_check_list(base_type, value, to_add_to) - else - to_add_to += value diff --git a/code/modules/unit_tests/traitor.dm b/code/modules/unit_tests/traitor.dm index 16098a9227c4..e79f5129e594 100644 --- a/code/modules/unit_tests/traitor.dm +++ b/code/modules/unit_tests/traitor.dm @@ -22,11 +22,3 @@ var/datum/antagonist/traitor/traitor = mind.add_antag_datum(/datum/antagonist/traitor) if(!traitor.uplink_handler) TEST_FAIL("[job_name] when made traitor does not have a proper uplink created when spawned in!") - for(var/datum/traitor_objective/objective_typepath as anything in subtypesof(/datum/traitor_objective)) - if(initial(objective_typepath.abstract_type) == objective_typepath) - continue - var/datum/traitor_objective/objective = allocate(objective_typepath, traitor.uplink_handler) - try - objective.generate_objective(mind, list()) - catch(var/exception/exception) - TEST_FAIL("[objective_typepath] failed to generate their objective. Reason: [exception.name] [exception.file]:[exception.line]\n[exception.desc]") diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm index 4d539be433de..d50766a56a95 100644 --- a/code/modules/uplink/uplink_devices.dm +++ b/code/modules/uplink/uplink_devices.dm @@ -80,34 +80,6 @@ var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) hidden_uplink.name = "dusty radio" -// Uplink subtype used as replacement uplink -/obj/item/uplink/replacement - lockable_uplink = TRUE - -/obj/item/uplink/replacement/Initialize(mapload, owner, tc_amount = 10, datum/uplink_handler/uplink_handler_override = null) - . = ..() - var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) - var/mob/living/replacement_needer = owner - if(!istype(replacement_needer)) - return - var/datum/antagonist/traitor/traitor_datum = replacement_needer?.mind.has_antag_datum(/datum/antagonist/traitor) - hidden_uplink.unlock_code = traitor_datum?.replacement_uplink_code - become_hearing_sensitive() - -/obj/item/uplink/replacement/screwdriver_act_secondary(mob/living/user, obj/item/tool) - tool.play_tool_sound(src) - balloon_alert(user, "deconstructing...") - if (!do_after(user, 3 SECONDS, target = src)) - return FALSE - qdel(src) - return TRUE - -/obj/item/uplink/replacement/examine(mob/user) - . = ..() - if(!IS_TRAITOR(user)) - return - . += span_notice("You can destroy this device with a screwdriver.") - // Multitool uplink /obj/item/multitool/uplink/Initialize(mapload, owner, tc_amount = 20, datum/uplink_handler/uplink_handler_override = null) . = ..() diff --git a/code/modules/uplink/uplink_items/contractor.dm b/code/modules/uplink/uplink_items/contractor.dm index 1364f8b088bb..7bf37f1b777e 100644 --- a/code/modules/uplink/uplink_items/contractor.dm +++ b/code/modules/uplink/uplink_items/contractor.dm @@ -13,7 +13,7 @@ item = /obj/item/storage/box/syndicate/contract_kit category = /datum/uplink_category/contractor cost = 20 - purchasable_from = UPLINK_INFILTRATORS + purchasable_from = UPLINK_TRAITORS /datum/uplink_item/bundles_tc/contract_kit/purchase(mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) . = ..() diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm index d8bead5da678..d639b78fafe1 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -581,7 +581,8 @@ along with slurred speech, aggression, and the ability to infect others with this agent." item = /obj/item/storage/box/syndie_kit/romerol cost = 25 - purchasable_from = UPLINK_CLOWN_OPS|UPLINK_NUKE_OPS + progression_minimum = 30 MINUTES + purchasable_from = UPLINK_ALL_SYNDIE_OPS | UPLINK_TRAITORS // Don't give this to spies cant_discount = TRUE // Modsuits diff --git a/tgstation.dme b/tgstation.dme index bf21a51d4ab5..9201bd23fa6c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2714,7 +2714,6 @@ #include "code\game\objects\structures\spawner.dm" #include "code\game\objects\structures\spirit_board.dm" #include "code\game\objects\structures\stairs.dm" -#include "code\game\objects\structures\syndicate_uplink_beacon.dm" #include "code\game\objects\structures\table_frames.dm" #include "code\game\objects\structures\tables_racks.dm" #include "code\game\objects\structures\tank_dispenser.dm" @@ -3267,40 +3266,13 @@ #include "code\modules\antagonists\spiders\spiders.dm" #include "code\modules\antagonists\survivalist\survivalist.dm" #include "code\modules\antagonists\syndicate_monkey\syndicate_monkey.dm" -#include "code\modules\antagonists\traitor\balance_helper.dm" #include "code\modules\antagonists\traitor\datum_traitor.dm" -#include "code\modules\antagonists\traitor\objective_category.dm" -#include "code\modules\antagonists\traitor\traitor_objective.dm" #include "code\modules\antagonists\traitor\uplink_handler.dm" #include "code\modules\antagonists\traitor\components\demoraliser.dm" -#include "code\modules\antagonists\traitor\components\traitor_objective_helpers.dm" -#include "code\modules\antagonists\traitor\components\traitor_objective_limit_per_time.dm" -#include "code\modules\antagonists\traitor\components\traitor_objective_mind_tracker.dm" #include "code\modules\antagonists\traitor\contractor\contract_teammate.dm" #include "code\modules\antagonists\traitor\contractor\contractor_hub.dm" #include "code\modules\antagonists\traitor\contractor\contractor_items.dm" #include "code\modules\antagonists\traitor\contractor\syndicate_contract.dm" -#include "code\modules\antagonists\traitor\objectives\assassination.dm" -#include "code\modules\antagonists\traitor\objectives\demoralise_assault.dm" -#include "code\modules\antagonists\traitor\objectives\destroy_heirloom.dm" -#include "code\modules\antagonists\traitor\objectives\destroy_item.dm" -#include "code\modules\antagonists\traitor\objectives\eyesnatching.dm" -#include "code\modules\antagonists\traitor\objectives\hack_comm_console.dm" -#include "code\modules\antagonists\traitor\objectives\infect.dm" -#include "code\modules\antagonists\traitor\objectives\kidnapping.dm" -#include "code\modules\antagonists\traitor\objectives\kill_pet.dm" -#include "code\modules\antagonists\traitor\objectives\locate_weakpoint.dm" -#include "code\modules\antagonists\traitor\objectives\sabotage_machinery.dm" -#include "code\modules\antagonists\traitor\objectives\sleeper_protocol.dm" -#include "code\modules\antagonists\traitor\objectives\steal.dm" -#include "code\modules\antagonists\traitor\objectives\abstract\target_player.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\battlecruiser.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\final_objective.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\infect_ai.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\objective_dark_matteor.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\romerol.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\space_dragon.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\supermatter_cascade.dm" #include "code\modules\antagonists\valentines\heartbreaker.dm" #include "code\modules\antagonists\valentines\valentine.dm" #include "code\modules\antagonists\venus_human_trap\venus_human_trap.dm" diff --git a/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx b/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx index f2fac4352328..22f424afefab 100644 --- a/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx +++ b/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx @@ -1,10 +1,4 @@ -import { - BlockQuote, - Button, - Dimmer, - Section, - Stack, -} from 'tgui-core/components'; +import { BlockQuote, Button, Section, Stack } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; @@ -36,8 +30,6 @@ type Info = { intro: string; code: string; failsafe_code: string; - replacement_code: string; - replacement_frequency: string; has_uplink: BooleanLike; uplink_intro: string; uplink_unlock_info: string; @@ -107,37 +99,12 @@ const EmployerSection = (props) => { const UplinkSection = (props) => { const { data } = useBackend(); - const { - has_uplink, - uplink_intro, - uplink_unlock_info, - code, - failsafe_code, - replacement_code, - replacement_frequency, - } = data; + const { has_uplink, uplink_intro, uplink_unlock_info, code, failsafe_code } = + data; return (
- {(!has_uplink && ( - - -
- Your uplink is missing or destroyed.
- Craft a Syndicate Uplink Beacon and then speak -
- - {replacement_code} - {' '} - on frequency{' '} - - {replacement_frequency} - {' '} - after synchronizing with the beacon. -
-
-
- )) || ( + { <> {uplink_intro} @@ -153,29 +120,16 @@ const UplinkSection = (props) => {
{uplink_unlock_info}
- )} + }

- {(has_uplink && ( -
- If you lose your uplink, you can craft a Syndicate Uplink Beacon and - then speak{' '} - - {replacement_code} - {' '} - on radio frequency{' '} - - {replacement_frequency} - {' '} - after synchronizing with the beacon. -
- )) || ( + {
{' '}

- )} + }
); }; diff --git a/tgui/packages/tgui/interfaces/TraitorObjectiveDebug.tsx b/tgui/packages/tgui/interfaces/TraitorObjectiveDebug.tsx deleted file mode 100644 index d820d440998b..000000000000 --- a/tgui/packages/tgui/interfaces/TraitorObjectiveDebug.tsx +++ /dev/null @@ -1,428 +0,0 @@ -import { useState } from 'react'; -import { Box, LabeledList, Stack, Tabs, Tooltip } from 'tgui-core/components'; - -import { useBackend } from '../backend'; -import { Window } from '../layouts'; -import { getDangerLevel } from './Uplink/calculateDangerLevel'; - -type Objective = { - name: string; - description: string; - progression_minimum: number; - progression_maximum: number; - global_progression_limit_coeff: number; - global_progression_influence_intensity: number; - progression_reward: [number, number]; - telecrystal_reward: [number, number]; - telecrystal_penalty: number; - weight: number; - type: string; -}; - -type ObjectiveList = { - objectives: (ObjectiveList | Objective)[]; - weight: number; -}; - -type ObjectiveCategory = ObjectiveList & { - name: string; -}; - -type PlayerData = { - player: string; - progression_points: number; - total_progression_from_objectives: number; -}; - -type ObjectiveData = { - current_progression: number; - objective_data: ObjectiveCategory[]; - player_data: PlayerData[]; -}; - -const recursivelyGetObjectives = (value: ObjectiveList) => { - let listToReturn: Objective[] = []; - for (let i = 0; i < value.objectives.length; i++) { - const possibleValue = value.objectives[i]; - if ((possibleValue as ObjectiveList).objectives) { - listToReturn = listToReturn.concat( - recursivelyGetObjectives(possibleValue as ObjectiveList), - ); - } else { - listToReturn.push(possibleValue as Objective); - } - } - return listToReturn; -}; - -// 150 minutes -const sizeLimit = 90000; - -type SortingOption = { - name: string; - // Function used to determine the order of the elements. - // It is expected to return a negative value - // if first argument is less than second argument, - // zero if they're equal and a positive value otherwise. - sort: (a: Objective, b: Objective) => number; -}; - -const sortingOptions: SortingOption[] = [ - { - name: 'Minimum Progression', - sort: (a, b) => { - if (a.progression_minimum < b.progression_minimum) { - return -1; - } else if (a.progression_minimum === b.progression_minimum) { - return 0; - } - return 1; - }, - }, - { - name: 'Telecrystal Payout', - sort: (a, b) => { - const telecrystalMeanA = - (a.telecrystal_reward[0] + a.telecrystal_reward[1]) / 2; - const telecrystalMeanB = - (b.telecrystal_reward[0] + b.telecrystal_reward[1]) / 2; - if (telecrystalMeanA < telecrystalMeanB) { - return -1; - } else if (telecrystalMeanA === telecrystalMeanB) { - return 0; - } - return 1; - }, - }, - { - name: 'Progression Payout', - sort: (a, b) => { - const progressionMeanA = - (a.progression_reward[0] + a.progression_reward[1]) / 2; - const progressionMeanB = - (b.progression_reward[0] + b.progression_reward[1]) / 2; - if (progressionMeanA < progressionMeanB) { - return -1; - } else if (progressionMeanA === progressionMeanB) { - return 0; - } - return 1; - }, - }, - { - name: 'Progression Payout + Min. Prog.', - sort: (a, b) => { - const progressionMeanA = - (a.progression_reward[0] + a.progression_reward[1]) / 2 + - a.progression_minimum; - const progressionMeanB = - (b.progression_reward[0] + b.progression_reward[1]) / 2 + - b.progression_minimum; - if (progressionMeanA < progressionMeanB) { - return -1; - } else if (progressionMeanA === progressionMeanB) { - return 0; - } - return 1; - }, - }, -]; - -export const TraitorObjectiveDebug = (props) => { - const { data, act } = useBackend(); - const { objective_data, player_data, current_progression } = data; - const lines: React.JSX.Element[] = []; - lines.sort(); - for (let i = 10; i < 100; i += 10) { - lines.push( - - - - {/* Time in minutes of this threshold */} - {Math.round((sizeLimit * (i / 100)) / 600)} mins - - , - ); - } - let objectivesToRender: Objective[] = []; - const [currentTab, setCurrentTab] = useState('All'); - const [sortingFunc, setSortingFunc] = useState(sortingOptions[0].name); - // true = ascending, false = descending - const [sortDirection, setSortingDirection] = useState(true); - - let actualSortingFunc; - for (let index = 0; index < sortingOptions.length; index++) { - const value = sortingOptions[index]; - if (value.name === sortingFunc) { - actualSortingFunc = value.sort; - } - } - - for (let index = 0; index < objective_data.length; index++) { - const value = objective_data[index]; - if (value.name !== currentTab && currentTab !== 'All') { - continue; - } - objectivesToRender = objectivesToRender.concat( - recursivelyGetObjectives(value), - ); - } - - objectivesToRender.sort(actualSortingFunc); - if (!sortDirection) { - objectivesToRender.reverse(); - } - - return ( - - - - - - - {sortingOptions.map((value) => ( - setSortingFunc(value.name)} - > - {value.name} - - ))} - - - - - setCurrentTab('All')} - > - All - - {objective_data.map((value) => ( - setCurrentTab(value.name)} - > - {value.name} - - ))} - - - - - setSortingDirection(true)} - > - Ascending - - setSortingDirection(false)} - > - Descending - - - - - - - {lines} - - {objectivesToRender.map((value, index) => ( - - - - ))} - - {player_data.map((value) => { - const rep = getDangerLevel(value.progression_points); - return ( - - - - {value.player} - - - {Math.floor(value.progression_points / 600)} mins - - - {Math.floor( - value.total_progression_from_objectives / 600, - )}{' '} - mins - - - - } - position="top" - > - - - - - ); - })} - - - - - - - - - ); -}; - -type ObjectiveBoxProps = { - objective: Objective; -}; - -const ObjectiveBox = (props: ObjectiveBoxProps) => { - const { objective } = props; - let width = `${ - (objective.progression_maximum / sizeLimit) * window.innerWidth - }px`; - if (objective.progression_maximum > sizeLimit) { - width = '100%'; - } - return ( - - - - {objective.name} - - - - - {objective.progression_minimum / 600} mins - - {objective.progression_maximum <= sizeLimit && ( - - {objective.progression_maximum / 600} mins - - )} - - - - - {objective.progression_reward[0] / 600} -  to {objective.progression_reward[1] / 600} pr - - - - - {objective.telecrystal_reward[0]} -  to {objective.telecrystal_reward[1]} tc - - - - - ); -}; diff --git a/tgui/packages/tgui/interfaces/Uplink/ObjectiveElement.tsx b/tgui/packages/tgui/interfaces/Uplink/ObjectiveElement.tsx new file mode 100644 index 000000000000..87c0bf227738 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Uplink/ObjectiveElement.tsx @@ -0,0 +1,41 @@ +import { Box, Flex, Stack } from 'tgui-core/components'; +import { classes } from 'tgui-core/react'; + +export type Objective = { + id: number; + name: string; + description: string; +}; + +type ObjectiveElementProps = { + name: string; + description: string; +}; + +export const ObjectiveElement = (props: ObjectiveElementProps) => { + const { name, description } = props; + + return ( + + + + + {name} + + + + + + {description} + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Uplink/ObjectiveMenu.tsx b/tgui/packages/tgui/interfaces/Uplink/ObjectiveMenu.tsx deleted file mode 100644 index e7cc81fb71f1..000000000000 --- a/tgui/packages/tgui/interfaces/Uplink/ObjectiveMenu.tsx +++ /dev/null @@ -1,569 +0,0 @@ -import { Component, type MouseEvent } from 'react'; -import { - Box, - Button, - Dimmer, - Flex, - Icon, - NoticeBox, - Section, - Stack, - Tooltip, -} from 'tgui-core/components'; -import { type BooleanLike, classes } from 'tgui-core/react'; - -import { - calculateProgression, - getDangerLevel, - type Rank, -} from './calculateDangerLevel'; -import { ObjectiveState } from './constants'; - -export type Objective = { - id: number; - name: string; - description: string; - progression_minimum: number; - progression_reward: number; - telecrystal_reward: number; - telecrystal_penalty: number; - ui_buttons?: ObjectiveUiButton[]; - objective_state: ObjectiveState; - original_progression: number; - final_objective: BooleanLike; -}; - -export type ObjectiveUiButton = { - name: string; - tooltip: string; - icon: string; - action: string; -}; - -type ObjectiveMenuProps = { - activeObjectives: Objective[]; - potentialObjectives: Objective[]; - maximumActiveObjectives: number; - maximumPotentialObjectives: number; - - handleStartObjective: (objective: Objective) => void; - handleObjectiveAction: (objective: Objective, action: string) => void; - handleObjectiveCompleted: (objective: Objective) => void; - handleObjectiveAbort: (objective: Objective) => void; - handleRequestObjectives: () => void; -}; - -type ObjectiveMenuState = { - draggingObjective: Objective | null; - objectiveX: number; - objectiveY: number; -}; - -let dragClickTimer = 0; - -export class ObjectiveMenu extends Component< - ObjectiveMenuProps, - ObjectiveMenuState -> { - constructor(props) { - super(props); - this.state = { - draggingObjective: null, - objectiveX: 0, - objectiveY: 0, - }; - - this.handleObjectiveClick = this.handleObjectiveClick.bind(this); - this.handleMouseUp = this.handleMouseUp.bind(this); - this.handleMouseMove = this.handleMouseMove.bind(this); - this.handleObjectiveAdded = this.handleObjectiveAdded.bind(this); - } - - handleObjectiveClick(event: MouseEvent, objective: Objective) { - if (this.state?.draggingObjective) { - return; - } - if (event.button === 0) { - // Left click - this.setState({ - draggingObjective: objective, - objectiveX: event.clientX, - objectiveY: event.clientY, - }); - window.addEventListener('mouseup', this.handleMouseUp as any); - window.addEventListener('mousemove', this.handleMouseMove as any); - event.stopPropagation(); - event.preventDefault(); - - dragClickTimer = Date.now() + 100; // 100 milliseconds - } - } - - handleMouseUp(event: MouseEvent) { - if (dragClickTimer > Date.now()) { - return; - } - - window.removeEventListener('mouseup', this.handleMouseUp as any); - window.removeEventListener('mousemove', this.handleMouseMove as any); - this.setState({ - draggingObjective: null, - }); - } - - handleMouseMove(event: MouseEvent) { - this.setState({ - objectiveX: event.pageX, - objectiveY: event.pageY - 32, - }); - } - - handleObjectiveAdded(event: MouseEvent) { - const { draggingObjective } = this.state as ObjectiveMenuState; - if (!draggingObjective) { - return; - } - const { handleStartObjective } = this.props; - handleStartObjective(draggingObjective); - } - - render() { - const { - activeObjectives = [], - potentialObjectives, - maximumActiveObjectives, - maximumPotentialObjectives, - handleObjectiveAction, - handleObjectiveCompleted, - handleObjectiveAbort, - handleRequestObjectives, - } = this.props; - const { draggingObjective, objectiveX, objectiveY } = this - .state as ObjectiveMenuState; - - potentialObjectives.sort((objA, objB) => { - if (objA.progression_minimum < objB.progression_minimum) { - return 1; - } else if (objA.progression_minimum > objB.progression_minimum) { - return -1; - } - return 0; - }); - return ( - <> - - -
- - {Array.apply(null, Array(maximumActiveObjectives)).map( - (_, index) => { - if (index >= activeObjectives.length) { - return ( - - - - - Empty Objective, drop objectives here to take - them - - - - - ); - } - const objective = activeObjectives[index]; - return ( - - {ObjectiveFunction( - objective, - true, - handleObjectiveAction, - handleObjectiveCompleted, - handleObjectiveAbort, - true, - )} - - ); - }, - )} - -
-
- -
- - {potentialObjectives.map((objective) => { - return ( - { - this.handleObjectiveClick(event, objective); - }} - > - {(objective.id !== draggingObjective?.id && - ObjectiveFunction( - objective, - false, - undefined, - undefined, - undefined, - true, - )) || ( - - )} - - ); - })} - {(maximumPotentialObjectives === 0 && ( - - - - You are locked out of objectives - - - )) || - (potentialObjectives.length < maximumPotentialObjectives && ( - - - -
-
-
- {!!draggingObjective && ( - - {ObjectiveFunction(draggingObjective, false)} - - )} - - ); - } -} - -const ObjectiveFunction = ( - objective: Objective, - active: boolean, - handleObjectiveAction?: (objective: Objective, action: string) => void, - handleCompletion?: (objective: Objective) => void, - handleAbort?: (objective: Objective) => void, - grow: boolean = false, -) => { - const dangerLevel = getDangerLevel(objective.progression_minimum); - return ( - { - if (handleCompletion) { - handleCompletion(objective); - } - }} - handleAbort={(event) => { - if (handleAbort) { - handleAbort(objective); - } - }} - uiButtons={ - active && handleObjectiveAction ? ( - - {objective.ui_buttons?.map((value, index) => ( - - -
- ) : null} - - )} - {!!uiButtons && !objectiveFinished && ( - {uiButtons} - )} - - - - - ); -}; diff --git a/tgui/packages/tgui/interfaces/Uplink/PrimaryObjectiveMenu.tsx b/tgui/packages/tgui/interfaces/Uplink/PrimaryObjectiveMenu.tsx index d88f2d014988..366f26b648ff 100644 --- a/tgui/packages/tgui/interfaces/Uplink/PrimaryObjectiveMenu.tsx +++ b/tgui/packages/tgui/interfaces/Uplink/PrimaryObjectiveMenu.tsx @@ -1,17 +1,16 @@ -import { Box, Button, Dimmer, Section, Stack } from 'tgui-core/components'; +import { Box, Button, Section, Stack } from 'tgui-core/components'; import { useBackend } from '../../backend'; -import { ObjectiveElement } from './ObjectiveMenu'; +import { ObjectiveElement } from './ObjectiveElement'; type PrimaryObjectiveMenuProps = { primary_objectives; - final_objective; can_renegotiate; }; export const PrimaryObjectiveMenu = (props: PrimaryObjectiveMenuProps) => { const { act } = useBackend(); - const { primary_objectives, final_objective, can_renegotiate } = props; + const { primary_objectives, can_renegotiate } = props; return (
@@ -20,57 +19,13 @@ export const PrimaryObjectiveMenu = (props: PrimaryObjectiveMenuProps) => { Your Primary Objectives are as follows. Complete these at all costs. - - Completing Secondary Objectives may allow you to aquire additional - equipment. - - {final_objective && ( - - - PRIORITY MESSAGE -
- SOURCE: xxx.xxx.xxx.224:41394 -
-
- \\Debrief in progress. -
- \\Final Objective confirmed complete.
- \\Your work is done here, agent. -
-
- CONNECTION CLOSED_ -
-
- )} {primary_objectives.map((prim_obj, index) => ( ))} diff --git a/tgui/packages/tgui/interfaces/Uplink/constants.ts b/tgui/packages/tgui/interfaces/Uplink/constants.ts deleted file mode 100644 index 3cda3fe45de1..000000000000 --- a/tgui/packages/tgui/interfaces/Uplink/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum ObjectiveState { - Inactive = 1, - Active = 2, - Completed = 3, - Failed = 4, - Invalid = 5, -} diff --git a/tgui/packages/tgui/interfaces/Uplink/index.tsx b/tgui/packages/tgui/interfaces/Uplink/index.tsx index bef59720e33b..7e2a65519a77 100644 --- a/tgui/packages/tgui/interfaces/Uplink/index.tsx +++ b/tgui/packages/tgui/interfaces/Uplink/index.tsx @@ -18,11 +18,9 @@ import { Window } from '../../layouts'; import { calculateDangerLevel, calculateProgression, - dangerDefault, dangerLevelsTooltip, } from './calculateDangerLevel'; import { GenericUplink, type Item } from './GenericUplink'; -import { type Objective, ObjectiveMenu } from './ObjectiveMenu'; import { PrimaryObjectiveMenu } from './PrimaryObjectiveMenu'; type UplinkItem = { @@ -40,6 +38,7 @@ type UplinkItem = { restricted_roles: string; restricted_species: string; progression_minimum: number; + population_minimum: number; cost_override_string: string; lock_other_purchases: BooleanLike; ref?: string; @@ -48,9 +47,8 @@ type UplinkItem = { type UplinkData = { telecrystals: number; progression_points: number; + joined_population?: number; lockable: BooleanLike; - current_expected_progression: number; - progression_scaling_deviance: number; current_progression_scaling: number; uplink_flag: number; assigned_role: string; @@ -64,16 +62,10 @@ type UplinkData = { [key: string]: number; }; - has_objectives: BooleanLike; has_progression: BooleanLike; primary_objectives: { [key: number]: string; }; - completed_final_objective: string; - potential_objectives: Objective[]; - active_objectives: Objective[]; - maximum_active_objectives: number; - maximum_potential_objectives: number; purchased_items: number; shop_locked: BooleanLike; can_renegotiate: BooleanLike; @@ -142,13 +134,15 @@ export class Uplink extends Component { uplinkData.items = uplinkData.items.filter((value) => { if ( value.restricted_roles.length > 0 && - !value.restricted_roles.includes(uplinkRole) + !value.restricted_roles.includes(uplinkRole) && + !data.debug ) { return false; } if ( value.restricted_species.length > 0 && - !value.restricted_species.includes(uplinkSpecies) + !value.restricted_species.includes(uplinkSpecies) && + !data.debug ) { return false; } @@ -179,17 +173,10 @@ export class Uplink extends Component { const { telecrystals, progression_points, + joined_population, primary_objectives, can_renegotiate, - completed_final_objective, - active_objectives, - potential_objectives, - has_objectives, has_progression, - maximum_active_objectives, - maximum_potential_objectives, - current_expected_progression, - progression_scaling_deviance, current_progression_scaling, extra_purchasable, extra_purchasable_stock, @@ -212,6 +199,8 @@ export class Uplink extends Component { const item = itemsToAdd[i]; const hasEnoughProgression = progression_points >= item.progression_minimum; + const hasEnoughPop = + !joined_population || joined_population >= item.population_minimum; let stock: number | null = current_stock[item.stock_key]; if (item.ref) { @@ -255,8 +244,14 @@ export class Uplink extends Component { )}
), + population_tooltip: + 'This item is not cleared for operations performed against stations crewed by fewer than ' + + item.population_minimum + + ' people.', + insufficient_population: !hasEnoughPop, disabled: !canBuy || + !hasEnoughPop || (has_progression && !hasEnoughProgression) || (item.lock_other_purchases && purchased_items > 0), extraData: { @@ -266,116 +261,57 @@ export class Uplink extends Component { }, }); } - // Get the difference between the current progression and - // expected progression - let progressionPercentage = - current_expected_progression - progression_points; - // Clamp it down between 0 and 2 - progressionPercentage = Math.min( - Math.max(progressionPercentage / progression_scaling_deviance, -1), - 1, - ); - // Round it and convert it into a percentage - progressionPercentage = Math.round(progressionPercentage * 1000) / 10; + return ( - {!!has_progression && ( - -
- + +
+ + {!!has_progression && ( - - Your current level of threat. Threat - determines - {has_objectives - ? ' the severity of secondary objectives you get and ' - : ' '} - what items you can purchase.  - - {/* A minute in deciseconds */} - Threat passively increases by{' '} - - {calculateProgression( - current_progression_scaling, - )} - -  every minute + Your current level of threat. Threat + determines what items you can purchase.  + + {/* A minute in deciseconds */} + Threat passively increases by{' '} + + {calculateProgression( + current_progression_scaling, + )} - {Math.abs(progressionPercentage) > 0 && ( - - Because your threat level is - {progressionPercentage < 0 - ? ' ahead ' - : ' behind '} - of where it should be, you are getting - - {progressionPercentage}% - - {progressionPercentage < 0 - ? 'less' - : 'more'}{' '} - threat every minute - - )} - {dangerLevelsTooltip} +  every minute + {dangerLevelsTooltip} - )) || - "Your current threat level. You are a killing machine and don't need to improve your threat level." + } > - {/* If we have no progression, - just give them a generic title */} - {has_progression - ? calculateDangerLevel(progression_points, false) - : calculateDangerLevel(dangerDefault, false)} + {calculateDangerLevel(progression_points, false)} - + )} + {!!primary_objectives && ( - {!!has_objectives && ( - <> - this.setState({ currentTab: 0 })} - > - Primary Objectives - - this.setState({ currentTab: 1 })} - > - Secondary Objectives - - + {primary_objectives && ( + this.setState({ currentTab: 0 })} + > + Primary Objectives + )} { textOverflow: 'ellipsis', }} icon="store" - selected={currentTab === 2 || !has_objectives} + selected={currentTab === 2} onClick={() => this.setState({ currentTab: 2 })} > Market + )} - {!!lockable && ( - - - - )} - -
-
- )} + {!!lockable && ( + + + + )} +
+
+
- {(currentTab === 0 && has_objectives && ( + {(currentTab === 0 && primary_objectives && ( - )) || - (currentTab === 1 && has_objectives && ( - - act('objective_act', { - check: objective.original_progression, - objective_action: action, - index: objective.id, - }) - } - handleStartObjective={(objective) => - act('start_objective', { - check: objective.original_progression, - index: objective.id, - }) - } - handleObjectiveAbort={(objective) => - act('objective_abort', { - check: objective.original_progression, - index: objective.id, - }) - } - handleObjectiveCompleted={(objective) => - act('finish_objective', { - check: objective.original_progression, - index: objective.id, - }) - } - handleRequestObjectives={() => act('regenerate_objectives')} + )) || ( + <> + { + if (!item.extraData?.ref) { + act('buy', { path: item.id }); + } else { + act('buy', { ref: item.extraData.ref }); + } + }} /> - )) || ( - <> - { - if (!item.extraData?.ref) { - act('buy', { path: item.id }); - } else { - act('buy', { ref: item.extraData.ref }); - } - }} - /> - {(shop_locked && ( - - - SHOP LOCKED - - - )) || - null} - - )} + {(shop_locked && !data.debug && ( + + + SHOP LOCKED + + + )) || + null} + + )}
From eed96ec50346797222e800f660f3f528fe51fb64 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Sat, 22 Feb 2025 22:38:40 +0000 Subject: [PATCH 02/21] Traitor Reputation does not scale with population & reintroduces population locked items (#89617) --- code/__DEFINES/uplink.dm | 3 ++ .../configuration/entries/game_options.dm | 5 --- code/controllers/subsystem/traitor.dm | 33 +++++-------------- code/datums/components/uplink.dm | 4 +-- .../antagonists/traitor/uplink_handler.dm | 12 ++++++- code/modules/asset_cache/assets/uplink.dm | 1 + code/modules/uplink/uplink_items.dm | 2 ++ code/modules/uplink/uplink_items/dangerous.dm | 1 + code/modules/uplink/uplink_items/job.dm | 1 + code/modules/uplink/uplink_items/nukeops.dm | 1 + code/modules/uplink/uplink_items/stealthy.dm | 1 + .../tgui/interfaces/AbductorConsole.tsx | 2 ++ .../tgui/interfaces/Uplink/GenericUplink.tsx | 29 +++++++++++++++- .../tgui/interfaces/common/MalfAiModules.tsx | 2 ++ 14 files changed, 64 insertions(+), 33 deletions(-) diff --git a/code/__DEFINES/uplink.dm b/code/__DEFINES/uplink.dm index 3b3621f24c54..3e75278132a5 100644 --- a/code/__DEFINES/uplink.dm +++ b/code/__DEFINES/uplink.dm @@ -42,3 +42,6 @@ /// Minimal cost for an item to be eligible for a discount #define TRAITOR_DISCOUNT_MIN_PRICE 4 + +/// The standard minimum player count for "don't spawn this item on low population rounds" +#define TRAITOR_POPULATION_LOWPOP 20 diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 31605037c84e..873c37317dc2 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -62,11 +62,6 @@ integer = FALSE min_val = 0 -/// Determines the ideal player count for maximum progression per minute. -/datum/config_entry/number/traitor_ideal_player_count - default = 20 - min_val = 1 - /// Determines how fast traitors scale in general. /datum/config_entry/number/traitor_scaling_multiplier default = 1 diff --git a/code/controllers/subsystem/traitor.dm b/code/controllers/subsystem/traitor.dm index 0097e23d1b90..599fd91e5785 100644 --- a/code/controllers/subsystem/traitor.dm +++ b/code/controllers/subsystem/traitor.dm @@ -15,48 +15,33 @@ SUBSYSTEM_DEF(traitor) /// The coefficient multiplied by the current_global_progression for new joining traitors to calculate their progression var/newjoin_progression_coeff = 1 - /// The current progression that all traitors should be at in the round + /// The current progression that all traitors should be at in the round, you can't have less than this var/current_global_progression = 0 - /// The amount of deviance from the current global progression before you start getting 2x the current scaling or no scaling at all - /// Also affects objectives, so -50% progress reduction or 50% progress boost. - var/progression_scaling_deviance = 20 MINUTES /// The current uplink handlers being managed var/list/datum/uplink_handler/uplink_handlers = list() - /// The current scaling per minute of progression. Has a maximum value of 1 MINUTES. + /// The current scaling per minute of progression. var/current_progression_scaling = 1 MINUTES /datum/controller/subsystem/traitor/Initialize() + current_progression_scaling = 1 MINUTES * CONFIG_GET(number/traitor_scaling_multiplier) for(var/theft_item in subtypesof(/datum/objective_item/steal)) new theft_item return SS_INIT_SUCCESS /datum/controller/subsystem/traitor/fire(resumed) - var/player_count = length(GLOB.alive_player_list) - // Has a maximum of 1 minute, however the value can be lower if there are lower players than the ideal - // player count for a traitor to be threatening. Rounds to the nearest 10% of a minute to prevent weird - // values from appearing in the UI. Traitor scaling multiplier bypasses the limit and only multiplies the end value. - // from all of our calculations. - current_progression_scaling = max(min( - (player_count / CONFIG_GET(number/traitor_ideal_player_count)) * 1 MINUTES, - 1 MINUTES - ), 0.1 MINUTES) * CONFIG_GET(number/traitor_scaling_multiplier) + var/previous_progression = current_global_progression + current_global_progression = (STATION_TIME_PASSED()) * CONFIG_GET(number/traitor_scaling_multiplier) + var/progression_increment = current_global_progression - previous_progression - var/progression_scaling_delta = (wait / (1 MINUTES)) * current_progression_scaling - var/previous_global_progression = current_global_progression - - current_global_progression += progression_scaling_delta for(var/datum/uplink_handler/handler in uplink_handlers) if(!handler.has_progression || QDELETED(handler)) uplink_handlers -= handler - var/deviance = (previous_global_progression - handler.progression_points) / progression_scaling_deviance - if(abs(deviance) < 0.01) - // If deviance is less than 1%, just set them to the current global progression + if(handler.progression_points < current_global_progression) + // If we got unsynced somehow, just set them to the current global progression // Prevents problems with precision errors. handler.progression_points = current_global_progression else - var/amount_to_give = progression_scaling_delta + (progression_scaling_delta * deviance) - amount_to_give = clamp(amount_to_give, 0, progression_scaling_delta * 2) - handler.progression_points += amount_to_give + handler.progression_points += progression_increment // Should only really happen if an admin is messing with an individual's progression value handler.on_update() /datum/controller/subsystem/traitor/proc/register_uplink_handler(datum/uplink_handler/uplink_handler) diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm index 64a930408d51..c6f57a5169ff 100644 --- a/code/datums/components/uplink.dm +++ b/code/datums/components/uplink.dm @@ -168,8 +168,7 @@ var/list/data = list() data["telecrystals"] = uplink_handler.telecrystals data["progression_points"] = uplink_handler.progression_points - data["current_expected_progression"] = SStraitor.current_global_progression - data["progression_scaling_deviance"] = SStraitor.progression_scaling_deviance + data["joined_population"] = length(GLOB.joined_player_list) data["current_progression_scaling"] = SStraitor.current_progression_scaling if(uplink_handler.primary_objectives) @@ -205,6 +204,7 @@ "restricted_roles" = item.restricted_roles, "restricted_species" = item.restricted_species, "progression_minimum" = item.progression_minimum, + "population_minimum" = item.population_minimum, "ref" = REF(item), )) diff --git a/code/modules/antagonists/traitor/uplink_handler.dm b/code/modules/antagonists/traitor/uplink_handler.dm index 2801ef29aad1..84e186bc116f 100644 --- a/code/modules/antagonists/traitor/uplink_handler.dm +++ b/code/modules/antagonists/traitor/uplink_handler.dm @@ -51,6 +51,10 @@ /datum/uplink_handler/proc/not_enough_reputation(datum/uplink_item/to_purchase) return has_progression && progression_points < to_purchase.progression_minimum +/// Checks if there are enough joined players to purchase an item +/datum/uplink_handler/proc/not_enough_population(datum/uplink_item/to_purchase) + return length(GLOB.joined_player_list) < to_purchase.population_minimum + /// Checks for uplink flags as well as items restricted to roles and species /datum/uplink_handler/proc/check_if_restricted(datum/uplink_item/to_purchase) if(!to_purchase.can_be_bought(src)) @@ -80,9 +84,15 @@ if(!check_if_restricted(to_purchase)) return FALSE + if(not_enough_reputation(to_purchase) || not_enough_population(to_purchase)) + return FALSE + + if(telecrystals < to_purchase.cost) + return FALSE + var/current_stock = item_stock[to_purchase.stock_key] var/stock = current_stock != null ? current_stock : INFINITY - if(telecrystals < to_purchase.cost || stock <= 0 || not_enough_reputation(to_purchase)) + if(stock <= 0) return FALSE return TRUE diff --git a/code/modules/asset_cache/assets/uplink.dm b/code/modules/asset_cache/assets/uplink.dm index 4542a638b4bf..e236c18122a0 100644 --- a/code/modules/asset_cache/assets/uplink.dm +++ b/code/modules/asset_cache/assets/uplink.dm @@ -35,6 +35,7 @@ "restricted_roles" = item.restricted_roles, "restricted_species" = item.restricted_species, "progression_minimum" = item.progression_minimum, + "population_minimum" = item.population_minimum, "cost_override_string" = item.cost_override_string, "lock_other_purchases" = item.lock_other_purchases )) diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm index 32783504fe87..9abc9d97ab2b 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -62,6 +62,8 @@ var/restricted_species = list() /// The minimum amount of progression needed for this item to be added to uplinks. var/progression_minimum = 0 + /// The minimum number of joined players (so not observers) needed for this item to be added to uplinks. + var/population_minimum = 0 /// Whether this purchase is visible in the purchase log. var/purchase_log_vis = TRUE // Visible in the purchase log? /// Whether this purchase is restricted or not (VR/Events related) diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm index 970741876bb7..8838e5918713 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -63,6 +63,7 @@ desc = "The double-bladed energy sword does slightly more damage than a standard energy sword and will deflect \ energy projectiles it blocks, but requires two hands to wield. It also struggles to protect you from tackles." progression_minimum = 30 MINUTES + population_minimum = TRAITOR_POPULATION_LOWPOP item = /obj/item/dualsaber cost = 13 diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm index 50befb0a35d8..96a9e5a74b85 100644 --- a/code/modules/uplink/uplink_items/job.dm +++ b/code/modules/uplink/uplink_items/job.dm @@ -318,6 +318,7 @@ also give them a bit of sentience though." progression_minimum = 30 MINUTES item = /obj/item/reagent_containers/syringe/spider_extract + population_minimum = TRAITOR_POPULATION_LOWPOP cost = 10 restricted_roles = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST, JOB_ROBOTICIST) surplus = 10 diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm index d639b78fafe1..8d35efeeb5b9 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -581,6 +581,7 @@ along with slurred speech, aggression, and the ability to infect others with this agent." item = /obj/item/storage/box/syndie_kit/romerol cost = 25 + population_minimum = TRAITOR_POPULATION_LOWPOP progression_minimum = 30 MINUTES purchasable_from = UPLINK_ALL_SYNDIE_OPS | UPLINK_TRAITORS // Don't give this to spies cant_discount = TRUE diff --git a/code/modules/uplink/uplink_items/stealthy.dm b/code/modules/uplink/uplink_items/stealthy.dm index 6ba88eda43ec..d81a56c1c720 100644 --- a/code/modules/uplink/uplink_items/stealthy.dm +++ b/code/modules/uplink/uplink_items/stealthy.dm @@ -76,6 +76,7 @@ and gain the ability to swat bullets from the air, but you will also refuse to use dishonorable ranged weaponry." item = /obj/item/book/granter/martial/carp progression_minimum = 30 MINUTES + population_minimum = TRAITOR_POPULATION_LOWPOP cost = 17 surplus = 0 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) diff --git a/tgui/packages/tgui/interfaces/AbductorConsole.tsx b/tgui/packages/tgui/interfaces/AbductorConsole.tsx index a8d03c82358a..4a4bdc632339 100644 --- a/tgui/packages/tgui/interfaces/AbductorConsole.tsx +++ b/tgui/packages/tgui/interfaces/AbductorConsole.tsx @@ -93,6 +93,8 @@ const Abductsoft = (props) => { disabled: (credits || 0) < item.cost, icon: item.icon, icon_state: item.icon_state, + population_tooltip: '', + insufficient_population: false, }); } } diff --git a/tgui/packages/tgui/interfaces/Uplink/GenericUplink.tsx b/tgui/packages/tgui/interfaces/Uplink/GenericUplink.tsx index 569a4e4395ea..4ab997a28693 100644 --- a/tgui/packages/tgui/interfaces/Uplink/GenericUplink.tsx +++ b/tgui/packages/tgui/interfaces/Uplink/GenericUplink.tsx @@ -133,6 +133,8 @@ export type Item = { category: string; cost: React.JSX.Element | string; desc: React.JSX.Element | string; + population_tooltip: string; + insufficient_population: BooleanLike; disabled: BooleanLike; }; @@ -184,9 +186,19 @@ const ItemList = (props: ItemListProps) => { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', + opacity: item.insufficient_population ? '0.5' : '1', }} > - {item.name} + {item.insufficient_population ? ( + + + + {item.name} + + + ) : ( + item.name + )} @@ -215,6 +227,21 @@ const ItemList = (props: ItemListProps) => { } > + {item.insufficient_population ? ( + + {' '} + {item.population_tooltip} + + ) : ( + '' + )} + Date: Mon, 17 Feb 2025 10:21:12 +0000 Subject: [PATCH 03/21] Removes some traitor item timelocks (#89472) --- code/modules/uplink/uplink_items/badass.dm | 9 --------- code/modules/uplink/uplink_items/dangerous.dm | 4 ---- code/modules/uplink/uplink_items/explosive.dm | 2 -- code/modules/uplink/uplink_items/job.dm | 7 ------- code/modules/uplink/uplink_items/stealthy_tools.dm | 2 +- code/modules/uplink/uplink_items/suits.dm | 10 ---------- 6 files changed, 1 insertion(+), 33 deletions(-) diff --git a/code/modules/uplink/uplink_items/badass.dm b/code/modules/uplink/uplink_items/badass.dm index fd28cd708b9b..5342d65f3861 100644 --- a/code/modules/uplink/uplink_items/badass.dm +++ b/code/modules/uplink/uplink_items/badass.dm @@ -59,16 +59,11 @@ name = "Clown Costume" desc = "Nothing is more terrifying than clowns with fully automatic weaponry." item = /obj/item/storage/backpack/duffelbag/clown/syndie - purchasable_from = ALL - progression_minimum = 70 MINUTES /datum/uplink_item/badass/costumes/tactical_naptime name = "Sleepy Time Pajama Bundle" desc = "Even soldiers need to get a good nights rest. Comes with blood-red pajamas, a blankie, a hot mug of cocoa and a fuzzy friend." item = /obj/item/storage/box/syndie_kit/sleepytime - purchasable_from = ALL - progression_minimum = 90 MINUTES - cost = 4 limited_stock = 1 cant_discount = TRUE @@ -76,16 +71,12 @@ name = "Broken Chameleon Kit" desc = "A set of items that contain chameleon technology allowing you to disguise as pretty much anything on the station, and more! \ Please note that this kit did NOT pass quality control." - purchasable_from = ALL - progression_minimum = 90 MINUTES item = /obj/item/storage/box/syndie_kit/chameleon/broken /datum/uplink_item/badass/costumes/centcom_official name = "CentCom Official Costume" desc = "Ask the crew to \"inspect\" their nuclear disk and weapons system, and then when they decline, pull out a fully automatic rifle and gun down the Captain. \ Radio headset does not include encryption key. No gun included." - purchasable_from = ALL - progression_minimum = 110 MINUTES item = /obj/item/storage/box/syndie_kit/centcom_costume /datum/uplink_item/badass/stickers diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm index 8838e5918713..bd9e06231284 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -35,7 +35,6 @@ name = "Energy Sword" desc = "The energy sword is an edged weapon with a blade of pure energy. The sword is small enough to be \ pocketed when inactive. Activating it produces a loud, distinctive noise." - progression_minimum = 20 MINUTES item = /obj/item/melee/energy/sword/saber cost = 8 purchasable_from = ~UPLINK_CLOWN_OPS @@ -46,7 +45,6 @@ Upon hitting a target, the piston-ram will extend forward to make contact for some serious damage. \ Using a wrench on the piston valve will allow you to tweak the amount of gas used per punch to \ deal extra damage and hit targets further. Use a screwdriver to take out any attached tanks." - progression_minimum = 20 MINUTES item = /obj/item/melee/powerfist cost = 6 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) @@ -54,7 +52,6 @@ /datum/uplink_item/dangerous/rapid name = "Gloves of the North Star" desc = "These gloves let the user punch people very fast. Does not improve weapon attack speed or the meaty fists of a hulk." - progression_minimum = 20 MINUTES item = /obj/item/clothing/gloves/rapid cost = 8 @@ -82,7 +79,6 @@ name = "Holoparasites" desc = "Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, they require an \ organic host as a home base and source of fuel. Holoparasites come in various types and share damage with their host." - progression_minimum = 30 MINUTES item = /obj/item/guardian_creator/tech cost = 18 surplus = 0 diff --git a/code/modules/uplink/uplink_items/explosive.dm b/code/modules/uplink/uplink_items/explosive.dm index 72f40b00dfca..b792039b39ab 100644 --- a/code/modules/uplink/uplink_items/explosive.dm +++ b/code/modules/uplink/uplink_items/explosive.dm @@ -31,7 +31,6 @@ desc = "Contains 3 X-4 shaped plastic explosives. Similar to C4, but with a stronger blast that is directional instead of circular. \ X-4 can be placed on a solid surface, such as a wall or window, and it will blast through the wall, injuring anything on the opposite side, while being safer to the user. \ For when you want a controlled explosion that leaves a wider, deeper, hole." - progression_minimum = 20 MINUTES item = /obj/item/storage/backpack/duffelbag/syndie/x4 cost = 4 cant_discount = TRUE @@ -61,7 +60,6 @@ name = "Pizza Bomb" desc = "A pizza box with a bomb cunningly attached to the lid. The timer needs to be set by opening the box; afterwards, \ opening the box again will trigger the detonation after the timer has elapsed. Comes with free pizza, for you or your target!" - progression_minimum = 15 MINUTES item = /obj/item/pizzabox/bomb cost = 6 surplus = 8 diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm index 96a9e5a74b85..7a88805944ab 100644 --- a/code/modules/uplink/uplink_items/job.dm +++ b/code/modules/uplink/uplink_items/job.dm @@ -102,7 +102,6 @@ name = "Combat Bakery Kit" desc = "A kit of clandestine baked weapons. Contains a baguette which a skilled mime could use as a sword, \ a pair of throwing croissants, and the recipe to make more on demand. Once the job is done, eat the evidence." - progression_minimum = 15 MINUTES item = /obj/item/storage/box/syndie_kit/combat_baking cost = 7 restricted_roles = list(JOB_COOK, JOB_MIME) @@ -205,7 +204,6 @@ name = "Reverse Revolver" desc = "A revolver that always fires at its user. \"Accidentally\" drop your weapon, then watch as the greedy corporate pigs blow their own brains all over the wall. \ The revolver itself is actually real. Only clumsy people, and clowns, can fire it normally. Comes in a box of hugs. Honk." - progression_minimum = 30 MINUTES cost = 14 item = /obj/item/storage/box/hug/reverse_revolver restricted_roles = list(JOB_CLOWN) @@ -214,8 +212,6 @@ name = "Kinetic Accelerator Pressure Mod" desc = "A modification kit which allows Kinetic Accelerators to do greatly increased damage while indoors. \ Occupies 35% mod capacity." - // While less deadly than a revolver it does have infinite ammo - progression_minimum = 15 MINUTES item = /obj/item/borg/upgrade/modkit/indoors cost = 5 //you need two for full damage, so total of 10 for maximum damage limited_stock = 2 //you can't use more than two! @@ -234,7 +230,6 @@ /datum/uplink_item/role_restricted/laser_arm name = "Laser Arm Implant" desc = "An implant that grants you a recharging laser gun inside your arm. Weak to EMPs. Comes with a syndicate autosurgeon for immediate self-application." - progression_minimum = 20 MINUTES cost = 10 item = /obj/item/autosurgeon/syndicate/laser_arm restricted_roles = list(JOB_ROBOTICIST, JOB_RESEARCH_DIRECTOR) @@ -243,7 +238,6 @@ /datum/uplink_item/role_restricted/chemical_gun name = "Reagent Dartgun" desc = "A heavily modified syringe gun which is capable of synthesizing its own chemical darts using input reagents. Can hold 90u of reagents." - progression_minimum = 15 MINUTES item = /obj/item/gun/chem cost = 12 restricted_roles = list(JOB_CHEMIST, JOB_CHIEF_MEDICAL_OFFICER, JOB_BOTANIST) @@ -304,7 +298,6 @@ Attach to an exosuit with an existing equipment to disguise the bay as that equipment. The sacrificed equipment will be lost.\ Alternatively, you can attach the bay to an empty equipment slot, but the bay will not be concealed. Once the bay is \ attached, an exosuit weapon can be fitted inside." - progression_minimum = 30 MINUTES item = /obj/item/mecha_parts/mecha_equipment/concealed_weapon_bay cost = 3 restricted_roles = list(JOB_ROBOTICIST, JOB_RESEARCH_DIRECTOR) diff --git a/code/modules/uplink/uplink_items/stealthy_tools.dm b/code/modules/uplink/uplink_items/stealthy_tools.dm index 86bfa95a8e90..7eeca800d6a3 100644 --- a/code/modules/uplink/uplink_items/stealthy_tools.dm +++ b/code/modules/uplink/uplink_items/stealthy_tools.dm @@ -119,7 +119,7 @@ desc = "When purchased, a virus will be uploaded to the engineering processing servers to force a routine power grid check, forcing all APCs on the station to be temporarily disabled." item = /obj/effect/gibspawner/generic surplus = 0 - progression_minimum = 20 MINUTES + progression_minimum = 15 MINUTES limited_stock = 1 cost = 6 restricted = TRUE diff --git a/code/modules/uplink/uplink_items/suits.dm b/code/modules/uplink/uplink_items/suits.dm index 2e7e0edecaf4..0a0e90948ebc 100644 --- a/code/modules/uplink/uplink_items/suits.dm +++ b/code/modules/uplink/uplink_items/suits.dm @@ -68,16 +68,6 @@ item = /obj/item/mod/module/shock_absorber cost = 1 -/datum/uplink_item/suits/modsuit/elite_traitor - name = "Elite Syndicate MODsuit" - desc = "An upgraded, elite version of the Syndicate MODsuit. It features fireproofing, and also \ - provides the user with superior armor and mobility compared to the standard Syndicate MODsuit." - item = /obj/item/mod/control/pre_equipped/traitor_elite - // This one costs more than the nuke op counterpart - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) - progression_minimum = 90 MINUTES - cost = 16 - /datum/uplink_item/suits/modsuit/wraith name = "MODsuit wraith cloaking module" desc = "A MODsuit module that grants to the user Optical camouflage and the ability to overload light sources to recharge suit power. \ From acc9c6f7fb84c9b8f199230a387a3c92f7e92b92 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:36:10 -0500 Subject: [PATCH 04/21] Dynamic Rework (#91290) --- code/__DEFINES/antagonists.dm | 9 +- .../dcs/signals/signals_global_object.dm | 2 +- .../signals/signals_mob/signals_mob_main.dm | 3 + .../dcs/signals/signals_subsystem.dm | 3 + code/__DEFINES/dynamic.dm | 40 - code/__DEFINES/jobs.dm | 4 + code/__DEFINES/role_preferences.dm | 52 +- code/__HELPERS/events.dm | 2 - code/__HELPERS/game.dm | 13 +- code/__HELPERS/logging/dynamic.dm | 5 - code/__HELPERS/logging/manifest.dm | 3 +- code/__HELPERS/priority_announce.dm | 2 +- code/__HELPERS/roundend.dm | 12 +- code/controllers/subsystem/blackbox.dm | 2 +- code/controllers/subsystem/communications.dm | 64 - .../subsystem/dynamic/__dynamic_defines.dm | 55 + .../subsystem/dynamic/_dynamic_ruleset.dm | 372 ++++ .../subsystem/dynamic/_dynamic_tier.dm | 316 ++++ code/controllers/subsystem/dynamic/dynamic.dm | 1616 +++++++---------- .../subsystem/dynamic/dynamic_admin.dm | 254 +++ .../subsystem/dynamic/dynamic_hijacking.dm | 25 - .../subsystem/dynamic/dynamic_logging.dm | 101 -- .../dynamic/dynamic_midround_rolling.dm | 108 -- .../dynamic/dynamic_ruleset_latejoin.dm | 128 ++ .../dynamic/dynamic_ruleset_midround.dm | 1150 ++++++++++++ .../dynamic/dynamic_ruleset_roundstart.dm | 433 +++++ .../subsystem/dynamic/dynamic_rulesets.dm | 285 --- .../dynamic/dynamic_rulesets_latejoin.dm | 254 --- .../dynamic/dynamic_rulesets_midround.dm | 933 ---------- .../dynamic/dynamic_rulesets_roundstart.dm | 701 ------- .../subsystem/dynamic/dynamic_testing.dm | 111 ++ .../dynamic/dynamic_unfavorable_situation.dm | 74 - code/controllers/subsystem/dynamic/readme.md | 194 -- .../subsystem/dynamic/ruleset_picking.dm | 139 -- code/controllers/subsystem/job.dm | 449 ++--- code/controllers/subsystem/polling.dm | 5 +- code/controllers/subsystem/ticker.dm | 128 +- code/datums/ai_laws/ai_laws.dm | 4 +- code/datums/brain_damage/creepy_trauma.dm | 3 +- code/datums/brain_damage/imaginary_friend.dm | 4 +- code/datums/communications.dm | 135 ++ code/datums/elements/art.dm | 2 +- code/datums/id_trim/jobs.dm | 4 +- code/datums/mind/_mind.dm | 13 +- code/datums/mind/antag.dm | 56 +- code/datums/mind/initialization.dm | 7 +- code/datums/records/manifest.dm | 2 +- code/datums/station_traits/_station_trait.dm | 7 +- code/datums/station_traits/job_traits.dm | 22 +- code/datums/station_traits/negative_traits.dm | 1 - code/game/gamemodes/objective.dm | 14 +- .../game/machinery/computer/communications.dm | 72 +- code/game/machinery/computer/crew.dm | 2 +- .../machinery/porta_turret/portable_turret.dm | 2 +- code/game/machinery/wishgranter.dm | 2 +- .../anomalies/anomalies_pyroclastic.dm | 1 - .../objects/effects/poster_motivational.dm | 6 +- .../effects/spawners/xeno_egg_delivery.dm | 4 +- code/game/objects/items/cards_ids.dm | 4 +- .../game/objects/items/devices/aicard_evil.dm | 2 +- code/game/objects/items/religion.dm | 2 +- code/game/objects/items/robot/robot_parts.dm | 2 +- .../game/objects/items/storage/uplink_kits.dm | 8 +- code/game/say.dm | 4 +- code/modules/admin/admin.dm | 168 +- code/modules/admin/admin_verbs.dm | 4 +- code/modules/admin/antag_panel.dm | 26 +- code/modules/admin/check_antagonists.dm | 4 +- code/modules/admin/player_panel.dm | 2 +- code/modules/admin/topic.dm | 120 +- code/modules/admin/verbs/adminevents.dm | 12 +- code/modules/admin/verbs/admingame.dm | 37 +- code/modules/admin/verbs/adminhelp.dm | 4 +- code/modules/admin/verbs/ert.dm | 2 +- code/modules/admin/verbs/secrets.dm | 2 +- .../admin/view_variables/admin_delete.dm | 3 + .../antagonists/_common/antag_datum.dm | 49 +- .../antagonists/_common/antag_helpers.dm | 3 +- .../antagonists/_common/antag_spawner.dm | 24 +- code/modules/antagonists/abductor/abductor.dm | 11 +- .../antagonists/ashwalker/ashwalker.dm | 5 +- .../battlecruiser/battlecruiser.dm | 5 +- code/modules/antagonists/blob/blob_antag.dm | 4 +- .../antagonists/brainwashing/brainwashing.dm | 5 +- code/modules/antagonists/brother/brother.dm | 48 +- .../antagonists/changeling/changeling.dm | 4 +- .../changeling/fallen_changeling.dm | 3 +- code/modules/antagonists/clown_ops/clownop.dm | 11 +- code/modules/antagonists/cult/cult.dm | 2 +- code/modules/antagonists/cult/runes.dm | 3 +- .../antagonists/disease/disease_datum.dm | 2 +- code/modules/antagonists/ert/ert.dm | 3 +- .../antagonists/evil_clone/evil_clone.dm | 67 + code/modules/antagonists/fugitive/fugitive.dm | 15 +- .../antagonists/fugitive/hunters/hunter.dm | 3 +- .../antagonists/greentext/greentext.dm | 2 +- .../antagonists/heretic/heretic_antag.dm | 2 +- .../antagonists/heretic/heretic_monsters.dm | 2 +- .../antagonists/highlander/highlander.dm | 3 +- .../antagonists/hypnotized/hypnotized.dm | 5 +- code/modules/antagonists/malf_ai/malf_ai.dm | 5 +- .../antagonists/nightmare/nightmare.dm | 2 +- .../antagonists/nukeop/datums/operative.dm | 196 ++ .../nukeop/datums/operative_leader.dm | 69 + .../nukeop/datums/operative_lone.dm | 13 + .../nukeop/datums/operative_team.dm | 354 ++++ code/modules/antagonists/nukeop/nukeop.dm | 638 ------- code/modules/antagonists/obsessed/obsessed.dm | 17 +- .../paradox_clone/paradox_clone.dm | 17 +- code/modules/antagonists/pirate/pirate.dm | 2 +- .../antagonists/pirate/pirate_event.dm | 119 -- .../antagonists/pyro_slime/pyro_slime.dm | 1 + .../revolution/enemy_of_the_state.dm | 2 +- .../antagonists/revolution/revolution.dm | 216 +-- .../revolution/revolution_handler.dm | 159 ++ .../sentient_creature/sentient_creature.dm | 2 +- .../modules/antagonists/shade/shade_minion.dm | 2 +- .../antagonists/space_dragon/space_dragon.dm | 8 +- .../antagonists/space_ninja/space_ninja.dm | 15 +- .../antagonists/survivalist/survivalist.dm | 1 - .../syndicate_monkey/syndicate_monkey.dm | 2 +- .../traitor/components/demoraliser.dm | 2 +- .../traitor/contractor/contract_teammate.dm | 2 + .../antagonists/traitor/datum_traitor.dm | 109 +- .../antagonists/valentines/valentine.dm | 4 +- .../antagonists/wishgranter/wishgranter.dm | 1 - .../equipment/spellbook_entries/summons.dm | 23 +- .../wizard/equipment/wizard_spellbook.dm | 2 +- .../wizard/grand_ritual/finales/armageddon.dm | 6 +- .../finales/grand_ritual_finale.dm | 2 +- .../antagonists/wizard/slaughter_antag.dm | 2 +- code/modules/antagonists/wizard/wizard.dm | 7 +- code/modules/antagonists/xeno/xeno.dm | 15 +- .../modules/bitrunning/antagonists/_parent.dm | 2 +- .../bitrunning/antagonists/ghost_role.dm | 2 +- .../components/avatar_connection.dm | 2 +- code/modules/bitrunning/event.dm | 1 - code/modules/bitrunning/server/threats.dm | 3 +- code/modules/bitrunning/spawners.dm | 2 +- code/modules/client/preferences.dm | 2 +- .../client/preferences/middleware/antags.dm | 91 +- .../client/preferences/middleware/jobs.dm | 6 +- .../preferences/species_features/vampire.dm | 2 +- code/modules/client/preferences_savefile.dm | 2 +- code/modules/client/verbs/who.dm | 2 +- .../clothing/chameleon/_chameleon_action.dm | 2 +- code/modules/clothing/outfits/event.dm | 3 +- code/modules/events/_event.dm | 6 - code/modules/events/creep_awakening.dm | 26 - code/modules/events/dynamic_tweak.dm | 35 + code/modules/events/false_alarm.dm | 79 +- code/modules/events/ghost_role/abductor.dm | 38 - .../events/ghost_role/alien_infestation.dm | 82 - code/modules/events/ghost_role/blob.dm | 44 - .../events/ghost_role/changeling_event.dm | 32 - .../events/ghost_role/fugitive_event.dm | 151 -- code/modules/events/ghost_role/morph_event.dm | 38 - code/modules/events/ghost_role/nightmare.dm | 41 - code/modules/events/ghost_role/operative.dm | 8 +- .../events/ghost_role/revenant_event.dm | 65 - .../events/ghost_role/slaughter_event.dm | 40 - .../modules/events/ghost_role/space_dragon.dm | 41 - code/modules/events/ghost_role/space_ninja.dm | 49 - code/modules/events/holiday/vday.dm | 2 - code/modules/events/shuttle_insurance.dm | 2 +- code/modules/events/spider_infestation.dm | 36 - code/modules/events/wizard/imposter.dm | 1 - code/modules/events/wizard/rpgtitles.dm | 2 +- code/modules/jobs/job_exp.dm | 2 +- code/modules/jobs/job_types/_job.dm | 8 +- code/modules/jobs/job_types/ai.dm | 3 + .../job_types/antagonists/clown_operative.dm | 2 - .../antagonists/nuclear_operative.dm | 21 +- code/modules/jobs/job_types/captain.dm | 2 +- code/modules/jobs/job_types/cook.dm | 2 +- code/modules/jobs/job_types/cyborg.dm | 3 + code/modules/jobs/job_types/detective.dm | 2 +- .../jobs/job_types/head_of_security.dm | 2 +- code/modules/jobs/job_types/prisoner.dm | 2 +- .../jobs/job_types/security_officer.dm | 2 +- .../station_trait/bridge_assistant.dm | 2 +- .../job_types/station_trait/cargo_gorilla.dm | 4 +- .../station_trait/veteran_advisor.dm | 2 +- code/modules/jobs/job_types/warden.dm | 2 +- .../modules/meteors/meteor_mode_controller.dm | 31 + code/modules/meteors/meteor_spawning.dm | 8 +- .../modules/mob/dead/new_player/new_player.dm | 35 +- .../mob/dead/new_player/preferences_setup.dm | 4 +- .../mob/living/basic/drone/interaction.dm | 4 +- .../living/basic/space_fauna/demon/demon.dm | 3 +- .../basic/space_fauna/revenant/_revenant.dm | 3 +- code/modules/mob/living/brain/posibrain.dm | 4 +- code/modules/mob/living/carbon/human/human.dm | 1 - .../mob/living/carbon/human/human_helpers.dm | 2 +- code/modules/mob/living/living.dm | 7 +- code/modules/mob/living/silicon/ai/ai.dm | 8 +- code/modules/mob/living/silicon/ai/ai_say.dm | 2 +- .../modules/mob/living/silicon/robot/robot.dm | 5 +- .../hostile/megafauna/colossus.dm | 2 +- code/modules/mob/mob_helpers.dm | 75 +- code/modules/mob/mob_lists.dm | 11 +- .../mob_spawn/ghost_roles/unused_roles.dm | 2 +- code/modules/mob_spawn/mob_spawn.dm | 2 +- .../modular_computers/computers/item/pda.dm | 2 +- .../file_system/programs/jobmanagement.dm | 6 +- .../programs/messenger/messenger_program.dm | 2 +- code/modules/projectiles/projectile/magic.dm | 16 +- code/modules/research/server.dm | 2 +- .../spells/spell_types/self/mime_vow.dm | 2 +- code/modules/unit_tests/anonymous_themes.dm | 2 +- code/modules/unit_tests/dummy_spawn.dm | 2 +- .../unit_tests/dynamic_ruleset_sanity.dm | 35 +- ...hot_antag_icons_clownoperativemidround.png | Bin 0 -> 2004 bytes ...creenshot_antag_icons_nuclearoperative.png | Bin 0 -> 1895 bytes .../security_officer_distribution.dm | 2 +- code/modules/unit_tests/traitor.dm | 4 +- .../unit_tests/traitor_mail_content_check.dm | 2 +- config/dynamic.json | 163 -- config/dynamic.toml | 612 +++++++ maplestation.dme | 9 +- .../advanced_traitor/advanced_traitor.dm | 177 -- .../infiltrator/advanced_infiltrator.dm | 26 - .../antagonists/infiltrator/infiltrator.dm | 175 -- .../loadouts/loadout_items/_loadout_datum.dm | 2 +- tgstation.dme | 37 +- .../packages/tgui/interfaces/DynamicAdmin.tsx | 739 ++++++++ .../tgui/interfaces/DynamicTester.tsx | 145 ++ 227 files changed, 7106 insertions(+), 7378 deletions(-) delete mode 100644 code/__DEFINES/dynamic.dm delete mode 100644 code/controllers/subsystem/communications.dm create mode 100644 code/controllers/subsystem/dynamic/__dynamic_defines.dm create mode 100644 code/controllers/subsystem/dynamic/_dynamic_ruleset.dm create mode 100644 code/controllers/subsystem/dynamic/_dynamic_tier.dm create mode 100644 code/controllers/subsystem/dynamic/dynamic_admin.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_hijacking.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_logging.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_midround_rolling.dm create mode 100644 code/controllers/subsystem/dynamic/dynamic_ruleset_latejoin.dm create mode 100644 code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm create mode 100644 code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_rulesets.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm create mode 100644 code/controllers/subsystem/dynamic/dynamic_testing.dm delete mode 100644 code/controllers/subsystem/dynamic/dynamic_unfavorable_situation.dm delete mode 100644 code/controllers/subsystem/dynamic/readme.md delete mode 100644 code/controllers/subsystem/dynamic/ruleset_picking.dm create mode 100644 code/datums/communications.dm create mode 100644 code/modules/antagonists/evil_clone/evil_clone.dm create mode 100644 code/modules/antagonists/nukeop/datums/operative.dm create mode 100644 code/modules/antagonists/nukeop/datums/operative_leader.dm create mode 100644 code/modules/antagonists/nukeop/datums/operative_lone.dm create mode 100644 code/modules/antagonists/nukeop/datums/operative_team.dm delete mode 100644 code/modules/antagonists/nukeop/nukeop.dm delete mode 100644 code/modules/antagonists/pirate/pirate_event.dm create mode 100644 code/modules/antagonists/revolution/revolution_handler.dm delete mode 100644 code/modules/events/creep_awakening.dm create mode 100644 code/modules/events/dynamic_tweak.dm delete mode 100644 code/modules/events/ghost_role/abductor.dm delete mode 100644 code/modules/events/ghost_role/alien_infestation.dm delete mode 100644 code/modules/events/ghost_role/blob.dm delete mode 100644 code/modules/events/ghost_role/changeling_event.dm delete mode 100644 code/modules/events/ghost_role/fugitive_event.dm delete mode 100644 code/modules/events/ghost_role/morph_event.dm delete mode 100644 code/modules/events/ghost_role/nightmare.dm delete mode 100644 code/modules/events/ghost_role/revenant_event.dm delete mode 100644 code/modules/events/ghost_role/slaughter_event.dm delete mode 100644 code/modules/events/ghost_role/space_dragon.dm delete mode 100644 code/modules/events/ghost_role/space_ninja.dm delete mode 100644 code/modules/events/spider_infestation.dm delete mode 100644 code/modules/jobs/job_types/antagonists/clown_operative.dm create mode 100644 code/modules/meteors/meteor_mode_controller.dm create mode 100644 code/modules/unit_tests/screenshots/screenshot_antag_icons_clownoperativemidround.png create mode 100644 code/modules/unit_tests/screenshots/screenshot_antag_icons_nuclearoperative.png delete mode 100644 config/dynamic.json create mode 100644 config/dynamic.toml delete mode 100644 maplestation_modules/code/modules/antagonists/advanced_traitor/advanced_traitor.dm delete mode 100644 maplestation_modules/code/modules/antagonists/infiltrator/advanced_infiltrator.dm delete mode 100644 maplestation_modules/code/modules/antagonists/infiltrator/infiltrator.dm create mode 100644 tgui/packages/tgui/interfaces/DynamicAdmin.tsx create mode 100644 tgui/packages/tgui/interfaces/DynamicTester.tsx diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 0f15e9cd271c..0cef14d35aed 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -233,6 +233,8 @@ GLOBAL_LIST_INIT(ai_employers, list( /// Checks if the given mob is a wizard #define IS_WIZARD(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/wizard)) +/// Checks if the given mob is a wizard apprentice +#define IS_WIZARD_APPRENTICE(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/wizard/apprentice)) /// Checks if the given mob is a revolutionary. Will return TRUE for rev heads as well. #define IS_REVOLUTIONARY(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/rev)) @@ -342,8 +344,11 @@ GLOBAL_LIST_INIT(human_invader_antagonists, list( #define ANTAG_GROUP_CREW "Deviant Crew" -// This flag disables certain checks that presume antagonist datums mean 'baddie'. -#define FLAG_FAKE_ANTAG (1 << 0) +/// Used to denote an antag datum that either isn't necessarily "evil" (like Valentines) +/// or isn't necessarily a "real" antag (like Ashwalkers) +#define ANTAG_FAKE (1 << 0) +/// Antag is not added to the global list of antags +#define ANTAG_SKIP_GLOBAL_LIST (1 << 1) #define HUNTER_PACK_COPS "Spacepol Fugitive Hunters" #define HUNTER_PACK_RUSSIAN "Russian Fugitive Hunters" diff --git a/code/__DEFINES/dcs/signals/signals_global_object.dm b/code/__DEFINES/dcs/signals/signals_global_object.dm index 452be16f8391..dd3ab4455011 100644 --- a/code/__DEFINES/dcs/signals/signals_global_object.dm +++ b/code/__DEFINES/dcs/signals/signals_global_object.dm @@ -1,6 +1,6 @@ /// signals from globally accessible objects -///from SSJob when DivideOccupations is called +///from SSJob when divide_occupations is called #define COMSIG_OCCUPATIONS_DIVIDED "occupations_divided" ///from SSsun when the sun changes position : (azimuth) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 9391a25ebb06..9fc8cb591106 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -74,6 +74,9 @@ #define COMSIG_MOB_MIND_TRANSFERRED_OUT_OF "mob_mind_transferred_out_of" /// From /mob/proc/ghostize() Called when a mob sucessfully ghosts #define COMSIG_MOB_GHOSTIZED "mob_ghostized" +/// can_roll_midround(datum/antagonist/antag_type) from certain midround rulesets, (mob/living/source, datum/mind/mind, datum/antagonist/antagonist) +#define COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL "mob_mind_before_midround_roll" + #define CANCEL_ROLL (1<<1) ///signal sent when a mob has their holy role set. Sent to the mob having their role changed. #define COMSIG_MOB_MIND_SET_HOLY_ROLE "mob_mind_set_holy_role" diff --git a/code/__DEFINES/dcs/signals/signals_subsystem.dm b/code/__DEFINES/dcs/signals/signals_subsystem.dm index 076fc1d3798b..bc85747b5c08 100644 --- a/code/__DEFINES/dcs/signals/signals_subsystem.dm +++ b/code/__DEFINES/dcs/signals/signals_subsystem.dm @@ -23,3 +23,6 @@ #define COMSIG_ADDED_POINT_OF_INTEREST "added_point_of_interest" /// Sent from base of /datum/controller/subsystem/points_of_interest/proc/on_poi_element_removed : (atom/old_poi) #define COMSIG_REMOVED_POINT_OF_INTEREST "removed_point_of_interest" + +/// Send after config is loaded but before picking roundstart rulesets +#define COMSIG_DYNAMIC_PRE_ROUNDSTART "dynamic_pre_roundstart" diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm deleted file mode 100644 index 07c833cdc494..000000000000 --- a/code/__DEFINES/dynamic.dm +++ /dev/null @@ -1,40 +0,0 @@ -/// This is the only ruleset that should be picked this round, used by admins and should not be on rulesets in code. -#define ONLY_RULESET (1 << 0) - -/// Only one ruleset with this flag will be picked. -#define HIGH_IMPACT_RULESET (1 << 1) - -/// This ruleset can only be picked once. Anything that does not have a scaling_cost MUST have this. -#define LONE_RULESET (1 << 2) - -/// This is a "heavy" midround ruleset, and should be run later into the round -#define MIDROUND_RULESET_STYLE_HEAVY "Heavy" - -/// This is a "light" midround ruleset, and should be run early into the round -#define MIDROUND_RULESET_STYLE_LIGHT "Light" - -/// No round event was hijacked this cycle -#define HIJACKED_NOTHING "HIJACKED_NOTHING" - -/// This cycle, a round event was hijacked when the last midround event was too recent. -#define HIJACKED_TOO_RECENT "HIJACKED_TOO_RECENT" - -/// Kill this ruleset from continuing to process -#define RULESET_STOP_PROCESSING 1 - -/// Requirements when something needs a lot of threat to run, but still possible at low-pop -#define REQUIREMENTS_VERY_HIGH_THREAT_NEEDED list(90,90,90,80,60,50,40,40,40,40) - -/// Max number of teams we can have for the abductor ruleset -#define ABDUCTOR_MAX_TEAMS 4 - -// Ruletype defines -#define ROUNDSTART_RULESET "Roundstart" -#define LATEJOIN_RULESET "Latejoin" -#define MIDROUND_RULESET "Midround" - -#define RULESET_NOT_FORCED "not forced" -/// Ruleset should run regardless of population and threat available -#define RULESET_FORCE_ENABLED "force enabled" -/// Ruleset should not run regardless of population and threat available -#define RULESET_FORCE_DISABLED "force disabled" diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 8419bd0902af..82f0d20945ab 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -213,6 +213,10 @@ #define JOB_LATEJOIN_ONLY (1<<11) /// This job is a head of staff. #define JOB_HEAD_OF_STAFF (1<<12) +/// This job will NEVER be selected as an antag role +#define JOB_ANTAG_BLACKLISTED (1<<13) +/// This job will never be selected as an antag role IF config `protect_roles_from_antagonist` is set +#define JOB_ANTAG_PROTECTED (1<<14) /// Combination flag for jobs which are considered regular crew members of the station. #define STATION_JOB_FLAGS (JOB_ANNOUNCE_ARRIVAL|JOB_CREW_MANIFEST|JOB_EQUIP_RANK|JOB_CREW_MEMBER|JOB_NEW_PLAYER_JOINABLE|JOB_REOPEN_ON_ROUNDSTART_LOSS|JOB_ASSIGN_QUIRKS|JOB_CAN_BE_INTERN) diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 09ba57c4ad36..4731186c0b01 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -30,6 +30,7 @@ #define ROLE_NINJA "Space Ninja" #define ROLE_OBSESSED "Obsessed" #define ROLE_OPERATIVE_MIDROUND "Operative (Midround)" +#define ROLE_CLOWN_OPERATIVE_MIDROUND "Clown Operative (Midround)" #define ROLE_PARADOX_CLONE "Paradox Clone" #define ROLE_REV_HEAD "Head Revolutionary" #define ROLE_SENTIENT_DISEASE "Sentient Disease" @@ -64,12 +65,12 @@ #define ROLE_REV "Revolutionary" #define ROLE_REVENANT "Revenant" #define ROLE_SENTIENCE "Sentience Potion Spawn" +/// This flag specifically is used as a generic catch-all antag ban #define ROLE_SYNDICATE "Syndicate" #define ROLE_CLOWN_OPERATIVE "Clown Operative" #define ROLE_FREE_GOLEM "Free Golem" #define ROLE_MORPH "Morph" -#define ROLE_NUCLEAR_OPERATIVE "Nuclear Operative" #define ROLE_POSITRONIC_BRAIN "Positronic Brain" #define ROLE_SANTA "Santa" #define ROLE_SERVANT_GOLEM "Servant Golem" @@ -112,55 +113,6 @@ #define ROLE_CYBER_TAC "Cyber Tac" #define ROLE_NETGUARDIAN "NetGuardian Prime" -/// This defines the antagonists you can operate with in the settings. -/// Keys are the antagonist, values are the number of days since the player's -/// first connection in order to play. -GLOBAL_LIST_INIT(special_roles, list( - // Roundstart - ROLE_BROTHER = 0, - ROLE_CHANGELING = 0, - ROLE_CLOWN_OPERATIVE = 14, - ROLE_CULTIST = 14, - ROLE_HERETIC = 0, - ROLE_MALF = 0, - ROLE_OPERATIVE = 14, - ROLE_REV_HEAD = 14, - ROLE_TRAITOR = 0, - ROLE_WIZARD = 14, - - // Midround - ROLE_ABDUCTOR = 0, - ROLE_ALIEN = 0, - ROLE_BLOB = 0, - ROLE_BLOB_INFECTION = 0, - ROLE_CHANGELING_MIDROUND = 0, - ROLE_FUGITIVE = 0, - ROLE_LONE_OPERATIVE = 14, - ROLE_MALF_MIDROUND = 0, - ROLE_NIGHTMARE = 0, - ROLE_NINJA = 0, - ROLE_OBSESSED = 0, - ROLE_OPERATIVE_MIDROUND = 14, - ROLE_PARADOX_CLONE = 0, - ROLE_REVENANT = 0, - ROLE_SENTIENT_DISEASE = 0, - ROLE_SLEEPER_AGENT = 0, - ROLE_SPACE_DRAGON = 0, - ROLE_SPIDER = 0, - ROLE_WIZARD_MIDROUND = 14, - - // Latejoin - ROLE_HERETIC_SMUGGLER = 0, - ROLE_PROVOCATEUR = 14, - ROLE_SYNDICATE_INFILTRATOR = 0, - ROLE_STOWAWAY_CHANGELING = 0, - - // I'm not too sure why these are here, but they're not moving. - ROLE_GLITCH = 0, - ROLE_PAI = 0, - ROLE_SENTIENCE = 0, -)) - //Job defines for what happens when you fail to qualify for any job during job selection #define BEOVERFLOW 1 #define BERANDOMJOB 2 diff --git a/code/__HELPERS/events.dm b/code/__HELPERS/events.dm index 9b5f939de15e..58d91c8caa8a 100644 --- a/code/__HELPERS/events.dm +++ b/code/__HELPERS/events.dm @@ -23,7 +23,6 @@ possible_spawns += spawn_turf if(!length(possible_spawns)) - message_admins("No valid generic_maintenance_landmark landmarks found, aborting...") return null return pick(possible_spawns) @@ -44,7 +43,6 @@ possible_spawns += get_turf(spawn_location) if(!length(possible_spawns)) - message_admins("No valid carpspawn landmarks found, aborting...") return null return pick(possible_spawns) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 1a1c8e3ef5be..3f0bfd1ca656 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -153,10 +153,9 @@ return ///Get active players who are playing in the round -/proc/get_active_player_count(alive_check = FALSE, afk_check = FALSE, human_check = FALSE) - var/active_players = 0 - for(var/i = 1; i <= GLOB.player_list.len; i++) - var/mob/player_mob = GLOB.player_list[i] +/proc/get_active_player_list(alive_check = FALSE, afk_check = FALSE, human_check = FALSE) + var/list/active_players = list() + for(var/mob/player_mob as anything in GLOB.player_list) if(!player_mob?.client) continue if(alive_check && player_mob.stat) @@ -171,9 +170,13 @@ var/mob/dead/observer/ghost_player = player_mob if(ghost_player.started_as_observer) // Exclude people who started as observers continue - active_players++ + active_players += player_mob return active_players +///Counts active players who are playing in the round +/proc/get_active_player_count(alive_check = FALSE, afk_check = FALSE, human_check = FALSE) + return length(get_active_player_list(alive_check, afk_check, human_check)) + ///Uses stripped down and bastardized code from respawn character /proc/make_body(mob/dead/observer/ghost_player) if(!ghost_player || !ghost_player.key) diff --git a/code/__HELPERS/logging/dynamic.dm b/code/__HELPERS/logging/dynamic.dm index 488aeaec3be7..70e3d8731633 100644 --- a/code/__HELPERS/logging/dynamic.dm +++ b/code/__HELPERS/logging/dynamic.dm @@ -1,8 +1,3 @@ -/// Log to dynamic and message admins -/datum/controller/subsystem/dynamic/proc/log_dynamic_and_announce(text) - message_admins("DYNAMIC: [text]") - log_dynamic("[text]") - /// Logging for dynamic procs /proc/log_dynamic(text, list/data) logger.Log(LOG_CATEGORY_DYNAMIC, text, data) diff --git a/code/__HELPERS/logging/manifest.dm b/code/__HELPERS/logging/manifest.dm index f4e7d16e4e1e..42d7ef724abf 100644 --- a/code/__HELPERS/logging/manifest.dm +++ b/code/__HELPERS/logging/manifest.dm @@ -1,6 +1,7 @@ /// Logging for player manifest (ckey, name, job, special role, roundstart/latejoin) /proc/log_manifest(ckey, datum/mind/mind, mob/body, latejoin = FALSE) - var/message = "[ckey] \\ [body.real_name] \\ [mind.assigned_role.title] \\ [mind.special_role || "NONE"] \\ [latejoin ? "LATEJOIN" : "ROUNDSTART"]" + var/roles = english_list(mind.get_special_roles(), nothing_text = "NONE") + var/message = "[ckey] \\ [body.real_name] \\ [mind.assigned_role.title] \\ [roles] \\ [latejoin ? "LATEJOIN" : "ROUNDSTART"]" logger.Log(LOG_CATEGORY_MANIFEST, message, list( "mind" = mind, "body" = body, "latejoin" = latejoin )) diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm index 76814458ae55..35e7898d9b4d 100644 --- a/code/__HELPERS/priority_announce.dm +++ b/code/__HELPERS/priority_announce.dm @@ -107,7 +107,7 @@ message.title = title message.content = text - SScommunications.send_message(message) + GLOB.communications_controller.send_message(message) /** * Sends a minor annoucement to players. diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 8078526d744a..4e6054a19ab2 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -353,15 +353,9 @@ GLOBAL_LIST_INIT(achievements_unlocked, list()) else parts += "[FOURSPACES]Nobody died this shift!" - parts += "[FOURSPACES]Threat level: [SSdynamic.threat_level]" - parts += "[FOURSPACES]Threat left: [SSdynamic.mid_round_budget]" - if(SSdynamic.roundend_threat_log.len) - parts += "[FOURSPACES]Threat edits:" - for(var/entry as anything in SSdynamic.roundend_threat_log) - parts += "[FOURSPACES][FOURSPACES][entry]
" - parts += "[FOURSPACES]Executed rules:" - for(var/datum/dynamic_ruleset/rule in SSdynamic.executed_rules) - parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - [rule.name]: -[rule.cost + rule.scaled_times * rule.scaling_cost] threat" + parts += "[FOURSPACES]Round: [SSdynamic.current_tier.name]" + for(var/datum/dynamic_ruleset/rule as anything in SSdynamic.executed_rulesets - SSdynamic.unreported_rulesets) + parts += "[FOURSPACES][FOURSPACES]- [rule.name] ([rule.config_tag])" return parts.Join("
") diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm index 3579ab472968..844adc95f57b 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -347,7 +347,7 @@ Versioning "name" = L.real_name, "key" = L.ckey, "job" = L.mind.assigned_role.title, - "special" = L.mind.special_role, + "special" = jointext(L.mind.get_special_roles(), " | "), "pod" = get_area_name(L, TRUE), "laname" = L.lastattacker, "lakey" = L.lastattackerckey, diff --git a/code/controllers/subsystem/communications.dm b/code/controllers/subsystem/communications.dm deleted file mode 100644 index 5d2d5a2e7f40..000000000000 --- a/code/controllers/subsystem/communications.dm +++ /dev/null @@ -1,64 +0,0 @@ -#define COMMUNICATION_COOLDOWN (30 SECONDS) -#define COMMUNICATION_COOLDOWN_AI (30 SECONDS) -#define COMMUNICATION_COOLDOWN_MEETING (5 MINUTES) - -SUBSYSTEM_DEF(communications) - name = "Communications" - flags = SS_NO_INIT | SS_NO_FIRE - - COOLDOWN_DECLARE(silicon_message_cooldown) - COOLDOWN_DECLARE(nonsilicon_message_cooldown) - - /// Are we trying to send a cross-station message that contains soft-filtered words? If so, flip to TRUE to extend the time admins have to cancel the message. - var/soft_filtering = FALSE - - /// A list of footnote datums, to be added to the bottom of the roundstart command report. - var/list/command_report_footnotes = list() - /// A counter of conditions that are blocking the command report from printing. Counter incremements up for every blocking condition, and de-incrememnts when it is complete. - var/block_command_report = 0 - /// Has a special xenomorph egg been delivered? - var/xenomorph_egg_delivered = FALSE - /// The location where the special xenomorph egg was planted - var/area/captivity_area - -/datum/controller/subsystem/communications/proc/can_announce(mob/living/user, is_silicon) - if(is_silicon && COOLDOWN_FINISHED(src, silicon_message_cooldown)) - return TRUE - else if(!is_silicon && COOLDOWN_FINISHED(src, nonsilicon_message_cooldown)) - return TRUE - else - return FALSE - -/datum/controller/subsystem/communications/proc/make_announcement(mob/living/user, is_silicon, input, syndicate, list/players) - if(!can_announce(user, is_silicon)) - return FALSE - if(is_silicon) - minor_announce(html_decode(input),"[user.name] announces:", players = players) - COOLDOWN_START(src, silicon_message_cooldown, COMMUNICATION_COOLDOWN_AI) - else - var/list/message_data = user.treat_message(input) - if(syndicate) - priority_announce(html_decode(message_data["message"]), null, 'sound/misc/announce_syndi.ogg', ANNOUNCEMENT_TYPE_SYNDICATE, has_important_message = TRUE, players = players, color_override = "red") - else - priority_announce(html_decode(message_data["message"]), null, 'sound/misc/announce.ogg', ANNOUNCEMENT_TYPE_CAPTAIN, has_important_message = TRUE, players = players) - COOLDOWN_START(src, nonsilicon_message_cooldown, COMMUNICATION_COOLDOWN) - user.log_talk(input, LOG_SAY, tag="priority announcement") - message_admins("[ADMIN_LOOKUPFLW(user)] has made a priority announcement.") - -/datum/controller/subsystem/communications/proc/send_message(datum/comm_message/sending,print = TRUE,unique = FALSE) - for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list) - if(!(C.machine_stat & (BROKEN|NOPOWER)) && is_station_level(C.z)) - if(unique) - C.add_message(sending) - else //We copy the message for each console, answers and deletions won't be shared - var/datum/comm_message/M = new(sending.title,sending.content,sending.possible_answers.Copy()) - C.add_message(M) - if(print) - var/obj/item/paper/printed_paper = new /obj/item/paper(C.loc) - printed_paper.name = "paper - '[sending.title]'" - printed_paper.add_raw_text(sending.content) - printed_paper.update_appearance() - -#undef COMMUNICATION_COOLDOWN -#undef COMMUNICATION_COOLDOWN_AI -#undef COMMUNICATION_COOLDOWN_MEETING diff --git a/code/controllers/subsystem/dynamic/__dynamic_defines.dm b/code/controllers/subsystem/dynamic/__dynamic_defines.dm new file mode 100644 index 000000000000..b22e7ff0798f --- /dev/null +++ b/code/controllers/subsystem/dynamic/__dynamic_defines.dm @@ -0,0 +1,55 @@ +// Config values, don't change these randomly +/// Configuring "roundstart" type rulesets +#define ROUNDSTART "roundstart" +/// Configuring "light midround" type rulesets +#define LIGHT_MIDROUND "light_midround" +/// Configuring "heavy midround" type rulesets +#define HEAVY_MIDROUND "heavy_midround" +/// Configuring "latejoin" type rulesets +#define LATEJOIN "latejoin" + +/// Lower end for how many of a ruleset type can be selected +#define LOW_END "low" +/// Upper end for how many of a ruleset type can be selected +#define HIGH_END "high" +/// Population threshold for ruleset types - below this, only a quarter of the low to high end is used +#define HALF_RANGE_POP_THRESHOLD "half_range_pop_threshold" +/// Population threshold for ruleset types - below this, only a half of the low to high end is used +#define FULL_RANGE_POP_THRESHOLD "full_range_pop_threshold" +/// Round time threshold for which a ruleset type will be selected +#define TIME_THRESHOLD "time_threshold" +/// Lower end for cooldown duration for a ruleset type +#define EXECUTION_COOLDOWN_LOW "execution_cooldown_low" +/// Upper end for cooldown duration for a ruleset type +#define EXECUTION_COOLDOWN_HIGH "execution_cooldown_high" + +// Tiers, don't change these randomly +/// Tier 0, no antags at all +#define DYNAMIC_TIER_GREEN 0 +/// Tier 1, low amount of antags +#define DYNAMIC_TIER_LOW 1 +/// Tier 2, medium amount of antags +#define DYNAMIC_TIER_LOWMEDIUM 2 +/// Tier 3, high amount of antags +#define DYNAMIC_TIER_MEDIUMHIGH 3 +/// Tier 4, maximum amount of antags +#define DYNAMIC_TIER_HIGH 4 + +// Ruleset flags +/// Ruleset denotes that it involves an outside force spawning in to attack the station +#define RULESET_INVADER (1<<0) +/// Multiple high impact rulesets cannot be selected unless we're at the highest tier +#define RULESET_HIGH_IMPACT (1<<1) +/// Ruleset can be configured by admins (implements /proc/configure_ruleset) +/// Only implemented for midrounds currently +#define RULESET_ADMIN_CONFIGURABLE (1<<2) + +/// Href for cancelling midround rulesets before execution +#define MIDROUND_CANCEL_HREF(...) "(CANCEL)" +/// Href for rerolling midround rulesets before execution +#define MIDROUND_REROLL_HREF(rulesets) "[length(rulesets) \ + ? "(SOMETHING ELSE)" \ + : "([span_tooltip("There are no more rulesets to pick from!", "NOTHING ELSE")])"\ +]" + +#define RULESET_CONFIG_CANCEL "Cancel" diff --git a/code/controllers/subsystem/dynamic/_dynamic_ruleset.dm b/code/controllers/subsystem/dynamic/_dynamic_ruleset.dm new file mode 100644 index 000000000000..2ca153493399 --- /dev/null +++ b/code/controllers/subsystem/dynamic/_dynamic_ruleset.dm @@ -0,0 +1,372 @@ +/** + * ## Dynamic ruleset datum + * + * These datums (which are not singletons) are used by dynamic to create antagonists + */ +/datum/dynamic_ruleset + /// Human-readable name of the ruleset. + var/name + /// Tag the ruleset uses for configuring. + /// Don't change this unless you know what you're doing. + var/config_tag + + /// What flag to check for jobbans? Optional, if unset, uses pref_flag + var/jobban_flag + /// What flag to check for prefs? Required if the antag has an associated preference + var/pref_flag + /// Flags for this ruleset + var/ruleset_flags = NONE + /// Points to what antag datum this ruleset will use for generating a preview icon in the prefs menu + var/preview_antag_datum + /// List of all minds selected for this ruleset + VAR_FINAL/list/datum/mind/selected_minds = list() + + /** + * The chance the ruleset is picked when selecting from the pool of rulesets. + * + * This can either be + * - A list of weight corresponding to dynamic tiers. + * If a tier is not specified, it will use the next highest tier. + * Or + * - A single weight for all tiers. + */ + var/list/weight = 0 + /** + * The min population for which this ruleset is available. + * + * This can either be + * - A list of min populations corresponding to dynamic tiers. + * If a tier is not specified, it will use the next highest tier. + * Or + * - A single min population for all tiers. + */ + var/list/min_pop = 0 + /// List of roles that are blacklisted from this ruleset + /// For roundstart rulesets, it will prevent players from being selected for this ruleset if they have one of these roles + /// For latejoin or midround rulesets, it will prevent players from being assigned to this ruleset if they have one of these roles + var/list/blacklisted_roles = list() + /** + * How many candidates are needed for this ruleset to be selected? + * Ie. "We won't even bother attempting to run this ruleset unless at least x players want to be it" + * + * This can either be + * - A number + * Or + * - A list in the form of list("denominator" = x, "offset" = y) + * which will divide the population size by x and add y to it to calculate the number of candidates + */ + var/min_antag_cap = 1 + /** + * How many candidates will be this ruleset try to select? + * Ie. "We have 10 cadidates, but we only want x of them to be antags" + * + * This can either be + * - A number + * Or + * - A list in the form of list("denominator" = x, "offset" = y) + * which will divide the population size by x and add y to it to calculate the number of candidates + * + * If null, defaults to min_antag_cap + */ + var/max_antag_cap + /// If set to TRUE, dynamic will be able to draft this ruleset again later on + var/repeatable = FALSE + /// Every time this ruleset is selected, the weight will be decreased by this amount + var/repeatable_weight_decrease = 2 + /// Players whose account is less than this many days old will be filtered out of the candidate list + var/minimum_required_age = 0 + /// Templates necessary for this ruleset to be executed + VAR_PROTECTED/list/ruleset_lazy_templates + +/datum/dynamic_ruleset/New(list/dynamic_config) + for(var/new_var in dynamic_config?[config_tag]) + set_config_value(new_var, dynamic_config[config_tag][new_var]) + +/datum/dynamic_ruleset/Destroy() + selected_minds = null + return ..() + +/// Used for parsing config entries to validate them +/datum/dynamic_ruleset/proc/set_config_value(new_var, new_val) + if(!(new_var in vars)) + log_dynamic("Erroneous config edit rejected: [new_var]") + return FALSE + var/static/list/locked_config_values = list( + NAMEOF_STATIC(src, config_tag), + NAMEOF_STATIC(src, jobban_flag), + NAMEOF_STATIC(src, pref_flag), + NAMEOF_STATIC(src, preview_antag_datum), + NAMEOF_STATIC(src, ruleset_flags), + NAMEOF_STATIC(src, ruleset_lazy_templates), + NAMEOF_STATIC(src, selected_minds), + NAMEOF_STATIC(src, vars), + ) + + if(new_var in locked_config_values) + log_dynamic("Bad config edit rejected: [new_var]") + return FALSE + if(islist(new_val) && (new_var == NAMEOF(src, weight) || new_var == NAMEOF(src, min_pop))) + new_val = load_tier_list(new_val) + + vars[new_var] = new_val + return TRUE + +/datum/dynamic_ruleset/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, config_tag)) + return FALSE + return ..() + +/// Used to create tier lists for weights and min_pop values +/datum/dynamic_ruleset/proc/load_tier_list(list/incoming_list) + PRIVATE_PROC(TRUE) + + var/list/tier_list = new /list(4) + // loads a list of list("2" = 1, "3" = 3) into a list(null, 1, 3, null) + for(var/tier in incoming_list) + tier_list[text2num(tier)] = incoming_list[tier] + + // turn list(null, 1, 3, null) into list(1, 1, 3, null) + for(var/i in 1 to length(tier_list)) + var/val = tier_list[i] + if(isnum(val)) + break + for(var/j in i to length(tier_list)) + var/other_val = tier_list[j] + if(!isnum(other_val)) + continue + tier_list[i] = other_val + break + + // turn list(1, 1, 3, null) into list(1, 1, 3, 3) + for(var/i in length(tier_list) to 1 step -1) + var/val = tier_list[i] + if(isnum(val)) + break + for(var/j in i to 1 step -1) + var/other_val = tier_list[j] + if(!isnum(other_val)) + continue + tier_list[i] = other_val + break + + // we can assert that tier[1] and tier[4] are not null, but we cannot say the same for tier[2] and tier[3] + // this can be happen due to the following setup: list(1, null, null, 4) + // (which is an invalid config, and should be fixed by the operator) + if(isnull(tier_list[2])) + tier_list[2] = tier_list[1] + if(isnull(tier_list[3])) + tier_list[3] = tier_list[4] + + return tier_list + +/** + * Any additional checks to see if this ruleset can be selected + */ +/datum/dynamic_ruleset/proc/can_be_selected() + return TRUE + +/** + * Calculates the weight of this ruleset for the given tier. + * + * * population_size - How many players are alive + * * tier - The dynamic tier to calculate the weight for + */ +/datum/dynamic_ruleset/proc/get_weight(population_size = 0, tier = DYNAMIC_TIER_LOW) + SHOULD_NOT_OVERRIDE(TRUE) + + if(type in SSdynamic.admin_disabled_rulesets) + return 0 + if(!can_be_selected()) + return 0 + var/final_minpop = islist(min_pop) ? min_pop[tier] : min_pop + if(final_minpop > population_size) + return 0 + + var/final_weight = islist(weight) ? weight[tier] : weight + for(var/datum/dynamic_ruleset/other_ruleset as anything in SSdynamic.executed_rulesets) + if(other_ruleset == src) + continue + if(tier != DYNAMIC_TIER_HIGH && (ruleset_flags & RULESET_HIGH_IMPACT) && (other_ruleset.ruleset_flags & RULESET_HIGH_IMPACT)) + return 0 + if(!istype(other_ruleset, type)) + continue + if(!repeatable) + return 0 + final_weight -= repeatable_weight_decrease + + return max(final_weight, 0) + +/// Returns what the antag cap with the given population is. +/datum/dynamic_ruleset/proc/get_antag_cap(population_size, antag_cap) + SHOULD_NOT_OVERRIDE(TRUE) + if (isnum(antag_cap)) + return antag_cap + + return ceil(population_size / antag_cap["denominator"]) + antag_cap["offset"] + +/** + * Prepares the ruleset for execution, primarily used for selecting the players who will be assigned to this ruleset + * + * * antag_candidates - List of players who are candidates for this ruleset + * This list is mutated by this proc! + * + * Returns TRUE if execution is ready, FALSE if it should be canceled + */ +/datum/dynamic_ruleset/proc/prepare_execution(population_size = 0, list/mob/antag_candidates = list()) + SHOULD_NOT_OVERRIDE(TRUE) + + // !! THIS SLEEPS !! + load_templates() + + // This is (mostly) redundant, buuuut the (potential) sleep above makes it iffy, so let's just be safe + if(!can_be_selected()) + return FALSE + + var/max_candidates = get_antag_cap(population_size, max_antag_cap || min_antag_cap) + var/min_candidates = get_antag_cap(population_size, min_antag_cap) + + var/list/selected_candidates = select_candidates(antag_candidates, max_candidates) + if(length(selected_candidates) < min_candidates) + return FALSE + + for(var/mob/candidate as anything in selected_candidates) + var/datum/mind/candidate_mind = get_candidate_mind(candidate) + prepare_for_role(candidate_mind) + LAZYADDASSOC(SSjob.prevented_occupations, candidate_mind, get_blacklisted_roles()) // this is what makes sure you can't roll traitor as a sec-off + selected_minds += candidate_mind + antag_candidates -= candidate + + return TRUE + +/// Gets the mind of a candidate, can be overridden to return a different mind if necessary +/datum/dynamic_ruleset/proc/get_candidate_mind(mob/dead/candidate) + return candidate.mind + +/// Returns a list of roles that cannot be selected for this ruleset +/datum/dynamic_ruleset/proc/get_blacklisted_roles() + return get_config_blacklisted_roles() | get_always_blacklisted_roles() + +/// Returns all the jobs the config says this ruleset cannot select +/datum/dynamic_ruleset/proc/get_config_blacklisted_roles() + SHOULD_NOT_OVERRIDE(TRUE) + var/list/blacklist = blacklisted_roles.Copy() + for(var/datum/job/job as anything in SSjob.all_occupations) + var/protected = (job.job_flags & JOB_ANTAG_PROTECTED) + var/blacklisted = (job.job_flags & JOB_ANTAG_BLACKLISTED) + if((CONFIG_GET(flag/protect_roles_from_antagonist) && protected) || blacklisted) + blacklist |= job.title + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + blacklisted_roles |= JOB_ASSISTANT + return blacklist + +/// Returns a list of roles that are always blacklisted from this ruleset, for mechanical reasons (an AI can't be a changeling) +/datum/dynamic_ruleset/proc/get_always_blacklisted_roles() + return list( + JOB_AI, + JOB_CYBORG, + ) + +/// Takes in a list of players and returns a list of players who are valid candidates for this ruleset +/// Don't touch this proc if you need to trim candidates further - override is_valid_candidate() instead +/datum/dynamic_ruleset/proc/trim_candidates(list/mob/antag_candidates) + SHOULD_NOT_OVERRIDE(TRUE) + + var/list/valid_candidates = list() + for(var/mob/candidate as anything in antag_candidates) + var/client/candidate_client = GET_CLIENT(candidate) + if(isnull(candidate_client)) + continue + if(candidate_client.get_remaining_days(minimum_required_age) > 0) + continue + if(pref_flag && !(pref_flag in candidate_client.prefs.be_special)) + continue + if(is_banned_from(candidate.ckey, list(ROLE_SYNDICATE, jobban_flag || pref_flag))) + continue + if(!is_valid_candidate(candidate, candidate_client)) + continue + valid_candidates += candidate + return valid_candidates + +/// Returns a list of players picked for this ruleset +/datum/dynamic_ruleset/proc/select_candidates(list/mob/antag_candidates, num_candidates = 0) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + if(num_candidates <= 0) + return list() + + // technically not pure + var/list/resulting_candidates = shuffle(trim_candidates(antag_candidates)) || list() + if(length(resulting_candidates) <= num_candidates) + return resulting_candidates + + resulting_candidates.Cut(num_candidates + 1) + return resulting_candidates + +/// Handles loading map templates that this ruleset requires +/datum/dynamic_ruleset/proc/load_templates() + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + for(var/template in ruleset_lazy_templates) + SSmapping.lazy_load_template(template) + +/** + * Any additional checks to see if this player is a valid candidate for this ruleset + */ +/datum/dynamic_ruleset/proc/is_valid_candidate(mob/candidate, client/candidate_client) + SHOULD_CALL_PARENT(TRUE) + return TRUE + +/** + * Handles any special logic that needs to be done for a player before they are assigned to this ruleset + * This is ran before the player is in their job position, and before they even have a player character + * + * Override this proc to do things like set forced jobs, DON'T assign roles or give out equipments here! + */ +/datum/dynamic_ruleset/proc/prepare_for_role(datum/mind/candidate) + PROTECTED_PROC(TRUE) + return + +/** + * Executes the ruleset, assigning the selected players to their roles. + * No backing out now, at this point it's guaranteed to run. + * + * Prefer to override assign_role() instead of this proc + */ +/datum/dynamic_ruleset/proc/execute() + var/list/execute_args = create_execute_args() + for(var/datum/mind/mind as anything in selected_minds) + assign_role(arglist(list(mind) + execute_args)) + +/// Allows you to supply extra arguments to assign_role() if needed +/datum/dynamic_ruleset/proc/create_execute_args() + return list() + +/** + * Used by the ruleset to actually assign the role to the player + * This is ran after they have a player character spawned, and after they're in their job (with all their job equipment) + * + * Override this proc to give out antag datums or special items or whatever + */ +/datum/dynamic_ruleset/proc/assign_role(datum/mind/candidate) + PROTECTED_PROC(TRUE) + stack_trace("Ruleset [src] does not implement assign_role()") + return + +/** + * Handles setting SSticker news report / mode result for more impactful rulsets + * + * Return TRUE if any result was set + */ +/datum/dynamic_ruleset/proc/round_result() + return FALSE + +/** + * Allows admins to configure rulesets before prepare_execution() is called. + * + * Only called if RULESET_ADMIN_CONFIGURABLE is set in ruleset_flags. + * Also only called by midrounds currently. + */ +/datum/dynamic_ruleset/proc/configure_ruleset(mob/admin) + stack_trace("Ruleset [type] sets flag RULESET_ADMIN_CONFIGURABLE but does not implement configure_ruleset!") diff --git a/code/controllers/subsystem/dynamic/_dynamic_tier.dm b/code/controllers/subsystem/dynamic/_dynamic_tier.dm new file mode 100644 index 000000000000..3d1fe57f17e6 --- /dev/null +++ b/code/controllers/subsystem/dynamic/_dynamic_tier.dm @@ -0,0 +1,316 @@ +/** + * ## Dynamic tier datum + * + * These datums are essentially used to configure the dynamic system + * They serve as a very simple way to see at a glance what dynamic is doing and what it is going to do + * + * For example, a tier will say "we will spawn 1-2 roundstart antags" + */ +/datum/dynamic_tier + /// Tier number - A number which determines the severity of the tier - the higher the number, the more antags + var/tier = -1 + /// The human readable name of the tier + var/name + /// Tag the tier uses for configuring. + /// Don't change this unless you know what you're doing. + var/config_tag + /// The chance this tier will be selected from all tiers + /// Keep all tiers added up to 100 weight, keeps things readable + var/weight = 0 + /// This tier will not be selected if the population is below this number + var/min_pop = 0 + + /// String which is sent to the players reporting which tier is active + var/advisory_report + + /** + * How Dynamic will select rulesets based on the tier + * + * Every tier configures each of the ruleset types - ie, roundstart, light midround, heavy midround, latejoin + * + * Every type can be configured with the following: + * - LOW_END: The lower for how many of this ruleset type can be selected + * - HIGH_END: The upper for how many of this ruleset type can be selected + * - HALF_RANGE_POP_THRESHOLD: Below this population range, the high end is quartered + * - FULL_RANGE_POP_THRESHOLD: Below this population range, the high end is halved + * + * Non-roundstart ruleset types also have: + * - TIME_THRESHOLD: World time must pass this threshold before dynamic starts running this ruleset type + * - EXECUTION_COOLDOWN_LOW: The lower end for how long to wait before running this ruleset type again + * - EXECUTION_COOLDOWN_HIGH: The upper end for how long to wait before running this ruleset type again + */ + var/list/ruleset_type_settings = list( + ROUNDSTART = list( + LOW_END = 0, + HIGH_END = 0, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 50, + TIME_THRESHOLD = 0 MINUTES, + EXECUTION_COOLDOWN_LOW = 0 MINUTES, + EXECUTION_COOLDOWN_HIGH = 0 MINUTES, + ), + LIGHT_MIDROUND = list( + LOW_END = 0, + HIGH_END = 0, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 30 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + HEAVY_MIDROUND = list( + LOW_END = 0, + HIGH_END = 0, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 60 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + LATEJOIN = list( + LOW_END = 0, + HIGH_END = 0, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 0 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + ) + +/datum/dynamic_tier/New(list/dynamic_config) + for(var/new_var in dynamic_config?[config_tag]) + if(!(new_var in vars)) + continue + set_config_value(new_var, dynamic_config[config_tag][new_var]) + +/// Used for parsing config entries to validate them +/datum/dynamic_tier/proc/set_config_value(new_var, new_val) + switch(new_var) + if(NAMEOF(src, tier), NAMEOF(src, config_tag), NAMEOF(src, vars)) + return FALSE + if(NAMEOF(src, ruleset_type_settings)) + for(var/category in new_val) + for(var/rule in new_val[category]) + if(rule == LOW_END || rule == HIGH_END) + ruleset_type_settings[category][rule] = max(0, new_val[category][rule]) + else if(rule == TIME_THRESHOLD || rule == EXECUTION_COOLDOWN_LOW || rule == EXECUTION_COOLDOWN_HIGH) + ruleset_type_settings[category][rule] = new_val[category][rule] * 1 MINUTES + else + ruleset_type_settings[category][rule] = new_val[category][rule] + return TRUE + + vars[new_var] = new_val + return TRUE + +/datum/dynamic_tier/vv_edit_var(var_name, var_value) + switch(var_name) + if(NAMEOF(src, tier)) + return FALSE + + return ..() + +/datum/dynamic_tier/greenshift + tier = DYNAMIC_TIER_GREEN + config_tag = "Greenshift" + name = "Greenshift" + weight = 2 + + advisory_report = "Advisory Level: Green Star
\ + Your sector's advisory level is Green Star. \ + Surveillance information shows no credible threats to Nanotrasen assets within the Spinward Sector at this time. \ + As always, the Department advises maintaining vigilance against potential threats, regardless of a lack of known threats." + +/datum/dynamic_tier/low + tier = DYNAMIC_TIER_LOW + config_tag = "Low Chaos" + name = "Low Chaos" + weight = 8 + + advisory_report = "Advisory Level: Yellow Star
\ + Your sector's advisory level is Yellow Star. \ + Surveillance shows a credible risk of enemy attack against our assets in the Spinward Sector. \ + We advise a heightened level of security alongside maintaining vigilance against potential threats." + + ruleset_type_settings = list( + ROUNDSTART = list( + LOW_END = 1, + HIGH_END = 1, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + ), + LIGHT_MIDROUND = list( + LOW_END = 0, + HIGH_END = 2, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 30 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + HEAVY_MIDROUND = list( + LOW_END = 0, + HIGH_END = 1, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 60 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + LATEJOIN = list( + LOW_END = 0, + HIGH_END = 1, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 5 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + ) + +/datum/dynamic_tier/lowmedium + tier = DYNAMIC_TIER_LOWMEDIUM + config_tag = "Low-Medium Chaos" + name = "Low-Medium Chaos" + weight = 46 + + advisory_report = "Advisory Level: Red Star
\ + Your sector's advisory level is Red Star. \ + The Department of Intelligence has decrypted Cybersun communications suggesting a high likelihood of attacks \ + on Nanotrasen assets within the Spinward Sector. \ + Stations in the region are advised to remain highly vigilant for signs of enemy activity and to be on high alert." + + ruleset_type_settings = list( + ROUNDSTART = list( + LOW_END = 1, + HIGH_END = 2, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + ), + LIGHT_MIDROUND = list( + LOW_END = 0, + HIGH_END = 2, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 30 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + HEAVY_MIDROUND = list( + LOW_END = 0, + HIGH_END = 1, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 60 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + LATEJOIN = list( + LOW_END = 1, + HIGH_END = 2, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 5 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + ) + +/datum/dynamic_tier/mediumhigh + tier = DYNAMIC_TIER_MEDIUMHIGH + config_tag = "Medium-High Chaos" + name = "Medium-High Chaos" + weight = 36 + + advisory_report = "Advisory Level: Black Orbit
\ + Your sector's advisory level is Black Orbit. \ + Your sector's local communications network is currently undergoing a blackout, \ + and we are therefore unable to accurately judge enemy movements within the region. \ + However, information passed to us by GDI suggests a high amount of enemy activity in the sector, \ + indicative of an impending attack. Remain on high alert and vigilant against any other potential threats." + + ruleset_type_settings = list( + ROUNDSTART = list( + LOW_END = 2, + HIGH_END = 3, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + ), + LIGHT_MIDROUND = list( + LOW_END = 1, + HIGH_END = 2, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 30 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + HEAVY_MIDROUND = list( + LOW_END = 1, + HIGH_END = 2, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 60 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + LATEJOIN = list( + LOW_END = 1, + HIGH_END = 3, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 5 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + ) + +/datum/dynamic_tier/high + tier = DYNAMIC_TIER_HIGH + config_tag = "High Chaos" + name = "High Chaos" + weight = 10 + + min_pop = 25 + + advisory_report = "Advisory Level: Midnight Sun
\ + Your sector's advisory level is Midnight Sun. \ + Credible information passed to us by GDI suggests that the Syndicate \ + is preparing to mount a major concerted offensive on Nanotrasen assets in the Spinward Sector to cripple our foothold there. \ + All stations should remain on high alert and prepared to defend themselves." + + ruleset_type_settings = list( + ROUNDSTART = list( + LOW_END = 3, + HIGH_END = 4, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + ), + LIGHT_MIDROUND = list( + LOW_END = 1, + HIGH_END = 2, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 20 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + HEAVY_MIDROUND = list( + LOW_END = 2, + HIGH_END = 4, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 30 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + LATEJOIN = list( + LOW_END = 2, + HIGH_END = 3, + HALF_RANGE_POP_THRESHOLD = 25, + FULL_RANGE_POP_THRESHOLD = 40, + TIME_THRESHOLD = 5 MINUTES, + EXECUTION_COOLDOWN_LOW = 10 MINUTES, + EXECUTION_COOLDOWN_HIGH = 20 MINUTES, + ), + ) diff --git a/code/controllers/subsystem/dynamic/dynamic.dm b/code/controllers/subsystem/dynamic/dynamic.dm index 60fd3cba20d3..6fd7fd289720 100644 --- a/code/controllers/subsystem/dynamic/dynamic.dm +++ b/code/controllers/subsystem/dynamic/dynamic.dm @@ -1,1057 +1,717 @@ -#define FAKE_GREENSHIFT_FORM_CHANCE 15 -#define FAKE_REPORT_CHANCE 8 -#define PULSAR_REPORT_CHANCE 8 -#define REPORT_NEG_DIVERGENCE -15 -#define REPORT_POS_DIVERGENCE 15 - -// Are HIGH_IMPACT_RULESETs allowed to stack? -GLOBAL_VAR_INIT(dynamic_no_stacking, TRUE) -// If enabled does not accept or execute any rulesets. -GLOBAL_VAR_INIT(dynamic_forced_extended, FALSE) -// How high threat is required for HIGH_IMPACT_RULESETs stacking. -// This is independent of dynamic_no_stacking. -GLOBAL_VAR_INIT(dynamic_stacking_limit, 90) -// List of forced roundstart rulesets. -GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset) -// Forced threat level, setting this to zero or higher forces the roundstart threat to the value. -GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) -/// Modify the threat level for station traits before dynamic can be Initialized. List(instance = threat_reduction) -GLOBAL_LIST_EMPTY(dynamic_station_traits) -/// Rulesets which have been forcibly enabled or disabled -GLOBAL_LIST_EMPTY(dynamic_forced_rulesets) - SUBSYSTEM_DEF(dynamic) name = "Dynamic" flags = SS_NO_INIT - wait = 1 SECONDS - - // Threat logging vars - /// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations - var/threat_level = 0 - - /// Set at the beginning of the round. Spent by the mode to "purchase" rules. Everything else goes in the postround budget. - var/round_start_budget = 0 - - /// Set at the beginning of the round. Spent by midrounds and latejoins. - var/mid_round_budget = 0 - - /// The initial round start budget for logging purposes, set once at the beginning of the round. - var/initial_round_start_budget = 0 - - /// Running information about the threat. Can store text or datum entries. - var/list/threat_log = list() - /// Threat log shown on the roundend report. Should only list player-made edits. - var/list/roundend_threat_log = list() - /// List of latejoin rules used for selecting the rules. - var/list/latejoin_rules - /// List of midround rules used for selecting the rules. - var/list/midround_rules - /** # Pop range per requirement. - * If the value is five the range is: - * 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+ - * If it is six the range is: - * 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+ - * If it is seven the range is: - * 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+ - */ - var/pop_per_requirement = 6 - /// Number of players who were ready on roundstart. - var/roundstart_pop_ready = 0 - /// List of candidates used on roundstart rulesets. - var/list/candidates = list() - /// Rules that are processed, rule_process is called on the rules in this list. - var/list/current_rules = list() - /// List of executed rulesets. - var/list/executed_rules = list() - /// If TRUE, the next player to latejoin will guarantee roll for a random latejoin antag - /// (this does not guarantee they get said antag roll, depending on preferences and circumstances) - var/forced_injection = FALSE - /// Forced ruleset to be executed for the next latejoin. - var/datum/dynamic_ruleset/latejoin/forced_latejoin_rule = null - /// How many percent of the rounds are more peaceful. - var/peaceful_percentage = 50 - /// If a high impact ruleset was executed. Only one will run at a time in most circumstances. - var/high_impact_ruleset_executed = FALSE - /// If a only ruleset has been executed. - var/only_ruleset_executed = FALSE - /// Dynamic configuration, loaded on pre_setup - var/list/configuration = null - - /// When world.time is over this number the mode tries to inject a latejoin ruleset. - var/latejoin_injection_cooldown = 0 - - /// The minimum time the recurring latejoin ruleset timer is allowed to be. - var/latejoin_delay_min = (5 MINUTES) - - /// The maximum time the recurring latejoin ruleset timer is allowed to be. - var/latejoin_delay_max = (25 MINUTES) - - /// The low bound for the midround roll time splits. - /// This number influences where to place midround rolls, making this smaller - /// will make midround rolls more frequent, and vice versa. - /// A midround will never be able to roll before this. - var/midround_lower_bound = 10 MINUTES - - /// The upper bound for the midround roll time splits. - /// This number influences where to place midround rolls, making this larger - /// will make midround rolls less frequent, and vice versa. - /// A midround will never be able to roll farther than this. - var/midround_upper_bound = 100 MINUTES - - /// The distance between the chosen midround roll point (which is deterministic), - /// and when it can actually roll. - /// Basically, if this is set to 5 minutes, and a midround roll point is decided to be at 20 minutes, - /// then it can roll anywhere between 15 and 25 minutes. - var/midround_roll_distance = 3 MINUTES - - /// The amount of threat per midround roll. - /// Basically, if this is set to 5, then for every 5 threat, one midround roll will be added. - /// The equation this is used in rounds up, meaning that if this is set to 5, and you have 6 - /// threat, then you will get 2 midround rolls. - var/threat_per_midround_roll = 7 - - /// A number between -5 and +5. - /// A negative value will give a more peaceful round and - /// a positive value will give a round with higher threat. - var/threat_curve_centre = 0 - - /// A number between 0.5 and 4. - /// Higher value will favour extreme rounds and - /// lower value rounds closer to the average. - var/threat_curve_width = 1.8 - - /// A number between -5 and +5. - /// Equivalent to threat_curve_centre, but for the budget split. - /// A negative value will weigh towards midround rulesets, and a positive - /// value will weight towards roundstart ones. - var/roundstart_split_curve_centre = 1 - - /// A number between 0.5 and 4. - /// Equivalent to threat_curve_width, but for the budget split. - /// Higher value will favour more variance in splits and - /// lower value rounds closer to the average. - var/roundstart_split_curve_width = 1.8 - - /// The minimum amount of time for antag random events to be hijacked. - var/random_event_hijack_minimum = 10 MINUTES - - /// The maximum amount of time for antag random events to be hijacked. - var/random_event_hijack_maximum = 18 MINUTES - - /// What is the lower bound of when the roundstart annoucement is sent out? - var/waittime_l = 600 - - /// What is the higher bound of when the roundstart annoucement is sent out? - var/waittime_h = 1800 - - /// A number between 0 and 100. The maximum amount of threat allowed to generate. - var/max_threat_level = 100 - - /// 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. - var/hijacked_random_event_injection_chance_modifier = 50 - - /// Any midround before this point is guaranteed to be light - var/midround_light_upper_bound = 25 MINUTES - - /// Any midround after this point is guaranteed to be heavy - var/midround_heavy_lower_bound = 55 MINUTES - - /// If there are less than this many players readied, threat level will be lowered. - /// This number should be kept fairly low, as there are other measures that population - /// impacts Dynamic, such as the requirements variable on rulesets. - var/low_pop_player_threshold = 20 - - /// The maximum threat that can roll with *zero* players. - /// As the number of players approaches `low_pop_player_threshold`, the maximum - /// threat level will increase. - /// For example, if `low_pop_maximum_threat` is 50, `low_pop_player_threshold` is 20, - /// and the number of readied players is 10, then the highest threat that can roll is - /// lerp(50, 100, 10 / 20), AKA 75. - var/low_pop_maximum_threat = 40 - - /// The chance for latejoins to roll when ready - var/latejoin_roll_chance = 50 - - // == EVERYTHING BELOW THIS POINT SHOULD NOT BE CONFIGURED == - - /// A list of recorded "snapshots" of the round, stored in the dynamic.json log - var/list/datum/dynamic_snapshot/snapshots - - /// The time when the last midround injection was attempted, whether or not it was successful - var/last_midround_injection_attempt = 0 - - /// Whether or not a random event has been hijacked this midround cycle - var/random_event_hijacked = HIJACKED_NOTHING - - /// The timer ID for the cancellable midround rule injection - var/midround_injection_timer_id - - /// The last drafted midround rulesets (without the current one included). - /// Used for choosing different midround injections. - var/list/current_midround_rulesets - - /// The amount of threat shown on the piece of paper. - /// Can differ from the actual threat amount. - var/shown_threat - - VAR_PRIVATE/next_midround_injection - -/datum/controller/subsystem/dynamic/proc/admin_panel() - var/list/dat = list() - dat += "Dynamic Mode VVRefresh
" - dat += "Threat Level: [threat_level]
" - dat += "Budgets (Roundstart/Midrounds): [initial_round_start_budget]/[threat_level - initial_round_start_budget]
" - - dat += "Midround budget to spend: [mid_round_budget] AdjustView Log
" - dat += "
" - dat += "Parameters: centre = [threat_curve_centre] ; width = [threat_curve_width].
" - dat += "Split parameters: centre = [roundstart_split_curve_centre] ; width = [roundstart_split_curve_width].
" - dat += "On average, [clamp(peaceful_percentage, 1, 99)]% of the rounds are more peaceful.
" - dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
" - dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"]
" - dat += "Stacking limit: [GLOB.dynamic_stacking_limit] Adjust" - dat += "
" - dat += "Force Next Latejoin Ruleset
" - if (forced_latejoin_rule) - dat += {"-> [forced_latejoin_rule.name] <-
"} - dat += "Execute Midround Ruleset
" - dat += "
" - dat += "Executed rulesets: " - if (executed_rules.len > 0) - dat += "
" - for (var/datum/dynamic_ruleset/DR in executed_rules) - dat += "[DR.ruletype] - [DR.name]
" - else - dat += "none.
" - dat += "
Injection Timers: ([get_heavy_midround_injection_chance(dry_run = TRUE)]% heavy midround chance)
" - dat += "Latejoin: [DisplayTimeText(latejoin_injection_cooldown-world.time)] Now!
" - - var/next_injection = next_midround_injection() - if (next_injection == INFINITY) - dat += "All midrounds have been exhausted." - else - dat += "Midround: [DisplayTimeText(next_injection - world.time)] Now!
" - - var/datum/browser/browser = new(usr, "gamemode_panel", "Game Mode Panel", 500, 500) - browser.set_content(dat.Join()) - browser.open() - -/datum/controller/subsystem/dynamic/Topic(href, href_list) - if (..()) // Sanity, maybe ? - return - if(!check_rights(R_ADMIN)) - message_admins("[usr.key] has attempted to override the game mode panel!") - log_admin("[key_name(usr)] tried to use the game mode panel without authorization.") + wait = 5 MINUTES + + // These vars just exist for admins interfacing with dynamic + /// Cooldown between "we're going to spawn a midround" and "we're actually spawning a midround", to give admins a chance to cancel + COOLDOWN_DECLARE(midround_admin_cancel_period) + /// Set to TRUE by hrefs if admins cancel a midround + var/tmp/midround_admin_cancel = FALSE + /// Set to TRUE by hrefs if admins reroll a midround + var/tmp/midround_admin_reroll = FALSE + /// Set to TRUE by admin panels if they want to max out the chance of a light ruleset spawning + var/tmp/admin_forcing_next_light = FALSE + /// Set to TRUE by admin panels if they want to max out the chance of a heavy ruleset spawning + var/tmp/admin_forcing_next_heavy = FALSE + /// Set to TRUE by admin panels if they want to max out the chance of a latejoin ruleset spawning + var/tmp/admin_forcing_next_latejoin = FALSE + /// List of ruleset typepaths that admins have explicitly disabled + var/tmp/list/admin_disabled_rulesets = list() + + // Dynamic vars + /// Reference to a dynamic tier datum, the tier picked for this round + var/datum/dynamic_tier/current_tier + /// The config for dynamic loaded from the toml file + var/list/dynamic_config = list() + /// Tracks how many of each ruleset category is yet to be spawned + var/list/rulesets_to_spawn = list( + ROUNDSTART = -1, + LIGHT_MIDROUND = -1, + HEAVY_MIDROUND = -1, + LATEJOIN = -1, + ) + /// Tracks the number of rulesets to spawn at game start (for admin reference) + var/list/base_rulesets_to_spawn = list( + ROUNDSTART = 0, + LIGHT_MIDROUND = 0, + HEAVY_MIDROUND = 0, + LATEJOIN = 0, + ) + /// Cooldown for when we are allowed to spawn light rulesets + COOLDOWN_DECLARE(light_ruleset_start) + /// Cooldown for when we are allowed to spawn heavy rulesets + COOLDOWN_DECLARE(heavy_ruleset_start) + /// Cooldown for when we are allowed to spawn latejoin rulesets + COOLDOWN_DECLARE(latejoin_ruleset_start) + /// Tracks how many time we fail to spawn a latejoin (to up the odds next time) + var/failed_latejoins = 0 + /// Cooldown between midround ruleset executions + COOLDOWN_DECLARE(midround_cooldown) + /// Cooldown between latejoin ruleset executions + COOLDOWN_DECLARE(latejoin_cooldown) + /// List of rulesets that have been executed this round + var/list/datum/dynamic_ruleset/executed_rulesets = list() + /// List of rulesets that have been set up to run, but not yet executed + var/list/datum/dynamic_ruleset/queued_rulesets = list() + /// Rulesets in this list will be excluded from the roundend report + var/list/datum/dynamic_ruleset/unreported_rulesets = list() + /// Whether random events that spawn antagonists or modify dynamic are enabled + var/antag_events_enabled = TRUE + +/datum/controller/subsystem/dynamic/fire(resumed) + if(!COOLDOWN_FINISHED(src, midround_cooldown) || EMERGENCY_PAST_POINT_OF_NO_RETURN) return - if (href_list["forced_extended"]) - GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended - else if (href_list["no_stacking"]) - GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking - else if (href_list["adjustthreat"]) - var/threatadd = input("Specify how much threat to add (negative to subtract). This can inflate the threat level.", "Adjust Threat", 0) as null|num - if(!threatadd) - return - if(threatadd > 0) - create_threat(threatadd, threat_log, "[worldtime2text()]: increased by [key_name(usr)]") - else - spend_midround_budget(-threatadd, threat_log, "[worldtime2text()]: decreased by [key_name(usr)]") - else if (href_list["injectlate"]) - latejoin_injection_cooldown = 0 - forced_injection = TRUE - message_admins("[key_name(usr)] forced a latejoin injection.") - else if (href_list["injectmid"]) - forced_injection = TRUE - message_admins("[key_name(usr)] forced a midround injection.") - try_midround_roll() - else if (href_list["threatlog"]) - show_threatlog(usr) - else if (href_list["stacking_limit"]) - GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num - else if(href_list["force_latejoin_rule"]) - var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in sort_names(init_rulesets(/datum/dynamic_ruleset/latejoin)) - if (!added_rule) + + if(COOLDOWN_FINISHED(src, light_ruleset_start)) + if(try_spawn_midround(LIGHT_MIDROUND)) return - forced_latejoin_rule = added_rule - log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") - message_admins("[key_name(usr)] set [added_rule] to proc on the next valid latejoin.") - else if(href_list["clear_forced_latejoin"]) - forced_latejoin_rule = null - log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") - message_admins("[key_name(usr)] cleared the forced latejoin ruleset.") - else if(href_list["force_midround_rule"]) - var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in sort_names(init_rulesets(/datum/dynamic_ruleset/midround)) - if (!added_rule) + + if(COOLDOWN_FINISHED(src, heavy_ruleset_start)) + if(try_spawn_midround(HEAVY_MIDROUND)) return - log_admin("[key_name(usr)] executed the [added_rule] ruleset.") - message_admins("[key_name(usr)] executed the [added_rule] ruleset.") - picking_specific_rule(added_rule, TRUE) - else if(href_list["cancelmidround"]) - admin_cancel_midround(usr, href_list["cancelmidround"]) - return - else if (href_list["differentmidround"]) - admin_different_midround(usr, href_list["differentmidround"]) - return - admin_panel() // Refreshes the window +/datum/controller/subsystem/dynamic/proc/get_config() + if(!length(dynamic_config)) + load_config() + return dynamic_config -// Set result and news report here -/datum/controller/subsystem/dynamic/proc/set_round_result() - // If it got to this part, just pick one high impact ruleset if it exists - for(var/datum/dynamic_ruleset/rule in executed_rules) - if(rule.flags & HIGH_IMPACT_RULESET) - rule.round_result() - // One was set, so we're done here - if(SSticker.news_report) - return +/** + * Selects which rulesets are to run at roundstart, and sets them up + * + * Note: This proc can sleep (due to lazyloading of templates)! + */ +/datum/controller/subsystem/dynamic/proc/select_roundstart_antagonists() + load_config() + SEND_SIGNAL(src, COMSIG_DYNAMIC_PRE_ROUNDSTART, dynamic_config) + // we start by doing a dry run of the job selection process to detect antag rollers + SSjob.divide_occupations(pure = TRUE, allow_all = TRUE) + + var/list/antag_candidates = list() + // anyone who was readied up and NOT unassigned is a potential candidate (even those who have no antag preferences) + for(var/mob/dead/new_player/player as anything in GLOB.new_player_list - SSjob.unassigned) + if(player.ready == PLAYER_READY_TO_PLAY && player.mind) + antag_candidates += player + + // anyone unassigned are potential antag rollers, so we need to warn them + for(var/mob/dead/new_player/player as anything in SSjob.unassigned) + var/list/job_data = list() + var/job_prefs = player.client?.prefs.job_preferences + for(var/job in job_prefs) + var/priority = job_prefs[job] + job_data += "[job]: [SSjob.job_priority_level_to_string(priority)]" + to_chat(player, span_danger("You were unable to qualify for any roundstart antagonist role this round \ + because your job preferences presented a high chance of all of your selected jobs being unavailable, \ + along with 'return to lobby if job is unavailable' enabled. \ + Increase the number of roles set to medium or low priority to reduce the chances of this happening.")) + log_admin("[player.ckey] failed to qualify for any roundstart antagonist role \ + because their job preferences presented a high chance of all of their selected jobs being unavailable, \ + along with 'return to lobby if job is unavailable' enabled and has [player.client.prefs.be_special.len] antag preferences enabled. \ + They will be unable to qualify for any roundstart antagonist role. These are their job preferences - [job_data.Join(" | ")]") + + var/num_real_players = length(antag_candidates) + // now select a tier (if admins didn't) + // this also calculates the number of rulesets to spawn + if(!current_tier) + pick_tier(num_real_players) + // put rulesets in the queue (if admins didn't) + // this will even handle the case in which the tier wants 0 roundstart rulesets + if(!length(queued_rulesets)) + queued_rulesets += pick_roundstart_rulesets(antag_candidates) + // we got what we needed, reset so we can do real job selection later + // reset only happens AFTER roundstart selection so we can verify stuff like "can we get 3 heads of staff for revs?" + SSjob.reset_occupations() + // finally, run through the queue and prepare rulesets for execution + // (actual execution, ie assigning antags, will happen after job assignment) + for(var/datum/dynamic_ruleset/roundstart/ruleset in queued_rulesets) + // NOTE: !! THIS CAN SLEEP !! + if(!ruleset.prepare_execution( num_real_players, antag_candidates )) + log_dynamic("Roundstart: Selected ruleset [ruleset.config_tag], but preparation failed!") + queued_rulesets -= ruleset + qdel(ruleset) + continue - SSticker.mode_result = "undefined" + // Just logs who was selected at roundstart + for(var/datum/mind/selected as anything in ruleset.selected_minds) + log_dynamic("Roundstart: [key_name(selected)] has been selected for [ruleset.config_tag].") - // Something nuked the station - it wasn't nuke ops (they set their own via their rulset) - if(GLOB.station_was_nuked) - SSticker.news_report = STATION_NUKED + rulesets_to_spawn[ROUNDSTART] -= 1 + // and start ticking + COOLDOWN_START(src, light_ruleset_start, current_tier.ruleset_type_settings[LIGHT_MIDROUND][TIME_THRESHOLD]) + COOLDOWN_START(src, heavy_ruleset_start, current_tier.ruleset_type_settings[HEAVY_MIDROUND][TIME_THRESHOLD]) + COOLDOWN_START(src, latejoin_ruleset_start, current_tier.ruleset_type_settings[LATEJOIN][TIME_THRESHOLD]) - if(SSsupermatter_cascade.cascade_initiated) - SSticker.news_report = SUPERMATTER_CASCADE + return TRUE - // Only show this one if we have nothing better to show - if(EMERGENCY_ESCAPED_OR_ENDGAMED && !SSticker.news_report) - SSticker.news_report = SSshuttle.emergency?.is_hijacked() ? SHUTTLE_HIJACK : STATION_EVACUATED +/datum/controller/subsystem/dynamic/proc/load_config() + PRIVATE_PROC(TRUE) -/datum/controller/subsystem/dynamic/proc/send_intercept() - if(SScommunications.block_command_report) //If we don't want the report to be printed just yet, we put it off until it's ready - addtimer(CALLBACK(src, PROC_REF(send_intercept)), 10 SECONDS) + if(!CONFIG_GET(flag/dynamic_config_enabled)) return - . = "Nanotrasen Department of Intelligence Threat Advisory, Spinward Sector, TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]:
" - . += generate_advisory_level() - - var/min_threat = 100 - for(var/datum/dynamic_ruleset/ruleset as anything in init_rulesets(/datum/dynamic_ruleset)) - if(ruleset.weight <= 0 || ruleset.cost <= 0) - continue - min_threat = min(ruleset.cost, min_threat) - - var/greenshift = GLOB.dynamic_forced_extended || (threat_level < min_threat && shown_threat < min_threat) //if both shown and real threat are below any ruleset, its extended time - SSstation.generate_station_goals(greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget)) - - var/list/datum/station_goal/goals = SSstation.get_station_goals() - if(length(goals)) - var/list/texts = list("
Special Orders for [station_name()]:
") - for(var/datum/station_goal/station_goal as anything in goals) - station_goal.on_report() - texts += station_goal.get_report() - . += texts.Join("
") - - var/list/trait_list_strings = list() - for(var/datum/station_trait/station_trait as anything in SSstation.station_traits) - if(!station_trait.show_in_report) - continue - trait_list_strings += "[station_trait.get_report()]
" - if(trait_list_strings.len > 0) - . += "
Identified shift divergencies:
" + trait_list_strings.Join() - - if(length(SScommunications.command_report_footnotes)) - var/footnote_pile = "" - - for(var/datum/command_footnote/footnote in SScommunications.command_report_footnotes) - footnote_pile += "[footnote.message]
" - footnote_pile += "[footnote.signature]
" - footnote_pile += "
" - - . += "
Additional Notes:

" + footnote_pile - -#ifndef MAP_TEST - print_command_report(., "[command_name()] Status Summary", announce=FALSE) - if(greenshift) - priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound(), color_override = "green") - else - if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE) - SSsecurity_level.set_level(SEC_LEVEL_BLUE, announce = FALSE) - priority_announce("[SSsecurity_level.current_security_level.elevating_to_announcement]\n\nA summary has been copied and printed to all communications consoles.", "Security level elevated.", ANNOUNCER_INTERCEPT, color_override = SSsecurity_level.current_security_level.announcement_color) -#endif - - return . - -/// Generate the advisory level depending on the shown threat level. -/datum/controller/subsystem/dynamic/proc/generate_advisory_level() - var/advisory_string = "" - if (prob(PULSAR_REPORT_CHANCE)) - if(HAS_TRAIT(SSstation, STATION_TRAIT_BANANIUM_SHIPMENTS)) - advisory_string += "Advisory Level: Clown Planet
" - advisory_string += "Your sector's advisory level is Clown Planet! Our bike horns have picked up on a large bananium stash. Clowns show a large influx of clowns on your station. We highly advice you to slip any threats to keep Honkotrasen assets within the Banana Sector. The Department advises defending chemistry from any clowns that are trying to make baldium or space lube." - return advisory_string - - advisory_string += "Advisory Level: Pulsar Star
" - advisory_string += "Your sector's advisory level is Pulsar Star. A large unknown electromagnetic field has stormed through nearby surveillance equipment. No surveillance data has been able to be obtained showing no credible threats to Nanotrasen assets within the Spinward Sector. The Department advises maintaining high alert against potential threats, regardless of a lack of information." - return advisory_string - - switch(round(shown_threat)) - if(0) - advisory_string += "Advisory Level: White Dwarf
" - advisory_string += "Your sector's advisory level is White Dwarf. Our surveillors have ruled out any and all potential risks known in our database, ruling out the loss of our assets in the Spinward Sector. We advise a lower level of security, alongside distributing ressources on potential profit." - if(1 to 19) - var/show_core_territory = (GLOB.current_living_antags.len > 0) - if (prob(FAKE_GREENSHIFT_FORM_CHANCE)) - show_core_territory = !show_core_territory - - if (show_core_territory) - advisory_string += "Advisory Level: Blue Star
" - advisory_string += "Your sector's advisory level is Blue Star. At this threat advisory, the risk of attacks on Nanotrasen assets within the sector is minor, but cannot be ruled out entirely. Remain vigilant." - else - advisory_string += "Advisory Level: Green Star
" - advisory_string += "Your sector's advisory level is Green Star. Surveillance information shows no credible threats to Nanotrasen assets within the Spinward Sector at this time. As always, the Department advises maintaining vigilance against potential threats, regardless of a lack of known threats." - if(20 to 39) - advisory_string += "Advisory Level: Yellow Star
" - advisory_string += "Your sector's advisory level is Yellow Star. Surveillance shows a credible risk of enemy attack against our assets in the Spinward Sector. We advise a heightened level of security, alongside maintaining vigilance against potential threats." - if(40 to 65) - advisory_string += "Advisory Level: Orange Star
" - advisory_string += "Your sector's advisory level is Orange Star. Upon reviewing your sector's intelligence, the Department has determined that the risk of enemy activity is moderate to severe. At this advisory, we recommend maintaining a higher degree of security and alertness, and vigilance against threats that may (or will) arise." - if(66 to 79) - advisory_string += "Advisory Level: Red Star
" - advisory_string += "Your sector's advisory level is Red Star. The Department of Intelligence has decrypted Cybersun communications suggesting a high likelihood of attacks on Nanotrasen assets within the Spinward Sector. Stations in the region are advised to remain highly vigilant for signs of enemy activity and to be on high alert." - if(80 to 99) - advisory_string += "Advisory Level: Black Orbit
" - advisory_string += "Your sector's advisory level is Black Orbit. Your sector's local comms network is currently undergoing a blackout, and we are therefore unable to accurately judge enemy movements within the region. However, information passed to us by GDI suggests a high amount of enemy activity in the sector, indicative of an impending attack. Remain on high alert, and as always, we advise remaining vigilant against any other potential threats." - if(100) - advisory_string += "Advisory Level: Midnight Sun
" - advisory_string += "Your sector's advisory level is Midnight Sun. Credible information passed to us by GDI suggests that the Syndicate is preparing to mount a major concerted offensive on Nanotrasen assets in the Spinward Sector to cripple our foothold there. All stations should remain on high alert and prepared to defend themselves." - - return advisory_string - -/datum/controller/subsystem/dynamic/proc/show_threatlog(mob/admin) - if(!SSticker.HasRoundStarted()) - tgui_alert(usr, "The round hasn't started yet!") + var/config_file = "[global.config.directory]/dynamic.toml" + var/list/result = rustg_raw_read_toml_file(config_file) + if(!result["success"]) + log_dynamic("Failed to load config file! ([config_file] - [result["content"]])") return - if(!check_rights(R_ADMIN)) - return + dynamic_config = json_decode(result["content"]) - var/list/out = list("Threat Log
Starting Threat: [threat_level]
") +/// Sets the tier to the typepath passed in +/datum/controller/subsystem/dynamic/proc/set_tier(picked_tier, population = length(GLOB.player_list)) + current_tier = new picked_tier(dynamic_config) - for(var/entry in threat_log) - if(istext(entry)) - out += "[entry]
" + for(var/category in current_tier.ruleset_type_settings) + var/list/range = current_tier.ruleset_type_settings[category] || list() + var/low_end = range[LOW_END] || 0 + var/high_end = range[HIGH_END] || 0 - out += "Remaining threat/threat_level: [mid_round_budget]/[threat_level]" + if(population <= (range[HALF_RANGE_POP_THRESHOLD] || 0)) + high_end = max(low_end, ceil(high_end * 0.25)) + else if(population <= (range[FULL_RANGE_POP_THRESHOLD] || 0)) + high_end = max(low_end, ceil(high_end * 0.5)) - usr << browse(HTML_SKELETON_TITLE("Threat Log", out.Join()), "window=threatlog;size=700x500") + rulesets_to_spawn[category] = rand(low_end, high_end) + base_rulesets_to_spawn[category] = rulesets_to_spawn[category] -/// Generates the threat level using lorentz distribution and assigns peaceful_percentage. -/datum/controller/subsystem/dynamic/proc/generate_threat() - // At lower pop levels we run a Liner Interpolation against the max threat based proportionally on the number - // of players ready. This creates a balanced lorentz curve within a smaller range than 0 to max_threat_level. - var/calculated_max_threat = (SSticker.totalPlayersReady < low_pop_player_threshold) ? LERP(low_pop_maximum_threat, max_threat_level, SSticker.totalPlayersReady / low_pop_player_threshold) : max_threat_level - log_dynamic("Calculated maximum threat level based on player count of [SSticker.totalPlayersReady]: [calculated_max_threat]") +/// Picks what tier we are going to use for this round and sets up all the corresponding variables and ranges +/datum/controller/subsystem/dynamic/proc/pick_tier(roundstart_population = 0) + PRIVATE_PROC(TRUE) + + var/list/tier_weighted = list() + for(var/datum/dynamic_tier/tier_datum as anything in subtypesof(/datum/dynamic_tier)) + var/min_players_config = dynamic_config[tier_datum::config_tag]?[NAMEOF(tier_datum, min_pop)] + var/min_players = isnull(min_players_config) ? tier_datum::min_pop : min_players_config + if(roundstart_population < min_players) + continue - threat_level = lorentz_to_amount(threat_curve_centre, threat_curve_width, calculated_max_threat) + var/tier_config_weight = dynamic_config[tier_datum::config_tag]?[NAMEOF(tier_datum, weight)] + var/tier_weight = isnull(tier_config_weight) ? tier_datum::weight : tier_config_weight + if(tier_weight <= 0) + continue - for(var/datum/station_trait/station_trait in GLOB.dynamic_station_traits) - threat_level = max(threat_level - GLOB.dynamic_station_traits[station_trait], 0) - log_dynamic("Threat reduced by [GLOB.dynamic_station_traits[station_trait]]. Source: [type].") + tier_weighted[tier_datum] = tier_weight - peaceful_percentage = (threat_level/max_threat_level)*100 + set_tier(pick_weight(tier_weighted), roundstart_population) -/// Generates the midround and roundstart budgets -/datum/controller/subsystem/dynamic/proc/generate_budgets() - round_start_budget = lorentz_to_amount(roundstart_split_curve_centre, roundstart_split_curve_width, threat_level, 0.1) - initial_round_start_budget = round_start_budget - mid_round_budget = threat_level - round_start_budget + var/roundstart_spawn = rulesets_to_spawn[ROUNDSTART] + var/light_midround_spawn = rulesets_to_spawn[LIGHT_MIDROUND] + var/heavy_midround_spawn = rulesets_to_spawn[HEAVY_MIDROUND] + var/latejoin_spawn = rulesets_to_spawn[LATEJOIN] -/datum/controller/subsystem/dynamic/proc/setup_parameters() - log_dynamic("Dynamic mode parameters for the round:") - log_dynamic("Centre is [threat_curve_centre], Width is [threat_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].") - log_dynamic("Stacking limit is [GLOB.dynamic_stacking_limit].") - if(GLOB.dynamic_forced_threat_level >= 0) - threat_level = round(GLOB.dynamic_forced_threat_level, 0.1) - else - generate_threat() - generate_budgets() - set_cooldowns() - log_dynamic("Dynamic Mode initialized with a Threat Level of... [threat_level]! ([round_start_budget] round start budget)") + log_dynamic("Selected tier: [current_tier.tier]") + log_dynamic("- Roundstart population: [roundstart_population]") + log_dynamic("- Roundstart ruleset count: [roundstart_spawn]") + log_dynamic("- Light midround ruleset count: [light_midround_spawn]") + log_dynamic("- Heavy midround ruleset count: [heavy_midround_spawn]") + log_dynamic("- Latejoin ruleset count: [latejoin_spawn]") SSblackbox.record_feedback( "associative", - "dynamic_threat", + "dynamic_tier", 1, list( "server_name" = CONFIG_GET(string/serversqlname), - "forced_threat_level" = GLOB.dynamic_forced_threat_level, - "threat_level" = threat_level, - "max_threat" = (SSticker.totalPlayersReady < low_pop_player_threshold) ? LERP(low_pop_maximum_threat, max_threat_level, SSticker.totalPlayersReady / low_pop_player_threshold) : max_threat_level, - "player_count" = SSticker.totalPlayersReady, - "round_start_budget" = round_start_budget, - "parameters" = list( - "threat_curve_centre" = threat_curve_centre, - "threat_curve_width" = threat_curve_width, - "forced_extended" = GLOB.dynamic_forced_extended, - "no_stacking" = GLOB.dynamic_no_stacking, - "stacking_limit" = GLOB.dynamic_stacking_limit, - ), + "tier" = current_tier.tier, + "player_count" = roundstart_population, + "roundstart_ruleset_count" = roundstart_spawn, + "light_midround_ruleset_count" = light_midround_spawn, + "heavy_midround_ruleset_count" = heavy_midround_spawn, + "latejoin_ruleset_count" = latejoin_spawn, ), ) - return TRUE - -/datum/controller/subsystem/dynamic/proc/setup_shown_threat() - if (prob(FAKE_REPORT_CHANCE)) - shown_threat = rand(1, 100) - else - shown_threat = clamp(threat_level + rand(REPORT_NEG_DIVERGENCE, REPORT_POS_DIVERGENCE), 0, 100) - -/datum/controller/subsystem/dynamic/proc/set_cooldowns() - var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min) - latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time - -// Called BEFORE everyone is equipped with their job -/datum/controller/subsystem/dynamic/proc/pre_setup() - if(CONFIG_GET(flag/dynamic_config_enabled)) - var/json_file = file("[global.config.directory]/dynamic.json") - if(fexists(json_file)) - configuration = json_decode(file2text(json_file)) - if(configuration["Dynamic"]) - for(var/variable in configuration["Dynamic"]) - if(!(variable in vars)) - stack_trace("Invalid dynamic configuration variable [variable] in game mode variable changes.") - continue - vars[variable] = configuration["Dynamic"][variable] - - configure_station_trait_costs() - setup_parameters() - setup_hijacking() - setup_shown_threat() - setup_rulesets() - - //We do this here instead of with the midround rulesets and such because these rules can hang refs - //To new_player and such, and we want the datums to just free when the roundstart work is done - var/list/roundstart_rules = init_rulesets(/datum/dynamic_ruleset/roundstart) - - SSjob.DivideOccupations(pure = TRUE, allow_all = TRUE) - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/player = i - if(player.ready == PLAYER_READY_TO_PLAY && player.mind && player.check_preferences()) - if(is_unassigned_job(player.mind.assigned_role)) - var/list/job_data = list() - var/job_prefs = player.client.prefs.job_preferences - for(var/job in job_prefs) - var/priority = job_prefs[job] - job_data += "[job]: [SSjob.job_priority_level_to_string(priority)]" - to_chat(player, span_danger("You were unable to qualify for any roundstart antagonist role this round because your job preferences presented a high chance of all of your selected jobs being unavailable, along with 'return to lobby if job is unavailable' enabled. Increase the number of roles set to medium or low priority to reduce the chances of this happening.")) - log_admin("[player.ckey] failed to qualify for any roundstart antagonist role because their job preferences presented a high chance of all of their selected jobs being unavailable, along with 'return to lobby if job is unavailable' enabled and has [player.client.prefs.be_special.len] antag preferences enabled. They will be unable to qualify for any roundstart antagonist role. These are their job preferences - [job_data.Join(" | ")]") - else - roundstart_pop_ready++ - candidates.Add(player) - SSjob.ResetOccupations() - log_dynamic("Listing [roundstart_rules.len] round start rulesets, and [candidates.len] players ready.") - if (candidates.len <= 0) - log_dynamic("[candidates.len] candidates.") - return TRUE - - if(GLOB.dynamic_forced_roundstart_ruleset.len > 0) - rigged_roundstart() - else - roundstart(roundstart_rules) - - log_dynamic("[round_start_budget] round start budget was left, donating it to midrounds.") - threat_log += "[worldtime2text()]: [round_start_budget] round start budget was left, donating it to midrounds." - mid_round_budget += round_start_budget - - var/starting_rulesets = "" - for (var/datum/dynamic_ruleset/roundstart/DR in executed_rules) - starting_rulesets += "[DR.name], " - log_dynamic("Picked the following roundstart rules: [starting_rulesets]") - candidates.Cut() - return TRUE -// Called AFTER everyone is equipped with their job -/datum/controller/subsystem/dynamic/proc/post_setup(report) - for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules) - rule.candidates.Cut() // The rule should not use candidates at this point as they all are null. - addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/dynamic/, execute_roundstart_rule), rule), rule.delay) +/// Gets a weighted list of roundstart rulesets +/datum/controller/subsystem/dynamic/proc/get_roundstart_rulesets(list/antag_candidates) + PRIVATE_PROC(TRUE) - if (!CONFIG_GET(flag/no_intercept_report)) - addtimer(CALLBACK(src, PROC_REF(send_intercept)), rand(waittime_l, waittime_h)) + var/list/datum/dynamic_ruleset/roundstart/rulesets = list() + for(var/ruleset_type in subtypesof(/datum/dynamic_ruleset/roundstart)) + var/datum/dynamic_ruleset/roundstart/ruleset = new ruleset_type(dynamic_config) + rulesets[ruleset] = ruleset.get_weight(length(antag_candidates), current_tier.tier) - addtimer(CALLBACK(src, PROC_REF(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 *= (1 SECONDS) - else - delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined. - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reopen_roundstart_suicide_roles)), delay) - - if(SSdbcore.Connect()) - var/list/to_set = list() - var/arguments = list() - 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) - return TRUE + return rulesets -/datum/controller/subsystem/dynamic/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_AFK_THRESHOLD) //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(HAS_TRAIT(L, TRAIT_SUICIDED)) //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(HAS_TRAIT(L, TRAIT_SUICIDED)) //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 - - var/concatenated_message = msg.Join() - log_admin(concatenated_message) - to_chat(GLOB.admins, concatenated_message) - - -/// Initializes the internal ruleset variables -/datum/controller/subsystem/dynamic/proc/setup_rulesets() - midround_rules = init_rulesets(/datum/dynamic_ruleset/midround) - latejoin_rules = init_rulesets(/datum/dynamic_ruleset/latejoin) - -/// Returns a list of the provided rulesets. -/// Configures their variables to match config. -/datum/controller/subsystem/dynamic/proc/init_rulesets(ruleset_subtype) - var/list/rulesets = list() - - for (var/datum/dynamic_ruleset/ruleset_type as anything in subtypesof(ruleset_subtype)) - if (initial(ruleset_type.name) == "") +/// Picks as many roundstart rulesets as we are allowed to spawn, returns them +/datum/controller/subsystem/dynamic/proc/pick_roundstart_rulesets(list/antag_candidates) + PRIVATE_PROC(TRUE) + + if(rulesets_to_spawn[ROUNDSTART] <= 0) + return list() + + var/list/rulesets_weighted = get_roundstart_rulesets(antag_candidates) + var/total_weight = 0 + for(var/ruleset in rulesets_weighted) + total_weight += rulesets_weighted[ruleset] + if(total_weight <= 0) + log_dynamic("Roundstart: No rulesets to pick from!") + return list() + + var/list/picked_rulesets = list() + while(rulesets_to_spawn[ROUNDSTART] > 0) + if(!length(rulesets_weighted) || total_weight <= 0) + log_dynamic("Roundstart: No more rulesets to pick from with [rulesets_to_spawn[ROUNDSTART]] left!") + break + rulesets_to_spawn[ROUNDSTART] -= 1 + var/datum/dynamic_ruleset/roundstart/picked_ruleset = pick_weight(rulesets_weighted) + log_dynamic("Roundstart: Ruleset [picked_ruleset.config_tag] (Chance: [round(rulesets_weighted[picked_ruleset] / total_weight * 100, 0.01)]%)") + if(picked_ruleset.solo) + log_dynamic("Roundstart: Ruleset is a solo ruleset. Cancelling other picks.") + QDEL_LIST(picked_rulesets) + rulesets_weighted -= picked_ruleset + picked_rulesets += picked_ruleset + break + if(!picked_ruleset.repeatable) + rulesets_weighted -= picked_ruleset + picked_rulesets += picked_ruleset continue - if (initial(ruleset_type.weight) == 0) - continue + rulesets_weighted[picked_ruleset] -= picked_ruleset.repeatable_weight_decrease + total_weight -= picked_ruleset.repeatable_weight_decrease + // Rulesets are not singletons. We need to to make a new one + picked_rulesets += new picked_ruleset.type(dynamic_config) - var/ruleset = new ruleset_type - configure_ruleset(ruleset) - rulesets += ruleset + // clean up unused rulesets + QDEL_LIST(rulesets_weighted) + return picked_rulesets - return rulesets +/datum/controller/subsystem/dynamic/proc/get_advisory_report() + var/shown_tier = current_tier.tier + if(prob(10)) + shown_tier = pick(list(DYNAMIC_TIER_LOW, DYNAMIC_TIER_LOWMEDIUM, DYNAMIC_TIER_MEDIUMHIGH, DYNAMIC_TIER_HIGH) - current_tier.tier) -/// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it. -/datum/controller/subsystem/dynamic/proc/rigged_roundstart() - message_admins("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.") - log_dynamic("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.") - for (var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) - configure_ruleset(rule) - message_admins("Drafting players for forced ruleset [rule.name].") - log_dynamic("Drafting players for forced ruleset [rule.name].") - rule.acceptable(roundstart_pop_ready, threat_level) // Assigns some vars in the modes, running it here for consistency - rule.candidates = candidates.Copy() - rule.trim_candidates() - rule.load_templates() - if (rule.ready(roundstart_pop_ready, TRUE)) - var/cost = rule.cost - var/scaled_times = 0 - if (rule.scaling_cost) - scaled_times = round(max(round_start_budget - cost, 0) / rule.scaling_cost) - cost += rule.scaling_cost * scaled_times - - spend_roundstart_budget(picking_roundstart_rule(rule, scaled_times, forced = TRUE)) - -/datum/controller/subsystem/dynamic/proc/roundstart(list/roundstart_rules) - if (GLOB.dynamic_forced_extended) - log_dynamic("Starting a round of forced extended.") - return TRUE - var/list/drafted_rules = list() - for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules) - if (!rule.weight) - continue - if (rule.acceptable(roundstart_pop_ready, threat_level) && round_start_budget >= rule.cost) // If we got the population and threat required - rule.candidates = candidates.Copy() - rule.trim_candidates() - rule.load_templates() - if (rule.ready(roundstart_pop_ready) && rule.candidates.len > 0) - drafted_rules[rule] = rule.weight - - var/list/rulesets_picked = list() - - // Kept in case a ruleset can't be initialized for whatever reason, we want to be able to only spend what we can use. - var/round_start_budget_left = round_start_budget - - while (round_start_budget_left > 0) - var/datum/dynamic_ruleset/roundstart/ruleset = pick_weight(drafted_rules) - if (isnull(ruleset)) - log_dynamic("No more rules can be applied, stopping with [round_start_budget] left.") - break + else if(prob(15)) + shown_tier = clamp(current_tier.tier + pick(-1, 1), DYNAMIC_TIER_LOW, DYNAMIC_TIER_HIGH) - var/cost = (ruleset in rulesets_picked) ? ruleset.scaling_cost : ruleset.cost - if (cost == 0) - stack_trace("[ruleset] cost 0, this is going to result in an infinite loop.") - drafted_rules[ruleset] = null - continue + for(var/datum/dynamic_tier/tier_datum as anything in subtypesof(/datum/dynamic_tier)) + if(tier_datum::tier == shown_tier) + return tier_datum::advisory_report - if (cost > round_start_budget_left) - drafted_rules[ruleset] = null - continue + return null - if (check_blocking(ruleset.blocking_rules, rulesets_picked)) - drafted_rules[ruleset] = null - continue - - round_start_budget_left -= cost - - rulesets_picked[ruleset] += 1 - - if (ruleset.flags & HIGH_IMPACT_RULESET) - for (var/_other_ruleset in drafted_rules) - var/datum/dynamic_ruleset/other_ruleset = _other_ruleset - if (other_ruleset.flags & HIGH_IMPACT_RULESET) - drafted_rules[other_ruleset] = null - - if (ruleset.flags & LONE_RULESET) - drafted_rules[ruleset] = null - - for (var/ruleset in rulesets_picked) - spend_roundstart_budget(picking_roundstart_rule(ruleset, rulesets_picked[ruleset] - 1)) - - update_log() - -/// Initializes the round start ruleset provided to it. Returns how much threat to spend. -/datum/controller/subsystem/dynamic/proc/picking_roundstart_rule(datum/dynamic_ruleset/roundstart/ruleset, scaled_times = 0, forced = FALSE) - log_dynamic("Picked a ruleset: [ruleset.name], scaled [scaled_times] times") - - ruleset.trim_candidates() - var/added_threat = ruleset.scale_up(roundstart_pop_ready, scaled_times) - - if(ruleset.pre_execute(roundstart_pop_ready)) - threat_log += "[worldtime2text()]: Roundstart [ruleset.name] spent [ruleset.cost + added_threat]. [ruleset.scaling_cost ? "Scaled up [ruleset.scaled_times]/[scaled_times] times." : ""]" - if(ruleset.flags & ONLY_RULESET) - only_ruleset_executed = TRUE - if(ruleset.flags & HIGH_IMPACT_RULESET) - high_impact_ruleset_executed = TRUE - executed_rules += ruleset - return ruleset.cost + added_threat - else - stack_trace("The starting rule \"[ruleset.name]\" failed to pre_execute.") - return 0 - -/// Mainly here to facilitate delayed rulesets. All roundstart rulesets are executed with a timered callback to this proc. -/datum/controller/subsystem/dynamic/proc/execute_roundstart_rule(sent_rule) - var/datum/dynamic_ruleset/rule = sent_rule - if(rule.execute()) - if(rule.persistent) - current_rules += rule - new_snapshot(rule) - rule.forget_startup() - return TRUE - rule.clean_up() // Refund threat, delete teams and so on. - rule.forget_startup() - executed_rules -= rule - stack_trace("The starting rule \"[rule.name]\" failed to execute.") - return FALSE - -/// An experimental proc to allow admins to call rules on the fly or have rules call other rules. -/datum/controller/subsystem/dynamic/proc/picking_specific_rule(ruletype, forced = FALSE, ignore_cost = FALSE) - var/datum/dynamic_ruleset/midround/new_rule - if(ispath(ruletype)) - new_rule = new ruletype() // You should only use it to call midround rules though. - configure_ruleset(new_rule) // This makes sure the rule is set up properly. - else if(istype(ruletype, /datum/dynamic_ruleset)) - new_rule = ruletype - else +/** + * Invoked by SSdynamic to try to spawn a random midround ruleset + * Respects ranges and thresholds + * + * Prioritizes light midround rulesets first, then heavy midround rulesets + * + * Returns TRUE if a ruleset was spawned, FALSE otherwise + */ +/datum/controller/subsystem/dynamic/proc/try_spawn_midround(range) + if(rulesets_to_spawn[range] <= 0) return FALSE + var/midround_chance = get_midround_chance(range) + if(!prob(midround_chance)) + log_dynamic("Midround ([range]): Ruleset chance failed ([midround_chance]% chance)") + return FALSE + + midround_admin_cancel = FALSE + midround_admin_reroll = FALSE + COOLDOWN_RESET(src, midround_admin_cancel_period) - if(!new_rule) + var/player_count = get_active_player_count(afk_check = TRUE) + var/list/rulesets_weighted = get_midround_rulesets(player_count, range) + var/datum/dynamic_ruleset/midround/picked_ruleset = pick_weight(rulesets_weighted) + if(isnull(picked_ruleset)) + log_dynamic("Midround ([range]): No rulesets to pick from!") return FALSE + message_admins("Midround ([range]): Executing [picked_ruleset.config_tag] \ + [MIDROUND_CANCEL_HREF()] [MIDROUND_REROLL_HREF(rulesets_weighted)]") + // if we have admins online, we have a waiting period before execution to allow them to cancel or reroll + if(length(GLOB.admins)) + COOLDOWN_START(src, midround_admin_cancel_period, 15 SECONDS) + while(!COOLDOWN_FINISHED(src, midround_admin_cancel_period)) + if(midround_admin_cancel) + QDEL_LIST(rulesets_weighted) + COOLDOWN_START(src, midround_cooldown, get_ruleset_cooldown(range)) + return FALSE + if(midround_admin_reroll && length(rulesets_weighted) >= 2) + midround_admin_reroll = FALSE + COOLDOWN_START(src, midround_admin_cancel_period, 15 SECONDS) + rulesets_weighted -= picked_ruleset + qdel(picked_ruleset) + picked_ruleset = pick_weight(rulesets_weighted) + if(isnull(picked_ruleset)) + log_dynamic("Midround ([range]): No rulesets to pick from!") + message_admins("Rerolling Midround ([range]): Failed to pick a new ruleset, cancelling instead!") + midround_admin_cancel = TRUE + continue + message_admins("Rerolling Midround ([range]): Executing [picked_ruleset.config_tag] - \ + [length(rulesets_weighted) - 1] remaining rulesets in pool. [MIDROUND_CANCEL_HREF()] [MIDROUND_REROLL_HREF(rulesets_weighted)]") + stoplag() + + // NOTE: !! THIS CAN SLEEP !! + if(!picked_ruleset.prepare_execution(player_count, picked_ruleset.collect_candidates())) + log_dynamic("Midround ([range]): Selected ruleset [picked_ruleset.config_tag], but preparation failed!") + QDEL_LIST(rulesets_weighted) + return FALSE + // Run the thing + executed_rulesets += picked_ruleset + rulesets_weighted -= picked_ruleset + picked_ruleset.execute() + // Post execute logging + for(var/datum/mind/selected as anything in picked_ruleset.selected_minds) + message_admins("Midround ([range]): [ADMIN_LOOKUPFLW(selected.current)] has been selected for [picked_ruleset.config_tag].") + log_dynamic("Midround ([range]): [key_name(selected.current)] has been selected for [picked_ruleset.config_tag].") + notify_ghosts("[selected.name] has been picked for [picked_ruleset.config_tag]!", source = selected.current) + // Clean up unused rulesets + QDEL_LIST(rulesets_weighted) + rulesets_to_spawn[range] -= 1 + if(range == LIGHT_MIDROUND) + admin_forcing_next_light = FALSE + if(range == HEAVY_MIDROUND) + admin_forcing_next_heavy = FALSE + COOLDOWN_START(src, midround_cooldown, get_ruleset_cooldown(range)) + return TRUE - if(!forced) - if(only_ruleset_executed) - return FALSE - // Check if a blocking ruleset has been executed. - else if(check_blocking(new_rule.blocking_rules, executed_rules)) +/// Gets a weighted list of midround rulesets +/datum/controller/subsystem/dynamic/proc/get_midround_rulesets(player_count, midround_type) + PRIVATE_PROC(TRUE) + + var/list/datum/dynamic_ruleset/midround/rulesets = list() + for(var/datum/dynamic_ruleset/midround/ruleset_type as anything in subtypesof(/datum/dynamic_ruleset/midround)) + if(initial(ruleset_type.midround_type) != midround_type) + continue + var/datum/dynamic_ruleset/midround/ruleset = new ruleset_type(dynamic_config) + rulesets[ruleset] = ruleset.get_weight(player_count, current_tier.tier) + + return rulesets + +/** + * Attempt to run a midround ruleset of the given type + * + * * midround_type - The type of midround ruleset to force + * * forced_max_cap - Rather than using the ruleset's max antag cap, use this value + * As an example, this allows you to only spawn 1 traitor rather than the ruleset's default of 3 + * Can't be set to 0 (why are you forcing a ruleset that spawns 0 antags?) + * * alert_admins_on_fail - If TRUE, alert admins if the ruleset fails to prepare/execute + * * mob/admin - The admin who is forcing the ruleset, used for configuring the ruleset if possible + */ +/datum/controller/subsystem/dynamic/proc/force_run_midround(midround_typepath, forced_max_cap, alert_admins_on_fail = FALSE, mob/admin) + if(!ispath(midround_typepath, /datum/dynamic_ruleset/midround)) + CRASH("force_run_midround() was called with an invalid midround type: [midround_typepath]") + + var/datum/dynamic_ruleset/midround/running = new midround_typepath(dynamic_config) + if(isnum(forced_max_cap) && forced_max_cap > 0) + running.min_antag_cap = min(forced_max_cap, running.min_antag_cap) + running.max_antag_cap = forced_max_cap + + if(admin && (running.ruleset_flags & RULESET_ADMIN_CONFIGURABLE)) + if(running.configure_ruleset(admin) == RULESET_CONFIG_CANCEL) + qdel(running) return FALSE - // Check if the ruleset is high impact and if a high impact ruleset has been executed - else if(new_rule.flags & HIGH_IMPACT_RULESET) - if(threat_level < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) - if(high_impact_ruleset_executed) - return FALSE - - var/population = GLOB.alive_player_list.len - if((new_rule.acceptable(population, threat_level) && (ignore_cost || new_rule.cost <= mid_round_budget)) || forced) - new_rule.trim_candidates() - new_rule.load_templates() - if (new_rule.ready(forced)) - if (!ignore_cost) - spend_midround_budget(new_rule.cost, threat_log, "[worldtime2text()]: Forced rule [new_rule.name]") - new_rule.pre_execute(population) - if (new_rule.execute()) // This should never fail since ready() returned 1 - if(new_rule.flags & HIGH_IMPACT_RULESET) - high_impact_ruleset_executed = TRUE - else if(new_rule.flags & ONLY_RULESET) - only_ruleset_executed = TRUE - log_dynamic("Making a call to a specific ruleset...[new_rule.name]!") - executed_rules += new_rule - if (new_rule.persistent) - current_rules += new_rule - new_rule.forget_startup() - return TRUE - else if (forced) - log_dynamic("The ruleset [new_rule.name] couldn't be executed due to lack of elligible players.") - new_rule.forget_startup() - return FALSE - -/datum/controller/subsystem/dynamic/fire() - for (var/datum/dynamic_ruleset/rule in current_rules) - if(rule.rule_process() == RULESET_STOP_PROCESSING) // If rule_process() returns 1 (RULESET_STOP_PROCESSING), stop processing. - current_rules -= rule - - try_midround_roll() - -/// Removes type from the list -/datum/controller/subsystem/dynamic/proc/remove_from_list(list/type_list, type) - for(var/I in type_list) - if(istype(I, type)) - type_list -= I - return type_list - -/// Checks if a type in blocking_list is in rule_list. -/datum/controller/subsystem/dynamic/proc/check_blocking(list/blocking_list, list/rule_list) - if(blocking_list.len > 0) - for(var/blocking in blocking_list) - for(var/_executed in rule_list) - var/datum/executed = _executed - if(blocking == executed.type) - log_dynamic("FAIL: check_blocking - [blocking] conflicts with [executed.type]") - return TRUE - return FALSE - -/// Handles late-join antag assignments -/datum/controller/subsystem/dynamic/proc/make_antag_chance(mob/living/carbon/human/newPlayer) - if (GLOB.dynamic_forced_extended) - return - if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left - return - if (forced_latejoin_rule) - log_dynamic("Forcing specific [forced_latejoin_rule.ruletype] ruleset [forced_latejoin_rule].") - if(!handle_executing_latejoin(forced_latejoin_rule, newPlayer, forced = TRUE)) - message_admins("The forced latejoin ruleset [forced_latejoin_rule.name] couldn't be executed \ - as the most recent latejoin did not fulfill the ruleset's requirements.") - forced_latejoin_rule = null - return + // NOTE: !! THIS CAN SLEEP !! + if(!running.prepare_execution(get_active_player_count(afk_check = TRUE), running.collect_candidates())) + if(alert_admins_on_fail) + message_admins("Midround (forced): Forced ruleset [running.config_tag], but preparation failed!") + log_dynamic("Midround (forced): Forced ruleset [running.config_tag], but preparation failed!") + qdel(running) + return FALSE - if(!forced_injection) - if(latejoin_injection_cooldown >= world.time) - return - if(!prob(latejoin_roll_chance)) - return + executed_rulesets += running + running.execute() + // Post execute logging + for(var/datum/mind/selected as anything in running.selected_minds) + message_admins("Midround (forced): [ADMIN_LOOKUPFLW(selected.current)] has been selected for [running.config_tag].") + log_dynamic("Midround (forced): [key_name(selected.current)] has been selected for [running.config_tag].") + notify_ghosts("[selected.name] has been picked for [running.config_tag]!", source = selected.current) + return TRUE - var/was_forced = forced_injection - forced_injection = FALSE - var/list/possible_latejoin_rules = list() - for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules) - if(!rule.weight) - continue - if(mid_round_budget < rule.cost) - continue - if(!rule.acceptable(GLOB.alive_player_list.len, threat_level)) +/** + * Called when someone latejoins + * (This could be a signal in the future) + */ +/datum/controller/subsystem/dynamic/proc/on_latejoin(mob/living/carbon/human/latejoiner) + // First check queued rulesets - queued rulesets by pass cooldowns and probability checks, + // because they're generally forced by events or admins (and thus have higher priority) + for(var/datum/dynamic_ruleset/latejoin/queued in queued_rulesets) + // NOTE: !! THIS CAN SLEEP !! + if(!queued.prepare_execution(get_active_player_count(afk_check = TRUE), list(latejoiner))) + message_admins("Latejoin (forced): Queued ruleset [queued.config_tag] failed to prepare! It remains queued for next latejoin. (REMOVE FROM QUEUE)") + log_dynamic("Latejoin (forced): Queued ruleset [queued.config_tag] failed to prepare! It remains queued for next latejoin.") continue - possible_latejoin_rules[rule] = rule.get_weight() - - if(!length(possible_latejoin_rules)) - log_dynamic("FAIL: [newPlayer] was selected to roll for a latejoin ruleset, but there were no valid rulesets.") + message_admins("Latejoin (forced): [ADMIN_LOOKUPFLW(latejoiner)] has been selected for [queued.config_tag].") + log_dynamic("Latejoin (forced): [key_name(latejoiner)] has been selected for [queued.config_tag].") + queued_rulesets -= queued + executed_rulesets += queued + queued.execute() return - log_dynamic("[newPlayer] was selected to roll for a latejoin ruleset from the following list: [english_list(possible_latejoin_rules)].") - // You get one shot at becoming a latejoin antag, if it fails the next guy will try. - var/datum/dynamic_ruleset/latejoin/picked_rule = pick_ruleset(possible_latejoin_rules, max_allowed_attempts = 1) - if(isnull(picked_rule)) - log_dynamic("FAIL: No valid rulset was selected for [newPlayer]'s latejoin[was_forced ? "" : ", the next player will be checked instead"].") - return - if(was_forced) - log_dynamic("Forcing random [picked_rule.ruletype] ruleset [picked_rule].") - handle_executing_latejoin(picked_rule, newPlayer, forced = was_forced) + if(COOLDOWN_FINISHED(src, latejoin_ruleset_start) && COOLDOWN_FINISHED(src, latejoin_cooldown)) + if(try_spawn_latejoin(latejoiner)) + return /** - * This proc handles the execution of a latejoin ruleset, including removing it from latejoin rulesets if not repeatable, - * upping the injection cooldown, and starting a timer to execute the ruleset on delay. + * Invoked by SSdynamic to try to spawn a latejoin ruleset + * Respects ranges and thresholds + * + * Returns TRUE if a ruleset was spawned, FALSE otherwise */ -/datum/controller/subsystem/dynamic/proc/handle_executing_latejoin(datum/dynamic_ruleset/ruleset, mob/living/carbon/human/only_candidate, forced = FALSE) - ruleset.candidates = list(only_candidate) - ruleset.trim_candidates() - ruleset.load_templates() - if (!ruleset.ready(forced)) - log_dynamic("FAIL: [only_candidate] was selected to latejoin with the [ruleset] ruleset, \ - but the ruleset failed to execute[length(ruleset.candidates) ? "":" as they were not a valid candiate"].") - return FALSE - if (!ruleset.repeatable) - latejoin_rules = remove_from_list(latejoin_rules, ruleset.type) - addtimer(CALLBACK(src, PROC_REF(execute_midround_latejoin_rule), ruleset), ruleset.delay) +/datum/controller/subsystem/dynamic/proc/try_spawn_latejoin(mob/living/carbon/human/latejoiner) - if(!forced) - var/latejoin_injection_cooldown_middle = 0.5 * (latejoin_delay_max + latejoin_delay_min) - latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time - log_dynamic("A latejoin rulset triggered successfully, the next latejoin injection will happen at [latejoin_injection_cooldown] round time.") + if(rulesets_to_spawn[LATEJOIN] <= 0) + return FALSE + var/latejoin_chance = get_latejoin_chance() + if(!prob(latejoin_chance)) + log_dynamic("Latejoin: Ruleset chance failed ([latejoin_chance]% chance)") + return FALSE + var/player_count = get_active_player_count(afk_check = TRUE) + var/list/rulesets_weighted = get_latejoin_rulesets(player_count) + // Note, we make no effort to actually pick a valid ruleset here + // We pick a ruleset, and they player might not even have that antag selected. And that's fine + var/datum/dynamic_ruleset/latejoin/picked_ruleset = pick_weight(rulesets_weighted) + if(isnull(picked_ruleset)) + log_dynamic("Latejoin: No rulesets to pick from!") + return FALSE + // NOTE: !! THIS CAN SLEEP !! + if(!picked_ruleset.prepare_execution(player_count, list(latejoiner))) + log_dynamic("Latejoin: Selected ruleset [picked_ruleset.name] for [key_name(latejoiner)], but preparation failed! Latejoin chance has increased.") + QDEL_LIST(rulesets_weighted) + failed_latejoins++ + return FALSE + // Run the thing + executed_rulesets += picked_ruleset + rulesets_weighted -= picked_ruleset + picked_ruleset.execute() + // Post execute logging + if(!(latejoiner.mind in picked_ruleset.selected_minds)) + stack_trace("Dynamic: Latejoin [picked_ruleset.type] executed, but the latejoiner was not in its selected minds list!") + message_admins("Latejoin: [ADMIN_LOOKUPFLW(latejoiner)] has been selected for [picked_ruleset.config_tag].") + log_dynamic("Latejoin: [key_name(latejoiner)] has been selected for [picked_ruleset.config_tag].") + // Clean up unused rulesets + QDEL_LIST(rulesets_weighted) + rulesets_to_spawn[LATEJOIN] -= 1 + failed_latejoins = 0 + admin_forcing_next_latejoin = FALSE + COOLDOWN_START(src, latejoin_cooldown, get_ruleset_cooldown(LATEJOIN)) return TRUE -/// Apply configurations to rule. -/datum/controller/subsystem/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset) - var/rule_conf = LAZYACCESSASSOC(configuration, ruleset.ruletype, ruleset.name) - for(var/variable in rule_conf) - if(!(variable in ruleset.vars)) - stack_trace("Invalid dynamic configuration variable [variable] in [ruleset.ruletype] [ruleset.name].") - continue - ruleset.vars[variable] = rule_conf[variable] - ruleset.restricted_roles |= SSstation.antag_restricted_roles - if(length(ruleset.protected_roles)) //if we care to protect any role, we should protect station trait roles too - ruleset.protected_roles |= SSstation.antag_protected_roles - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - ruleset.restricted_roles |= ruleset.protected_roles - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - ruleset.restricted_roles |= JOB_ASSISTANT - -/// Get station traits and call for their config -/datum/controller/subsystem/dynamic/proc/configure_station_trait_costs() - if(!CONFIG_GET(flag/dynamic_config_enabled)) - return - for(var/datum/station_trait/station_trait as anything in GLOB.dynamic_station_traits) - configure_station_trait(station_trait) +/// Gets a weighted list of latejoin rulesets +/datum/controller/subsystem/dynamic/proc/get_latejoin_rulesets(player_count) + PRIVATE_PROC(TRUE) -/// Apply configuration for station trait costs -/datum/controller/subsystem/dynamic/proc/configure_station_trait(datum/station_trait/station_trait) - var/list/station_trait_config = LAZYACCESSASSOC(configuration, "Station", station_trait.dynamic_threat_id) - var/cost = station_trait_config["cost"] + var/list/datum/dynamic_ruleset/latejoin/rulesets = list() + for(var/ruleset_type in subtypesof(/datum/dynamic_ruleset/latejoin)) + var/datum/dynamic_ruleset/latejoin/ruleset = new ruleset_type(dynamic_config) + rulesets[ruleset] = ruleset.get_weight(player_count, current_tier.tier) - if(isnull(cost)) //0 is valid so check for null specifically - return + return rulesets - if(cost != GLOB.dynamic_station_traits[station_trait]) - log_dynamic("Config set [station_trait.dynamic_threat_id] cost from [station_trait.threat_reduction] to [cost]") +/** + * Queues a ruleset to run on roundstart or next latejoin which fulfills all requirements + * + * For example, if you queue a latejoin revolutionary, it'll only run when population gets large enough and there are enough heads of staff + * For all latejoins until then, it will simply do nothing + * + * * latejoin_type - The type of latejoin ruleset to force + */ +/datum/controller/subsystem/dynamic/proc/queue_ruleset(ruleset_typepath) + if(!ispath(ruleset_typepath, /datum/dynamic_ruleset/latejoin) && !ispath(ruleset_typepath, /datum/dynamic_ruleset/roundstart)) + CRASH("queue_ruleset() was called with an invalid type: [ruleset_typepath]") - GLOB.dynamic_station_traits[station_trait] = cost + queued_rulesets += new ruleset_typepath(dynamic_config) -/// Refund threat, but no more than threat_level. -/datum/controller/subsystem/dynamic/proc/refund_threat(regain) - mid_round_budget = min(threat_level, mid_round_budget + regain) +/** + * Get the cooldown between attempts to spawn a ruleset of the given type + */ +/datum/controller/subsystem/dynamic/proc/get_ruleset_cooldown(range) + if(range == ROUNDSTART) + stack_trace("Attempting to get cooldown for roundstart rulesets - this is redundant and is likely an error") + return 0 -/// Generate threat and increase the threat_level if it goes beyond, capped at 100 -/datum/controller/subsystem/dynamic/proc/create_threat(gain, list/threat_log, reason) - mid_round_budget = min(100, mid_round_budget + gain) - if(mid_round_budget > threat_level) - threat_level = mid_round_budget - for(var/list/logs in threat_log) - log_threat(gain, logs, reason) + var/low = current_tier.ruleset_type_settings[range][EXECUTION_COOLDOWN_LOW] || 0 + var/high = current_tier.ruleset_type_settings[range][EXECUTION_COOLDOWN_HIGH] || 0 + return rand(low, high) -/datum/controller/subsystem/dynamic/proc/log_threat(threat_change, list/threat_log, reason) - var/gain_or_loss = "+" - if(threat_change < 0) - gain_or_loss = "-" - threat_log += "Threat [gain_or_loss][abs(threat_change)] - [reason]." +/** + * Gets the chance of a midround ruleset being selected + */ +/datum/controller/subsystem/dynamic/proc/get_midround_chance(range) + if(admin_forcing_next_light && range == LIGHT_MIDROUND) + return 100 + if(admin_forcing_next_heavy && range == HEAVY_MIDROUND) + return 100 -/// Expend round start threat, can't fall under 0. -/datum/controller/subsystem/dynamic/proc/spend_roundstart_budget(cost, list/threat_log, reason) - round_start_budget = max(round_start_budget - cost,0) - if (!isnull(threat_log)) - log_threat(-cost, threat_log, reason) + var/chance = 0 + var/num_antags = length(GLOB.current_living_antags) + var/num_dead = length(GLOB.dead_player_list) + var/num_alive = get_active_player_count(afk_check = TRUE) + if(num_dead + num_alive <= 0) + return 0 -/// Expend midround threat, can't fall under 0. -/datum/controller/subsystem/dynamic/proc/spend_midround_budget(cost, list/threat_log, reason) - mid_round_budget = max(mid_round_budget - cost,0) - if (!isnull(threat_log)) - log_threat(-cost, threat_log, reason) + chance += 100 - (200 * (num_dead / (num_alive + num_dead))) + if(num_antags < 0) + chance += 50 -#define MAXIMUM_DYN_DISTANCE 5 + return chance /** - * Returns the comulative distribution of threat centre and width, and a random location of -0.5 to 0.5 - * plus or minus the otherwise unattainable lower and upper percentiles. All multiplied by the maximum - * threat and then rounded to the nearest interval. - * rand() calls without arguments returns a value between 0 and 1, allowing for smaller intervals. + * Gets the chance of a latejoin ruleset being selected */ -/datum/controller/subsystem/dynamic/proc/lorentz_to_amount(centre = 0, scale = 1.8, max_threat = 100, interval = 1) - var/location = RANDOM_DECIMAL(-MAXIMUM_DYN_DISTANCE, MAXIMUM_DYN_DISTANCE) * rand() - var/lorentz_result = LORENTZ_CUMULATIVE_DISTRIBUTION(centre, location, scale) - var/std_threat = lorentz_result * max_threat - ///Without these, the amount won't come close to hitting 0% or 100% of the max threat. - var/lower_deviation = max(std_threat * (location-centre)/MAXIMUM_DYN_DISTANCE, 0) - var/upper_deviation = max((max_threat - std_threat) * (centre-location)/MAXIMUM_DYN_DISTANCE, 0) - return clamp(round(std_threat + upper_deviation - lower_deviation, interval), 0, 100) - -/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)) +/datum/controller/subsystem/dynamic/proc/get_latejoin_chance() + if(admin_forcing_next_latejoin) + return 100 + + var/chance = 0 + var/num_antags = length(GLOB.current_living_antags) + var/num_dead = length(GLOB.dead_player_list) + var/num_alive = get_active_player_count(afk_check = TRUE) + if(num_dead + num_alive <= 0) + return 0 + + chance += 100 - (200 * (num_dead / (num_alive + num_dead))) + if(num_antags < 0) + chance += 50 + chance += (failed_latejoins * 15) + // Reduced chance before lights start + if(!COOLDOWN_FINISHED(src, light_ruleset_start)) + chance *= 0.2 + + return chance + +/datum/controller/subsystem/dynamic/proc/set_round_result() + // If it got to this part, just pick one high impact ruleset if it exists + for(var/datum/dynamic_ruleset/rule as anything in executed_rulesets) + if(rule.round_result()) + return + + SSticker.mode_result = "undefined" + + switch(GLOB.revolution_handler?.result) + if(STATION_VICTORY) + SSticker.mode_result = "loss - rev heads killed" + SSticker.news_report = REVS_LOSE + if(REVOLUTION_VICTORY) + SSticker.mode_result = "win - heads killed" + SSticker.news_report = REVS_WIN + + // Something nuked the station - it wasn't nuke ops (they set their own via their rulset) + if(GLOB.station_was_nuked) + SSticker.news_report = STATION_NUKED + + if(SSsupermatter_cascade.cascade_initiated) + SSticker.news_report = SUPERMATTER_CASCADE + + // Only show this one if we have nothing better to show + if(EMERGENCY_ESCAPED_OR_ENDGAMED && !SSticker.news_report) + SSticker.news_report = SSshuttle.emergency?.is_hijacked() ? SHUTTLE_HIJACK : STATION_EVACUATED + +/// Helper to clear all queued rulesets and stop any other rulesets from naturally spawning +/datum/controller/subsystem/dynamic/proc/force_extended() + for(var/category in rulesets_to_spawn) + rulesets_to_spawn[category] = 0 + QDEL_LIST(queued_rulesets) + +/datum/controller/subsystem/dynamic/Topic(href, list/href_list) + . = ..() + if(href_list["admin_dequeue"]) + if(!check_rights(R_ADMIN)) + return + var/datum/dynamic_ruleset/to_remove = locate(href_list["admin_dequeue"]) in queued_rulesets + if(!istype(to_remove)) + return + queued_rulesets -= to_remove + qdel(to_remove) + message_admins(span_adminnotice("[key_name_admin(usr)] [to_remove.config_tag] from the latejoin queue.")) + log_admin("[key_name(usr)] removed [to_remove.config_tag] from the latejoin queue.") + return + + if(href_list["admin_reroll"]) + if(!check_rights(R_ADMIN) || midround_admin_reroll) + return + if(COOLDOWN_FINISHED(src, midround_admin_cancel_period)) + to_chat(usr, span_alert("Too late!")) + return + midround_admin_reroll = TRUE + message_admins(span_adminnotice("[key_name_admin(usr)] rerolled the queued midround ruleset.")) + log_admin("[key_name(usr)] rerolled the queued midround ruleset.") + return + + if(href_list["admin_cancel_midround"]) + if(!check_rights(R_ADMIN) || midround_admin_cancel) + return + if(COOLDOWN_FINISHED(src, midround_admin_cancel_period)) + to_chat(usr, span_alert("Too late!")) + return + midround_admin_cancel = TRUE + message_admins(span_adminnotice("[key_name_admin(usr)] cancelled the queued midround ruleset.")) + log_admin("[key_name(usr)] cancelled the queued midround ruleset.") + return + +#ifdef TESTING +/// Puts all repo defaults into a dynamic.toml file +/datum/controller/subsystem/dynamic/proc/build_dynamic_toml() + var/data = "" + for(var/tier_type in subtypesof(/datum/dynamic_tier)) + var/datum/dynamic_tier/tier = new tier_type() + if(!tier.config_tag) + qdel(tier) continue - if(!include_command && job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) + + data += "\[\"[tier.config_tag]\"\]\n" + data += "name = \"[tier.name]\"\n" + data += "min_pop = [tier.min_pop]\n" + data += "weight = [tier.weight]\n" + data += "advisory_report = \"[tier.advisory_report]\"\n" + for(var/range in tier.ruleset_type_settings) + data += "ruleset_type_settings.[range].[LOW_END] = [tier.ruleset_type_settings[range]?[LOW_END] || 0]\n" + data += "ruleset_type_settings.[range].[HIGH_END] = [tier.ruleset_type_settings[range]?[HIGH_END] || 0]\n" + data += "ruleset_type_settings.[range].[HALF_RANGE_POP_THRESHOLD] = [tier.ruleset_type_settings[range]?[HALF_RANGE_POP_THRESHOLD] || 0]\n" + data += "ruleset_type_settings.[range].[FULL_RANGE_POP_THRESHOLD] = [tier.ruleset_type_settings[range]?[FULL_RANGE_POP_THRESHOLD] || 0]\n" + if(range != ROUNDSTART) + data += "ruleset_type_settings.[range].[TIME_THRESHOLD] = [(tier.ruleset_type_settings[range]?[TIME_THRESHOLD] || 0) / 60 / 10]\n" + data += "ruleset_type_settings.[range].[EXECUTION_COOLDOWN_LOW] = [(tier.ruleset_type_settings[range]?[EXECUTION_COOLDOWN_LOW] || 0) / 60 / 10]\n" + data += "ruleset_type_settings.[range].[EXECUTION_COOLDOWN_HIGH] = [(tier.ruleset_type_settings[range]?[EXECUTION_COOLDOWN_HIGH] || 0) / 60 / 10]\n" + + data += "\n" + qdel(tier) + + for(var/ruleset_type in subtypesof(/datum/dynamic_ruleset)) + var/datum/dynamic_ruleset/ruleset = new ruleset_type() + if(!ruleset.config_tag) + qdel(ruleset) 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 = {" - [command_name()] 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") - - -#undef MAXIMUM_DYN_DISTANCE - -#undef FAKE_REPORT_CHANCE -#undef FAKE_GREENSHIFT_FORM_CHANCE -#undef PULSAR_REPORT_CHANCE -#undef REPORT_NEG_DIVERGENCE -#undef REPORT_POS_DIVERGENCE + + data += "\[\"[ruleset.config_tag]\"\]\n" + if(islist(ruleset.weight)) + for(var/i in 1 to length(ruleset.weight)) + data += "weight.[i] = [ruleset.weight[i]]\n" + else + data += "weight = [ruleset.weight || 0]\n" + if(islist(ruleset.min_pop)) + for(var/i in 1 to length(ruleset.min_pop)) + data += "min_pop.[i] = [ruleset.min_pop[i]]\n" + else + data += "min_pop = [ruleset.min_pop || 0]\n" + if(length(ruleset.blacklisted_roles)) + data += "blacklisted_roles = \[\n" + for(var/i in ruleset.blacklisted_roles) + data += "\t\"[i]\",\n" + data += "\]\n" + else + data += "blacklisted_roles = \[\]\n" + if(!istype(ruleset, /datum/dynamic_ruleset/latejoin) && !istype(ruleset, /datum/dynamic_ruleset/midround/from_living)) + if(islist(ruleset.min_antag_cap)) + for(var/ruleset_min_antag_cap in ruleset.min_antag_cap) + data += "min_antag_cap.[ruleset_min_antag_cap] = [ruleset.min_antag_cap[ruleset_min_antag_cap]]\n" + else + data += "min_antag_cap = [ruleset.min_antag_cap || 0]\n" + if(islist(ruleset.max_antag_cap)) + for(var/ruleset_max_antag_cap in ruleset.max_antag_cap) + data += "max_antag_cap.[ruleset_max_antag_cap] = [ruleset.max_antag_cap[ruleset_max_antag_cap]]\n" + else if(!isnull(ruleset.max_antag_cap)) + data += "max_antag_cap = [ruleset.max_antag_cap]\n" + else + data += "# max_antag_cap = min_antag_cap\n" + data += "repeatable_weight_decrease = [ruleset.repeatable_weight_decrease]\n" + data += "repeatable = [ruleset.repeatable]\n" + data += "minimum_required_age = [ruleset.minimum_required_age]\n" + data += "\n" + qdel(ruleset) + + var/filepath = "[global.config.directory]/dynamic.toml" + fdel(file(filepath)) + text2file(data, filepath) + return TRUE +#endif diff --git a/code/controllers/subsystem/dynamic/dynamic_admin.dm b/code/controllers/subsystem/dynamic/dynamic_admin.dm new file mode 100644 index 000000000000..7c8cb3ecabd5 --- /dev/null +++ b/code/controllers/subsystem/dynamic/dynamic_admin.dm @@ -0,0 +1,254 @@ +ADMIN_VERB(dynamic_panel, R_ADMIN, "Dynamic Panel", "Mess with dynamic.", ADMIN_CATEGORY_GAME) + dynamic_panel(user.mob) + +/proc/dynamic_panel(mob/user) + if(!check_rights(R_ADMIN)) + return + var/datum/dynamic_panel/tgui = new() + tgui.ui_interact(user) + + log_admin("[key_name(user)] opened the Dynamic Panel.") + if(!isobserver(user)) + message_admins("[key_name_admin(user)] opened the Dynamic Panel.") + BLACKBOX_LOG_ADMIN_VERB("Dynamic Panel") + +/datum/dynamic_panel + +/datum/dynamic_panel/ui_state(mob/user) + return ADMIN_STATE(R_ADMIN) + +/datum/dynamic_panel/ui_close() + qdel(src) + +/datum/dynamic_panel/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DynamicAdmin") + ui.open() + +/datum/dynamic_panel/ui_data(mob/user) + var/list/data = list() + + if(SSdynamic.current_tier) + data["current_tier"] = list( + "number" = SSdynamic.current_tier.tier, + "name" = SSdynamic.current_tier.name, + ) + + data["ruleset_count"] = list() + for(var/category in SSdynamic.rulesets_to_spawn) + data["ruleset_count"][category] = max(SSdynamic.rulesets_to_spawn[category], 0) + + data["full_config"] = SSdynamic.get_config() + data["config_even_enabled"] = CONFIG_GET(flag/dynamic_config_enabled) && length(data["full_config"]) + + data["queued_rulesets"] = list() + for(var/i in 1 to length(SSdynamic.queued_rulesets)) + data["queued_rulesets"] += list(ruleset_to_data(SSdynamic.queued_rulesets[i]) + list("index" = i)) + + data["active_rulesets"] = list() + for(var/i in 1 to length(SSdynamic.executed_rulesets)) + data["active_rulesets"] += list(ruleset_to_data(SSdynamic.executed_rulesets[i]) + list("index" = i)) + + data["all_rulesets"] = list() + for(var/ruleset_type in subtypesof(/datum/dynamic_ruleset/roundstart)) + data["all_rulesets"][ROUNDSTART] += list(ruleset_to_data(ruleset_type)) + for(var/ruleset_type in subtypesof(/datum/dynamic_ruleset/midround)) + var/datum/dynamic_ruleset/midround/midround = ruleset_type + switch(initial(midround.midround_type)) + if(HEAVY_MIDROUND) + data["all_rulesets"][HEAVY_MIDROUND] += list(ruleset_to_data(ruleset_type)) + if(LIGHT_MIDROUND) + data["all_rulesets"][LIGHT_MIDROUND] += list(ruleset_to_data(ruleset_type)) + for(var/ruleset_type in subtypesof(/datum/dynamic_ruleset/latejoin)) + data["all_rulesets"][LATEJOIN] += list(ruleset_to_data(ruleset_type)) + + data["time_until_lights"] = COOLDOWN_TIMELEFT(SSdynamic, light_ruleset_start) + data["time_until_heavies"] = COOLDOWN_TIMELEFT(SSdynamic, heavy_ruleset_start) + data["time_until_latejoins"] = COOLDOWN_TIMELEFT(SSdynamic, latejoin_ruleset_start) + + data["time_until_next_midround"] = COOLDOWN_TIMELEFT(SSdynamic, midround_cooldown) + data["time_until_next_latejoin"] = COOLDOWN_TIMELEFT(SSdynamic, latejoin_cooldown) + data["failed_latejoins"] = SSdynamic.failed_latejoins + + data["light_midround_chance"] = SSdynamic.get_midround_chance(LIGHT_MIDROUND) + data["heavy_midround_chance"] = SSdynamic.get_midround_chance(HEAVY_MIDROUND) + data["latejoin_chance"] = SSdynamic.get_latejoin_chance() + + data["roundstarted"] = SSticker.HasRoundStarted() + + data["light_chance_maxxed"] = SSdynamic.admin_forcing_next_light + data["heavy_chance_maxxed"] = SSdynamic.admin_forcing_next_heavy + data["latejoin_chance_maxxed"] = SSdynamic.admin_forcing_next_latejoin + + data["next_dynamic_tick"] = SSdynamic.next_fire ? SSdynamic.next_fire - world.time : SSticker.GetTimeLeft() + + data["antag_events_enabled"] = SSdynamic.antag_events_enabled + + return data + +/// Pass a ruleset typepath or a ruleset instance +/datum/dynamic_panel/proc/ruleset_to_data(datum/dynamic_ruleset/ruleset) + var/list/data = list() + var/ruleset_path = isdatum(ruleset) ? ruleset.type : ruleset + data["name"] = initial(ruleset.name) + data["id"] = initial(ruleset.config_tag) + data["typepath"] = ruleset_path + data["selected_players"] = list() + data["admin_disabled"] = (ruleset_path in SSdynamic.admin_disabled_rulesets) + if(isdatum(ruleset)) + for(var/datum/mind/player as anything in ruleset.selected_minds) + data["selected_players"] += list(list( + "key" = player.key, + )) + data["hidden"] = (ruleset in SSdynamic.unreported_rulesets) + return data + +/datum/dynamic_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + switch(action) + if("remove_queued_ruleset") + var/index = params["ruleset_index"] + if(length(SSdynamic.queued_rulesets) < index) + return + var/datum/dynamic_ruleset/ruleset = SSdynamic.queued_rulesets[index] + if(!ruleset) + return + SSdynamic.queued_rulesets -= ruleset + message_admins("[key_name_admin(ui.user)] removed [ruleset.config_tag] from the dynamic ruleset queue.") + log_admin("[key_name_admin(ui.user)] removed [ruleset.config_tag] from the dynamic ruleset queue.") + qdel(ruleset) + return TRUE + if("add_queued_ruleset") + var/datum/dynamic_ruleset/ruleset_path = text2path(params["ruleset_type"]) + if(!ruleset_path) + return + SSdynamic.queue_ruleset(ruleset_path) + message_admins("[key_name_admin(ui.user)] added [initial(ruleset_path.config_tag)] to the dynamic ruleset queue.") + log_admin("[key_name_admin(ui.user)] added [initial(ruleset_path.config_tag)] to the dynamic ruleset queue.") + return TRUE + if("dynamic_vv") + ui.user?.client?.debug_variables(SSdynamic) + return TRUE + if("add_ruleset_category_count") + var/category = params["ruleset_category"] + if(!category) + return + SSdynamic.rulesets_to_spawn[category] += 1 + message_admins("[key_name_admin(ui.user)] added 1 to the [category] ruleset category.") + log_admin("[key_name_admin(ui.user)] added 1 to the [category] ruleset category.") + return TRUE + if("set_ruleset_category_count") + var/category = params["ruleset_category"] + var/count = params["ruleset_count"] + if(!category || !isnum(count)) + return + SSdynamic.rulesets_to_spawn[category] = count + message_admins("[key_name_admin(ui.user)] set the [category] ruleset category to [count].") + log_admin("[key_name_admin(ui.user)] set the [category] ruleset category to [count].") + return TRUE + if("execute_ruleset") + var/datum/dynamic_ruleset/ruleset_path = text2path(params["ruleset_type"]) + if(!ruleset_path) + return + message_admins("[key_name_admin(ui.user)] executed the ruleset [initial(ruleset_path.config_tag)].") + log_admin("[key_name_admin(ui.user)] executed the ruleset [initial(ruleset_path.config_tag)].") + ASYNC + SSdynamic.force_run_midround(ruleset_path, alert_admins_on_fail = TRUE, admin = ui.user) + return TRUE + if("disable_ruleset") + var/ruleset_path = text2path(params["ruleset_type"]) + if(!ruleset_path) + return + if(ruleset_path in SSdynamic.admin_disabled_rulesets) + SSdynamic.admin_disabled_rulesets -= ruleset_path + message_admins("[key_name_admin(ui.user)] enabled [ruleset_path] to be selected.") + log_admin("[key_name_admin(ui.user)] enabled [ruleset_path] to be selected.") + else + SSdynamic.admin_disabled_rulesets += ruleset_path + message_admins("[key_name_admin(ui.user)] disabled [ruleset_path] from being selected.") + log_admin("[key_name_admin(ui.user)] disabled [ruleset_path] from being selected.") + return TRUE + if("disable_all") + SSdynamic.admin_disabled_rulesets |= subtypesof(/datum/dynamic_ruleset) + message_admins("[key_name_admin(ui.user)] disabled all rulesets from being selected.") + log_admin("[key_name_admin(ui.user)] disabled all rulesets from being selected.") + if("enable_all") + SSdynamic.admin_disabled_rulesets.Cut() + message_admins("[key_name_admin(ui.user)] re-enabled all rulesets.") + log_admin("[key_name_admin(ui.user)] re_enabled all rulesets.") + if("set_tier") + if(SSdynamic.current_tier && SSticker.HasRoundStarted()) + return TRUE + var/list/tiers = list() + for(var/datum/dynamic_tier/tier as anything in subtypesof(/datum/dynamic_tier)) + tiers[initial(tier.name)] = tier + var/datum/dynamic_tier/picked = tgui_input_list(ui.user, "Pick a dynamic tier before the game starts", "Pick tier", tiers, ui_state = ADMIN_STATE(R_ADMIN)) + if(picked && !SSticker.HasRoundStarted()) + SSdynamic.set_tier(tiers[picked]) + message_admins("[key_name_admin(ui.user)] set the dynamic tier to [initial(picked.tier)].") + log_admin("[key_name_admin(ui.user)] set the dynamic tier to [initial(picked.tier)].") + return TRUE + if("max_light_chance") + SSdynamic.admin_forcing_next_light = !SSdynamic.admin_forcing_next_light + message_admins("[key_name_admin(ui.user)] [SSdynamic.admin_forcing_next_light ? "forced" : "reset"] the next light ruleset chance.") + log_admin("[key_name_admin(ui.user)] [SSdynamic.admin_forcing_next_light ? "forced" : "reset"] the next light ruleset chance.") + return TRUE + if("max_heavy_chance") + SSdynamic.admin_forcing_next_heavy = !SSdynamic.admin_forcing_next_heavy + message_admins("[key_name_admin(ui.user)] [SSdynamic.admin_forcing_next_heavy ? "forced" : "reset"] the next heavy ruleset chance.") + log_admin("[key_name_admin(ui.user)] [SSdynamic.admin_forcing_next_heavy ? "forced" : "reset"] the next heavy ruleset chance.") + return TRUE + if("max_latejoin_chance") + SSdynamic.admin_forcing_next_latejoin = !SSdynamic.admin_forcing_next_latejoin + message_admins("[key_name_admin(ui.user)] [SSdynamic.admin_forcing_next_latejoin ? "forced" : "reset"] the next latejoin ruleset chance.") + log_admin("[key_name_admin(ui.user)] [SSdynamic.admin_forcing_next_latejoin ? "forced" : "reset"] the next latejoin ruleset chance.") + return TRUE + if("light_start_now") + COOLDOWN_RESET(SSdynamic, light_ruleset_start) + message_admins("[key_name_admin(ui.user)] reset the light ruleset start cooldown.") + log_admin("[key_name_admin(ui.user)] reset the light ruleset start cooldown.") + return TRUE + if("heavy_start_now") + COOLDOWN_RESET(SSdynamic, heavy_ruleset_start) + message_admins("[key_name_admin(ui.user)] reset the heavy ruleset start cooldown.") + log_admin("[key_name_admin(ui.user)] reset the heavy ruleset start cooldown.") + return TRUE + if("latejoin_start_now") + COOLDOWN_RESET(SSdynamic, latejoin_ruleset_start) + message_admins("[key_name_admin(ui.user)] reset the latejoin ruleset start cooldown.") + log_admin("[key_name_admin(ui.user)] reset the latejoin ruleset start cooldown.") + return TRUE + if("reset_midround_cooldown") + COOLDOWN_RESET(SSdynamic, midround_cooldown) + message_admins("[key_name_admin(ui.user)] reset the midround cooldown.") + log_admin("[key_name_admin(ui.user)] reset the midround cooldown.") + return TRUE + if("reset_latejoin_cooldown") + COOLDOWN_RESET(SSdynamic, latejoin_cooldown) + message_admins("[key_name_admin(ui.user)] reset the latejoin cooldown.") + log_admin("[key_name_admin(ui.user)] reset the latejoin cooldown.") + return TRUE + if("hide_ruleset") + var/index = params["ruleset_index"] + if(length(SSdynamic.executed_rulesets) < index) + return + var/datum/dynamic_ruleset/ruleset = SSdynamic.executed_rulesets[index] + if(!ruleset) + return + if(ruleset in SSdynamic.unreported_rulesets) + SSdynamic.unreported_rulesets -= ruleset + message_admins("[key_name_admin(ui.user)] hid [ruleset] from the roundend report.") + log_admin("[key_name_admin(ui.user)] hid [ruleset] from the roundend report.") + else + SSdynamic.unreported_rulesets += ruleset + message_admins("[key_name_admin(ui.user)] unhid [ruleset] from the roundend report.") + log_admin("[key_name_admin(ui.user)] unhid [ruleset] from the roundend report.") + return TRUE + if("toggle_antag_events") + SSdynamic.antag_events_enabled = !SSdynamic.antag_events_enabled + message_admins("[key_name_admin(ui.user)] [SSdynamic.antag_events_enabled ? "enabled" : "disabled"] antag events.") + log_admin("[key_name_admin(ui.user)] [SSdynamic.antag_events_enabled ? "enabled" : "disabled"] antag events.") + return TRUE diff --git a/code/controllers/subsystem/dynamic/dynamic_hijacking.dm b/code/controllers/subsystem/dynamic/dynamic_hijacking.dm deleted file mode 100644 index 7577cbcd84f6..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_hijacking.dm +++ /dev/null @@ -1,25 +0,0 @@ -/datum/controller/subsystem/dynamic/proc/setup_hijacking() - RegisterSignal(SSdcs, COMSIG_GLOB_PRE_RANDOM_EVENT, PROC_REF(on_pre_random_event)) - -/datum/controller/subsystem/dynamic/proc/on_pre_random_event(datum/source, datum/round_event_control/round_event_control) - SIGNAL_HANDLER - if (!round_event_control.dynamic_should_hijack) - return - - if (random_event_hijacked != HIJACKED_NOTHING) - log_dynamic_and_announce("Random event [round_event_control.name] tried to roll, but Dynamic vetoed it (random event has already ran).") - SSevents.spawnEvent() - SSevents.reschedule() - return CANCEL_PRE_RANDOM_EVENT - - var/time_range = rand(random_event_hijack_minimum, random_event_hijack_maximum) - - if (world.time - last_midround_injection_attempt < time_range) - random_event_hijacked = HIJACKED_TOO_RECENT - log_dynamic_and_announce("Random event [round_event_control.name] tried to roll, but the last midround injection \ - was too recent. Heavy injection chance has been raised to [get_heavy_midround_injection_chance(dry_run = TRUE)]%.") - return CANCEL_PRE_RANDOM_EVENT - - if (next_midround_injection() - world.time < time_range) - log_dynamic_and_announce("Random event [round_event_control.name] tried to roll, but the next midround injection is too soon.") - return CANCEL_PRE_RANDOM_EVENT diff --git a/code/controllers/subsystem/dynamic/dynamic_logging.dm b/code/controllers/subsystem/dynamic/dynamic_logging.dm deleted file mode 100644 index 16bd56a73031..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_logging.dm +++ /dev/null @@ -1,101 +0,0 @@ -/// A "snapshot" of dynamic at an important point in time. -/// Exported to JSON in the dynamic.json log file. -/datum/dynamic_snapshot - /// The remaining midround threat - var/remaining_threat - - /// The world.time when the snapshot was taken - var/time - - /// The total number of players in the server - var/total_players - - /// The number of alive players - var/alive_players - - /// The number of dead players - var/dead_players - - /// The number of observers - var/observers - - /// The number of alive antags - var/alive_antags - - /// The rulesets chosen this snapshot - var/datum/dynamic_snapshot_ruleset/ruleset_chosen - - /// The cached serialization of this snapshot - var/serialization - -/// A ruleset chosen during a snapshot -/datum/dynamic_snapshot_ruleset - /// The name of the ruleset chosen - var/name - - /// If it is a round start ruleset, how much it was scaled by - var/scaled - - /// The number of assigned antags - var/assigned - -/datum/dynamic_snapshot_ruleset/New(datum/dynamic_ruleset/ruleset) - name = ruleset.name - assigned = ruleset.assigned.len - - if (istype(ruleset, /datum/dynamic_ruleset/roundstart)) - scaled = ruleset.scaled_times - -/// Convert the snapshot to an associative list -/datum/dynamic_snapshot/proc/to_list() - if (!isnull(serialization)) - return serialization - - serialization = list( - "remaining_threat" = remaining_threat, - "time" = time, - "total_players" = total_players, - "alive_players" = alive_players, - "dead_players" = dead_players, - "observers" = observers, - "alive_antags" = alive_antags, - "ruleset_chosen" = list( - "name" = ruleset_chosen.name, - "scaled" = ruleset_chosen.scaled, - "assigned" = ruleset_chosen.assigned, - ), - ) - - return serialization - -/// Updates the log for the current snapshots. -/datum/controller/subsystem/dynamic/proc/update_log() - var/list/serialized = list() - serialized["threat_level"] = threat_level - serialized["round_start_budget"] = initial_round_start_budget - serialized["mid_round_budget"] = threat_level - initial_round_start_budget - serialized["shown_threat"] = shown_threat - - var/list/serialized_snapshots = list() - for (var/datum/dynamic_snapshot/snapshot as anything in snapshots) - serialized_snapshots += list(snapshot.to_list()) - serialized["snapshots"] = serialized_snapshots - - rustg_file_write(json_encode(serialized), "[GLOB.log_directory]/dynamic.json") - -/// Creates a new snapshot with the given rulesets chosen, and writes to the JSON output. -/datum/controller/subsystem/dynamic/proc/new_snapshot(datum/dynamic_ruleset/ruleset_chosen) - var/datum/dynamic_snapshot/new_snapshot = new - - new_snapshot.remaining_threat = mid_round_budget - new_snapshot.time = world.time - new_snapshot.alive_players = GLOB.alive_player_list.len - new_snapshot.dead_players = GLOB.dead_player_list.len - new_snapshot.observers = GLOB.current_observers_list.len - new_snapshot.total_players = new_snapshot.alive_players + new_snapshot.dead_players + new_snapshot.observers - new_snapshot.alive_antags = GLOB.current_living_antags.len - new_snapshot.ruleset_chosen = new /datum/dynamic_snapshot_ruleset(ruleset_chosen) - - LAZYADD(snapshots, new_snapshot) - - update_log() diff --git a/code/controllers/subsystem/dynamic/dynamic_midround_rolling.dm b/code/controllers/subsystem/dynamic/dynamic_midround_rolling.dm deleted file mode 100644 index 968037b9fa2e..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_midround_rolling.dm +++ /dev/null @@ -1,108 +0,0 @@ -/// Returns the world.time of the next midround injection. -/// Will return a cached result from `next_midround_injection`, the variable. -/// If that variable is null, will generate a new one. -/datum/controller/subsystem/dynamic/proc/next_midround_injection() - if (!isnull(next_midround_injection)) - return next_midround_injection - - // Admins can futz around with the midround threat, and we want to be able to react to that - var/midround_threat = threat_level - round_start_budget - - var/rolls = CEILING(midround_threat / threat_per_midround_roll, 1) - var/distance = ((1 / (rolls + 1)) * midround_upper_bound) + midround_lower_bound - - if (last_midround_injection_attempt == 0) - last_midround_injection_attempt = SSticker.round_start_time - - return last_midround_injection_attempt + distance - -/datum/controller/subsystem/dynamic/proc/try_midround_roll() - if (!forced_injection && next_midround_injection() > world.time) - return - - if (GLOB.dynamic_forced_extended) - return - - if (EMERGENCY_PAST_POINT_OF_NO_RETURN) - return - - var/spawn_heavy = prob(get_heavy_midround_injection_chance()) - - last_midround_injection_attempt = world.time - next_midround_injection = null - forced_injection = FALSE - - log_dynamic_and_announce("A midround ruleset is rolling, and will be [spawn_heavy ? "HEAVY" : "LIGHT"].") - - random_event_hijacked = HIJACKED_NOTHING - - var/list/drafted_heavies = list() - var/list/drafted_lights = list() - - for (var/datum/dynamic_ruleset/midround/ruleset in midround_rules) - if (ruleset.weight == 0) - log_dynamic("FAIL: [ruleset] has a weight of 0") - continue - - if (!ruleset.acceptable(GLOB.alive_player_list.len, threat_level)) - var/ruleset_forced = GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED - if (ruleset_forced == RULESET_NOT_FORCED) - log_dynamic("FAIL: [ruleset] is not acceptable with the current parameters. Alive players: [GLOB.alive_player_list.len], threat level: [threat_level]") - else - log_dynamic("FAIL: [ruleset] was disabled.") - continue - - if (mid_round_budget < ruleset.cost) - log_dynamic("FAIL: [ruleset] is too expensive, and cannot be bought. Midround budget: [mid_round_budget], ruleset cost: [ruleset.cost]") - continue - - if (ruleset.minimum_round_time > world.time - SSticker.round_start_time) - log_dynamic("FAIL: [ruleset] is trying to run too early. Minimum round time: [ruleset.minimum_round_time], current round time: [world.time - SSticker.round_start_time]") - continue - - // If admins have disabled dynamic from picking from the ghost pool - if(istype(ruleset, /datum/dynamic_ruleset/midround/from_ghosts) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) - log_dynamic("FAIL: [ruleset] is a from_ghosts ruleset, but ghost roles are disabled") - continue - - ruleset.trim_candidates() - ruleset.load_templates() - if (!ruleset.ready()) - log_dynamic("FAIL: [ruleset] is not ready()") - continue - - var/ruleset_is_heavy = (ruleset.midround_ruleset_style == MIDROUND_RULESET_STYLE_HEAVY) - if (ruleset_is_heavy) - drafted_heavies[ruleset] = ruleset.get_weight() - else - drafted_lights[ruleset] = ruleset.get_weight() - - var/heavy_light_log_count = "[drafted_heavies.len] heavies / [drafted_lights.len] lights" - - log_dynamic("Rolling [spawn_heavy ? "HEAVY" : "LIGHT"]... [heavy_light_log_count]") - - if (spawn_heavy && drafted_heavies.len > 0 && pick_midround_rule(drafted_heavies, "heavy rulesets")) - return - else if (drafted_lights.len > 0 && pick_midround_rule(drafted_lights, "light rulesets")) - if (spawn_heavy) - log_dynamic_and_announce("A heavy ruleset was intended to roll, but there weren't any available. [heavy_light_log_count]") - else - log_dynamic_and_announce("No midround rulesets could be drafted. ([heavy_light_log_count])") - -/// Gets the chance for a heavy ruleset midround injection, the dry_run argument is only used for forced injection. -/datum/controller/subsystem/dynamic/proc/get_heavy_midround_injection_chance(dry_run) - var/chance_modifier = 1 - var/next_midround_roll = next_midround_injection() - SSticker.round_start_time - - if (random_event_hijacked != HIJACKED_NOTHING) - chance_modifier += (hijacked_random_event_injection_chance_modifier / 100) - - if (GLOB.current_living_antags.len == 0) - chance_modifier += 0.5 - - if (GLOB.dead_player_list.len > GLOB.alive_player_list.len) - chance_modifier -= 0.3 - - var/heavy_coefficient = CLAMP01((next_midround_roll - midround_light_upper_bound) / (midround_heavy_lower_bound - midround_light_upper_bound)) - - return 100 * (heavy_coefficient * max(1, chance_modifier)) diff --git a/code/controllers/subsystem/dynamic/dynamic_ruleset_latejoin.dm b/code/controllers/subsystem/dynamic/dynamic_ruleset_latejoin.dm new file mode 100644 index 000000000000..d780e2fa3446 --- /dev/null +++ b/code/controllers/subsystem/dynamic/dynamic_ruleset_latejoin.dm @@ -0,0 +1,128 @@ +/datum/dynamic_ruleset/latejoin + min_antag_cap = 1 + max_antag_cap = 1 + repeatable = TRUE + +/datum/dynamic_ruleset/latejoin/set_config_value(nvar, nval) + if(nvar == NAMEOF(src, min_antag_cap) || nvar == NAMEOF(src, max_antag_cap)) + return FALSE + return ..() + +/datum/dynamic_ruleset/latejoin/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, min_antag_cap) || var_name == NAMEOF(src, max_antag_cap)) + return FALSE + return ..() + +/datum/dynamic_ruleset/latejoin/is_valid_candidate(mob/candidate, client/candidate_client) + if(isnull(candidate.mind)) + return FALSE + if(candidate.mind.assigned_role.title in get_blacklisted_roles()) + return FALSE + return ..() + +/datum/dynamic_ruleset/latejoin/traitor + name = "Traitor" + config_tag = "Latejoin Traitor" + preview_antag_datum = /datum/antagonist/traitor + pref_flag = ROLE_SYNDICATE_INFILTRATOR + jobban_flag = ROLE_TRAITOR + weight = 10 + min_pop = 3 + blacklisted_roles = list( + JOB_HEAD_OF_PERSONNEL, + ) + +/datum/dynamic_ruleset/latejoin/traitor/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/traitor) + +/datum/dynamic_ruleset/latejoin/heretic + name = "Heretic" + config_tag = "Latejoin Heretic" + preview_antag_datum = /datum/antagonist/heretic + pref_flag = ROLE_HERETIC_SMUGGLER + jobban_flag = ROLE_HERETIC + weight = 3 + min_pop = 30 // Ensures good spread of sacrifice targets + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE) + blacklisted_roles = list( + JOB_HEAD_OF_PERSONNEL, + ) + +/datum/dynamic_ruleset/latejoin/heretic/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/heretic) + +/datum/dynamic_ruleset/latejoin/changeling + name = "Changeling" + config_tag = "Latejoin Changeling" + preview_antag_datum = /datum/antagonist/changeling + pref_flag = ROLE_STOWAWAY_CHANGELING + jobban_flag = ROLE_CHANGELING + weight = 3 + min_pop = 15 + blacklisted_roles = list( + JOB_HEAD_OF_PERSONNEL, + ) + +/datum/dynamic_ruleset/latejoin/changeling/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/changeling) + +/datum/dynamic_ruleset/latejoin/revolution + name = "Revolution" + config_tag = "Latejoin Revolution" + preview_antag_datum = /datum/antagonist/rev/head + pref_flag = ROLE_PROVOCATEUR + jobban_flag = ROLE_REV_HEAD + ruleset_flags = RULESET_HIGH_IMPACT + weight = 1 + min_pop = 30 + repeatable = FALSE + /// How many heads of staff are required to be on the station for this to be selected + var/heads_necessary = 3 + +/datum/dynamic_ruleset/latejoin/revolution/can_be_selected() + if(GLOB.revolution_handler) + return FALSE + var/head_check = 0 + for(var/mob/player as anything in get_active_player_list(alive_check = TRUE, afk_check = TRUE)) + if (player.mind.assigned_role.job_flags & JOB_HEAD_OF_STAFF) + head_check++ + return head_check >= heads_necessary + +/datum/dynamic_ruleset/latejoin/revolution/get_always_blacklisted_roles() + . = ..() + for(var/datum/job/job as anything in SSjob.all_occupations) + if(job.job_flags & JOB_HEAD_OF_STAFF) + . |= job.title + +/datum/dynamic_ruleset/latejoin/revolution/assign_role(datum/mind/candidate) + LAZYADD(candidate.special_roles, "Dormant Head Revolutionary") + addtimer(CALLBACK(src, PROC_REF(reveal_head), candidate), 1 MINUTES, TIMER_DELETE_ME) + +/datum/dynamic_ruleset/latejoin/revolution/proc/reveal_head(datum/mind/candidate) + LAZYREMOVE(candidate.special_roles, "Dormant Head Revolutionary") + + var/head_check = 0 + for(var/mob/player as anything in get_active_player_list(alive_check = TRUE, afk_check = TRUE)) + if(player.mind?.assigned_role.job_flags & JOB_HEAD_OF_STAFF) + head_check++ + + if(head_check < heads_necessary - 1) // little bit of leeway + SSdynamic.unreported_rulesets += src + name += " (Canceled)" + log_dynamic("[config_tag]: Not enough heads of staff were present to start a revolution.") + return + + if(!can_be_headrev(candidate)) + SSdynamic.unreported_rulesets += src + name += " (Canceled)" + log_dynamic("[config_tag]: [key_name(candidate)] was ineligible after the timer expired. Ruleset canceled.") + message_admins("[config_tag]: [key_name(candidate)] was ineligible after the timer expired. Ruleset canceled.") + return + + GLOB.revolution_handler ||= new() + var/datum/antagonist/rev/head/new_head = new() + new_head.give_flash = TRUE + new_head.give_hud = TRUE + new_head.remove_clumsy = TRUE + candidate.add_antag_datum(new_head, GLOB.revolution_handler.revs) + GLOB.revolution_handler.start_revolution() diff --git a/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm b/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm new file mode 100644 index 000000000000..40e46d4cf0e4 --- /dev/null +++ b/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm @@ -0,0 +1,1150 @@ +/datum/dynamic_ruleset/midround + repeatable = TRUE + repeatable_weight_decrease = 2 + /// LIGHT_MIDROUND or HEAVY_MIDROUND - determines which pool it enters + var/midround_type + /// If the false alarm event can pick this ruleset to trigger, well, a false alarm + var/false_alarm_able = FALSE + +/** + * Collect candidates handles getting the broad pool of players we want to pick from + * + * You can sleep in this - say, if you wanted to poll players. + */ +/datum/dynamic_ruleset/midround/proc/collect_candidates() + return list() + +/** + * Called when the ruleset is selected for false alarm + */ +/datum/dynamic_ruleset/midround/proc/false_alarm() + return + +/datum/dynamic_ruleset/midround/spiders + name = "Spiders" + config_tag = "Spiders" + midround_type = HEAVY_MIDROUND + false_alarm_able = TRUE + ruleset_flags = RULESET_INVADER + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 0, + DYNAMIC_TIER_MEDIUMHIGH = 1, + DYNAMIC_TIER_HIGH = 2, + ) + min_pop = 30 + min_antag_cap = 0 + /// Determines how many eggs to create - can take a formula like antag_cap + var/egg_count = 2 + +/datum/dynamic_ruleset/midround/spiders/can_be_selected() + return ..() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT) && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE)) + +/datum/dynamic_ruleset/midround/spiders/execute() + var/num_egg = get_antag_cap(length(GLOB.alive_player_list), egg_count) + while(num_egg > 0) + var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) + if(isnull(spawn_loc)) + break + var/obj/effect/mob_spawn/ghost_role/spider/midwife/new_eggs = new(spawn_loc) + new_eggs.amount_grown = 98 + num_egg-- + + addtimer(CALLBACK(src, PROC_REF(announce_spiders)), rand(375, 600) SECONDS) + +/datum/dynamic_ruleset/midround/spiders/proc/announce_spiders() + priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", ANNOUNCER_ALIENS) + +/datum/dynamic_ruleset/midround/spiders/false_alarm() + announce_spiders() + +/datum/dynamic_ruleset/midround/pirates + name = "Pirates" + config_tag = "Light Pirates" + midround_type = LIGHT_MIDROUND + jobban_flag = ROLE_TRAITOR + ruleset_flags = RULESET_INVADER|RULESET_ADMIN_CONFIGURABLE + weight = 3 + min_pop = 15 + min_antag_cap = 0 // ship will spawn if there are no ghosts around + + /// Pool to pick pirates from + var/list/datum/pirate_gang/pirate_pool + +/datum/dynamic_ruleset/midround/pirates/New(list/dynamic_config) + . = ..() + pirate_pool = default_pirate_pool() + +/datum/dynamic_ruleset/midround/pirates/can_be_selected() + return ..() && !SSmapping.is_planetary() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT) && length(default_pirate_pool()) > 0 + +// An abornmal ruleset that selects no players, but just spawns a pirate ship +/datum/dynamic_ruleset/midround/pirates/execute() + send_pirate_threat(pirate_pool) + +/// Returns what pool of pirates to drawn from +/// Returned list is mutated by the ruleset +/datum/dynamic_ruleset/midround/pirates/proc/default_pirate_pool() + return GLOB.light_pirate_gangs + +/datum/dynamic_ruleset/midround/pirates/heavy + name = "Pirates" + config_tag = "Heavy Pirates" + midround_type = HEAVY_MIDROUND + jobban_flag = ROLE_TRAITOR + ruleset_flags = RULESET_INVADER + weight = 3 + min_pop = 25 + min_antag_cap = 0 // ship will spawn if there are no ghosts around + +/datum/dynamic_ruleset/midround/pirates/heavy/default_pirate_pool() + return GLOB.heavy_pirate_gangs + +#define RANDOM_PIRATE_POOL "Random" + +/datum/dynamic_ruleset/midround/pirates/configure_ruleset(mob/admin) + var/list/admin_pool = list("[RULESET_CONFIG_CANCEL]" = TRUE, "[RANDOM_PIRATE_POOL]" = TRUE) + for(var/datum/pirate_gang/gang as anything in default_pirate_pool()) + admin_pool[gang.name] = gang + var/picked = tgui_input_list(admin, "Select a pirate gang", "Pirate Gang Selection", admin_pool) + if(!picked || picked == RULESET_CONFIG_CANCEL) + return RULESET_CONFIG_CANCEL + if(picked == RANDOM_PIRATE_POOL) + return null + + pirate_pool = list(admin_pool[picked]) + return null + +#undef RANDOM_PIRATE_POOL + +#define NO_ANSWER 0 +#define POSITIVE_ANSWER 1 +#define NEGATIVE_ANSWER 2 + +/datum/dynamic_ruleset/midround/pirates/proc/send_pirate_threat(list/pirate_selection) + var/datum/pirate_gang/chosen_gang = pick_n_take(pirate_selection) + ///If there was nothing to pull from our requested list, stop here. + if(!chosen_gang) + message_admins("Error attempting to run the space pirate event, as the given pirate gangs list was empty.") + return + //set payoff + var/payoff = 0 + var/datum/bank_account/account = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(account) + payoff = max(PAYOFF_MIN, FLOOR(account.account_balance * 0.80, 1000)) + var/datum/comm_message/threat = chosen_gang.generate_message(payoff) + //send message + priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", SSstation.announcer.get_rand_report_sound()) + threat.answer_callback = CALLBACK(src, PROC_REF(pirates_answered), threat, chosen_gang, payoff, world.time) + addtimer(CALLBACK(src, PROC_REF(spawn_pirates), threat, chosen_gang), RESPONSE_MAX_TIME) + GLOB.communications_controller.send_message(threat, unique = TRUE) + +/datum/dynamic_ruleset/midround/pirates/proc/pirates_answered(datum/comm_message/threat, datum/pirate_gang/chosen_gang, payoff, initial_send_time) + if(world.time > initial_send_time + RESPONSE_MAX_TIME) + priority_announce(chosen_gang.response_too_late, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color) + return + if(!threat?.answered) + return + if(threat.answered == NEGATIVE_ANSWER) + priority_announce(chosen_gang.response_rejected, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color) + return + + var/datum/bank_account/plundered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(plundered_account) + if(plundered_account.adjust_money(-payoff)) + chosen_gang.paid_off = TRUE + priority_announce(chosen_gang.response_received, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color) + else + priority_announce(chosen_gang.response_not_enough, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color) + +/datum/dynamic_ruleset/midround/pirates/proc/spawn_pirates(datum/comm_message/threat, datum/pirate_gang/chosen_gang) + if(chosen_gang.paid_off) + return + + var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a [span_notice("pirate crew of [chosen_gang.name]?")]", check_jobban = ROLE_TRAITOR, alert_pic = /obj/item/claymore/cutlass, role_name_text = "pirate crew") + shuffle_inplace(candidates) + + var/template_key = "pirate_[chosen_gang.ship_template_id]" + var/datum/map_template/shuttle/pirate/ship = SSmapping.shuttle_templates[template_key] + var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width) + var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height) + var/z = SSmapping.empty_space.z_value + var/turf/T = locate(x,y,z) + if(!T) + CRASH("Pirate event found no turf to load in") + + if(!ship.load(T)) + CRASH("Loading pirate ship failed!") + + for(var/turf/area_turf as anything in ship.get_affected_turfs(T)) + for(var/obj/effect/mob_spawn/ghost_role/human/pirate/spawner in area_turf) + if(candidates.len > 0) + var/mob/our_candidate = candidates[1] + var/mob/spawned_mob = spawner.create_from_ghost(our_candidate) + candidates -= our_candidate + notify_ghosts( + "The [chosen_gang.ship_name] has an object of interest: [spawned_mob]!", + source = spawned_mob, + header = "Pirates!", + ) + else + notify_ghosts( + "The [chosen_gang.ship_name] has an object of interest: [spawner]!", + source = spawner, + header = "Pirate Spawn Here!", + ) + + priority_announce(chosen_gang.arrival_announcement, sender_override = chosen_gang.ship_name) + +#undef NO_ANSWER +#undef POSITIVE_ANSWER +#undef NEGATIVE_ANSWER +/** + * ### Ghost rulesets + * + * Rulesets which select an observer/ghost player to play as a new character + * + * Implementation notes: + * - prepare_role will handle making the body for the mob for you. Avoid touching it if not necessary. + * - create_ruleset_body is what makes the new /mob for the candidate. It handles putting the player in the body for you. + * You can override it entirely for to spawn a different mob type. + * You can also override it to spawn nothing, if you're doing special handling in assign_role, but you'll have to handle moving the player yourself. + * - assign_role is what gives the player their antag datum. + */ +/datum/dynamic_ruleset/midround/from_ghosts + ///Path of an item to show up in ghost polls for applicants to sign up. + var/signup_atom_appearance = /obj/structure/sign/poster/contraband/syndicate_recruitment + /// Text shown in the candidate poll. Optional, if unset uses pref_flag. (Though required if pref_flag is unset) + var/candidate_role + +/datum/dynamic_ruleset/midround/from_ghosts/can_be_selected() + SHOULD_CALL_PARENT(TRUE) + return ..() && (GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT) + +/datum/dynamic_ruleset/midround/from_ghosts/get_candidate_mind(mob/dead/candidate) + // Ghost roles will always get a fresh mind + return new /datum/mind(candidate.key) + +/datum/dynamic_ruleset/midround/from_ghosts/prepare_for_role(datum/mind/candidate) + var/mob/living/body = create_ruleset_body() + if(isnull(body)) + return + candidate.transfer_to(body, force_key_move = TRUE) // yoinks the candidate's client + if(ishuman(body)) + var/mob/living/carbon/human/human_body = body + body.client?.prefs.safe_transfer_prefs_to(body) + human_body.dna.remove_all_mutations() + human_body.dna.update_dna_identity() + +/** + * Handles making the body for the candidate + * + * Handling loc is not necessary here - you can do it in assign_role + * + * Returning null will skip body creation entirely, though you will be expected to do it yourself in assign_role + */ +/datum/dynamic_ruleset/midround/from_ghosts/proc/create_ruleset_body() + return new /mob/living/carbon/human + +/datum/dynamic_ruleset/midround/from_ghosts/collect_candidates() + var/readable_poll_role = candidate_role || pref_flag + if(isnull(readable_poll_role)) + stack_trace("[config_tag]: No candidate role or pref_flag set, give it a human readable candidate roll at the bare minimum.") + readable_poll_role = "Some Midround Antagonist Without A Role Set (Yell At Coders)" + + return SSpolling.poll_candidates( + group = trim_candidates(GLOB.dead_player_list | GLOB.current_observers_list), + question = "Looking for volunteers to become [span_notice(readable_poll_role)] for [span_danger(name)]", + // check_jobban = list(ROLE_SYNDICATE, jobban_flag || pref_flag), // Not necessary, handled in trim_candidates() + // role = pref_flag, // Not necessary, handled in trim_candidates() + poll_time = 30 SECONDS, + alert_pic = signup_atom_appearance, + role_name_text = readable_poll_role, + ) + +/datum/dynamic_ruleset/midround/from_ghosts/wizard + name = "Wizard" + config_tag = "Midround Wizard" + preview_antag_datum = /datum/antagonist/wizard + midround_type = HEAVY_MIDROUND + candidate_role = "Wizard" + pref_flag = ROLE_WIZARD_MIDROUND + jobban_flag = ROLE_WIZARD + ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 0, + DYNAMIC_TIER_MEDIUMHIGH = 1, + DYNAMIC_TIER_HIGH = 2, + ) + min_pop = 30 + max_antag_cap = 1 + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN) + signup_atom_appearance = /obj/item/clothing/head/wizard + +/datum/dynamic_ruleset/midround/from_ghosts/wizard/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/wizard) // moves to lair for us + +/datum/dynamic_ruleset/midround/from_ghosts/nukies + name = "Nuclear Operatives" + config_tag = "Midround Nukeops" + preview_antag_datum = /datum/antagonist/nukeop + midround_type = HEAVY_MIDROUND + candidate_role = "Operative" + pref_flag = ROLE_OPERATIVE_MIDROUND + jobban_flag = ROLE_OPERATIVE + ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 30 + min_antag_cap = list("denominator" = 18, "offset" = 1) + repeatable = FALSE + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE) + signup_atom_appearance = /obj/machinery/nuclearbomb/syndicate + +/datum/dynamic_ruleset/midround/from_ghosts/nukies/create_execute_args() + return list( + new /datum/team/nuclear(), + get_most_experienced(selected_minds, pref_flag), + ) + +/datum/dynamic_ruleset/midround/from_ghosts/nukies/assign_role(datum/mind/candidate, datum/team/nuclear/nuke_team, datum/mind/most_experienced) + if(most_experienced == candidate) + candidate.add_antag_datum(/datum/antagonist/nukeop/leader, nuke_team) // moves to nuke base for us + else + candidate.add_antag_datum(/datum/antagonist/nukeop, nuke_team) // moves to nuke base for us + +/datum/dynamic_ruleset/midround/from_ghosts/nukies/round_result() + var/datum/antagonist/nukeop/nukie = selected_minds[1].has_antag_datum(/datum/antagonist/nukeop) + var/datum/team/nuclear/nuke_team = nukie.get_team() + var/result = nuke_team.get_result() + switch(result) + if(NUKE_RESULT_FLUKE) + SSticker.mode_result = "loss - syndicate nuked - disk secured" + SSticker.news_report = NUKE_SYNDICATE_BASE + if(NUKE_RESULT_NUKE_WIN) + SSticker.mode_result = "win - syndicate nuke" + SSticker.news_report = STATION_DESTROYED_NUKE + if(NUKE_RESULT_NOSURVIVORS) + SSticker.mode_result = "halfwin - syndicate nuke - did not evacuate in time" + SSticker.news_report = STATION_DESTROYED_NUKE + if(NUKE_RESULT_WRONG_STATION) + SSticker.mode_result = "halfwin - blew wrong station" + SSticker.news_report = NUKE_MISS + if(NUKE_RESULT_WRONG_STATION_DEAD) + SSticker.mode_result = "halfwin - blew wrong station - did not evacuate in time" + SSticker.news_report = NUKE_MISS + if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD) + SSticker.mode_result = "loss - evacuation - disk secured - syndi team dead" + SSticker.news_report = OPERATIVES_KILLED + if(NUKE_RESULT_CREW_WIN) + SSticker.mode_result = "loss - evacuation - disk secured" + SSticker.news_report = OPERATIVES_KILLED + if(NUKE_RESULT_DISK_LOST) + SSticker.mode_result = "halfwin - evacuation - disk not secured" + SSticker.news_report = OPERATIVE_SKIRMISH + if(NUKE_RESULT_DISK_STOLEN) + SSticker.mode_result = "halfwin - detonation averted" + SSticker.news_report = OPERATIVE_SKIRMISH + else + SSticker.mode_result = "halfwin - interrupted" + SSticker.news_report = OPERATIVE_SKIRMISH + +/datum/dynamic_ruleset/midround/from_ghosts/nukies/clown + name = "Clown Operatives" + config_tag = "Midround Clownops" + preview_antag_datum = /datum/antagonist/nukeop/clownop + candidate_role = "Operative" + pref_flag = ROLE_CLOWN_OPERATIVE_MIDROUND + jobban_flag = ROLE_CLOWN_OPERATIVE + weight = 0 + signup_atom_appearance = /obj/machinery/nuclearbomb/syndicate/bananium + +/datum/dynamic_ruleset/midround/from_ghosts/nukies/clown/assign_role(datum/mind/candidate, datum/team/nuclear/nuke_team, datum/mind/most_experienced) + if(most_experienced == candidate) + candidate.add_antag_datum(/datum/antagonist/nukeop/leader/clownop, nuke_team) // moves to nuke base for us + else + candidate.add_antag_datum(/datum/antagonist/nukeop/clownop, nuke_team) // moves to nuke base for us + +/datum/dynamic_ruleset/midround/from_ghosts/blob + name = "Blob" + config_tag = "Blob" + preview_antag_datum = /datum/antagonist/blob + midround_type = HEAVY_MIDROUND + false_alarm_able = TRUE + pref_flag = ROLE_BLOB + ruleset_flags = RULESET_INVADER + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 30 + max_antag_cap = 1 + repeatable_weight_decrease = 3 + signup_atom_appearance = /obj/structure/blob/normal + /// How many points does the blob spawn with + var/starting_points = OVERMIND_STARTING_POINTS + +/datum/dynamic_ruleset/midround/from_ghosts/blob/create_ruleset_body() + return new /mob/eye/blob(get_blobspawn(), starting_points) + +/datum/dynamic_ruleset/midround/from_ghosts/blob/assign_role(datum/mind/candidate) + return // everything is handled by blob new() + +/datum/dynamic_ruleset/midround/from_ghosts/blob/proc/get_blobspawn() + if(!length(GLOB.blobstart)) + var/obj/effect/landmark/observer_start/default = locate() in GLOB.landmarks_list + return get_turf(default) + + return pick(GLOB.blobstart) + +/datum/dynamic_ruleset/midround/from_ghosts/blob/false_alarm() + priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", ANNOUNCER_OUTBREAK5) + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph + name = "Alien Infestation" + config_tag = "Xenomorph" + preview_antag_datum = /datum/antagonist/xeno + midround_type = HEAVY_MIDROUND + false_alarm_able = TRUE + pref_flag = ROLE_ALIEN + ruleset_flags = RULESET_INVADER + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 5, + DYNAMIC_TIER_HIGH = 5, + ) + min_pop = 30 + max_antag_cap = 1 + min_antag_cap = 1 + repeatable_weight_decrease = 3 + signup_atom_appearance = /mob/living/basic/alien + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/New(list/dynamic_config) + . = ..() + max_antag_cap += prob(50) // 50% chance to get a second xeno, free! + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/can_be_selected() + return ..() && length(find_vents()) > 0 + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute() + . = ..() + addtimer(CALLBACK(src, PROC_REF(announce_xenos)), rand(375, 600) SECONDS) + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/proc/announce_xenos() + priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", ANNOUNCER_ALIENS) + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/false_alarm() + announce_xenos() + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/create_ruleset_body() + return new /mob/living/carbon/alien/larva + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/create_execute_args() + return list(find_vents()) + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/assign_role(datum/mind/candidate, list/vent_list) + // xeno login gives antag datums + var/obj/vent = length(vent_list) >= 2 ? pick_n_take(vent_list) : vent_list[1] + candidate.current.move_into_vent(vent) + +/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/proc/find_vents() + var/list/vents = list() + var/list/vent_pumps = SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/vent_pump) + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent as anything in vent_pumps) + if(QDELETED(temp_vent)) + continue + if(!is_station_level(temp_vent.loc.z) || temp_vent.welded) + continue + var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] + if(!temp_vent_parent) + continue + // Stops Aliens getting stuck in small networks. + // See: Security, Virology + if(length(temp_vent_parent.other_atmos_machines) <= 20) + continue + vents += temp_vent + return vents + +/datum/dynamic_ruleset/midround/from_ghosts/nightmare + name = "Nightmare" + config_tag = "Nightmare" + preview_antag_datum = /datum/antagonist/nightmare + midround_type = LIGHT_MIDROUND + pref_flag = ROLE_NIGHTMARE + ruleset_flags = RULESET_INVADER + weight = 5 + min_pop = 15 + max_antag_cap = 1 + signup_atom_appearance = /obj/item/light_eater + +/datum/dynamic_ruleset/midround/from_ghosts/nightmare/can_be_selected() + return ..() && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE)) + +/datum/dynamic_ruleset/midround/from_ghosts/nightmare/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/nightmare) + candidate.current.set_species(/datum/species/shadow/nightmare) + candidate.current.forceMove(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE)) + playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1) + +/datum/dynamic_ruleset/midround/from_ghosts/space_dragon + name = "Space Dragon" + config_tag = "Space Dragon" + preview_antag_datum = /datum/antagonist/space_dragon + midround_type = HEAVY_MIDROUND + false_alarm_able = TRUE + pref_flag = ROLE_SPACE_DRAGON + ruleset_flags = RULESET_INVADER + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 3, + DYNAMIC_TIER_MEDIUMHIGH = 5, + DYNAMIC_TIER_HIGH = 5, + ) + min_pop = 30 + max_antag_cap = 1 + repeatable_weight_decrease = 3 + signup_atom_appearance = /mob/living/basic/space_dragon + +/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/can_be_selected() + return ..() && !isnull(find_space_spawn()) + +/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/create_ruleset_body() + return new /mob/living/basic/space_dragon + +/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/space_dragon) + candidate.current.forceMove(find_space_spawn()) + playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1) + +/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/execute() + . = ..() + addtimer(CALLBACK(src, PROC_REF(announce_space_dragon)), rand(5, 10) SECONDS) + +/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/proc/announce_space_dragon() + priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert") + +/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/false_alarm() + announce_space_dragon() + +/datum/dynamic_ruleset/midround/from_ghosts/abductors + name = "Abductors" + config_tag = "Abductors" + preview_antag_datum = /datum/antagonist/abductor + midround_type = LIGHT_MIDROUND + pref_flag = ROLE_ABDUCTOR + ruleset_flags = RULESET_INVADER + weight = 5 + min_pop = 20 + min_antag_cap = 2 + repeatable_weight_decrease = 3 + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS) + signup_atom_appearance = /obj/item/melee/baton/abductor + +/datum/dynamic_ruleset/midround/from_ghosts/abductors/can_be_selected() + if(!..()) + return FALSE + var/num_abductors = 0 + for(var/datum/team/abductor_team/team in GLOB.antagonist_teams) + num_abductors++ + return num_abductors < 4 + +/datum/dynamic_ruleset/midround/from_ghosts/abductors/create_execute_args() + return list(new /datum/team/abductor_team()) + +/datum/dynamic_ruleset/midround/from_ghosts/abductors/assign_role(datum/mind/candidate, datum/team/abductor_team/team) + if(candidate == selected_minds[1]) + candidate.add_antag_datum(/datum/antagonist/abductor/scientist, team) // sets species and moves to spawn point + else + candidate.add_antag_datum(/datum/antagonist/abductor/agent, team) // sets species and moves to spawn point + +/datum/dynamic_ruleset/midround/from_ghosts/space_ninja + name = "Space Ninja" + config_tag = "Space Ninja" + preview_antag_datum = /datum/antagonist/ninja + midround_type = HEAVY_MIDROUND + pref_flag = ROLE_NINJA + ruleset_flags = RULESET_INVADER + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 0, + DYNAMIC_TIER_MEDIUMHIGH = 1, + DYNAMIC_TIER_HIGH = 2, + ) + min_pop = 30 + max_antag_cap = 1 + repeatable = FALSE + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY) + signup_atom_appearance = /obj/item/energy_katana + +/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/can_be_selected() + return ..() && !isnull(find_space_spawn()) + +/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/assign_role(datum/mind/candidate) + var/mob/living/carbon/human/new_ninja = candidate.current + new_ninja.forceMove(find_space_spawn()) // ninja antag datum needs the mob to be in place first + randomize_human_normie(new_ninja) + var/new_name = "[pick(GLOB.ninja_titles)] [pick(GLOB.ninja_names)]" + new_ninja.name = new_name + new_ninja.real_name = new_name + new_ninja.dna.update_dna_identity() // ninja antag datum needs dna to be set first + candidate.add_antag_datum(/datum/antagonist/ninja) + +/datum/dynamic_ruleset/midround/from_ghosts/revenant + name = "Revenant" + config_tag = "Revenant" + preview_antag_datum = /datum/antagonist/revenant + midround_type = LIGHT_MIDROUND + pref_flag = ROLE_REVENANT + ruleset_flags = RULESET_INVADER + weight = 5 + min_pop = 10 + max_antag_cap = 1 + repeatable = FALSE + signup_atom_appearance = /mob/living/basic/revenant + /// There must be this many dead mobs on the station for a revenant to spawn (of all mob types, not just humans) + /// Remember there's usually 2-3 that spawn in the Morgue roundstart, so adjust this accordingly + var/required_station_corpses = 10 + +/datum/dynamic_ruleset/midround/from_ghosts/revenant/can_be_selected() + if(!..()) + return FALSE + var/num_station_corpses = 0 + for(var/mob/deceased as anything in GLOB.dead_mob_list) + var/turf/deceased_turf = get_turf(deceased) + if(is_station_level(deceased_turf?.z)) + num_station_corpses++ + + return num_station_corpses > required_station_corpses + +/datum/dynamic_ruleset/midround/from_ghosts/revenant/create_ruleset_body() + return new /mob/living/basic/revenant(pick(get_revenant_spawns())) + +/datum/dynamic_ruleset/midround/from_ghosts/revenant/assign_role(datum/mind/candidate) + return // revenant new() handles everything + +/datum/dynamic_ruleset/midround/from_ghosts/revenant/proc/get_revenant_spawns() + var/list/spawn_locs = list() + for(var/mob/deceased in GLOB.dead_mob_list) + var/turf/deceased_turf = get_turf(deceased) + if(is_station_level(deceased_turf?.z)) + spawn_locs += deceased_turf + if(!length(spawn_locs) || length(spawn_locs) < 12) // get a comfortably large pool of spawnpoints + for(var/obj/structure/bodycontainer/corpse_container in GLOB.bodycontainers) + var/turf/container_turf = get_turf(corpse_container) + if(is_station_level(container_turf?.z)) + spawn_locs += container_turf + if(!length(spawn_locs) || length(spawn_locs) < 4) // get a comfortably large pool of spawnpoints + for(var/obj/effect/landmark/carpspawn/carpspawn in GLOB.landmarks_list) + spawn_locs += carpspawn.loc + + return spawn_locs + +/datum/dynamic_ruleset/midround/from_ghosts/space_changeling + name = "Space Changeling" + config_tag = "Midround Changeling" + preview_antag_datum = /datum/antagonist/changeling/space + midround_type = LIGHT_MIDROUND + candidate_role = "Changeling" + pref_flag = ROLE_CHANGELING_MIDROUND + jobban_flag = ROLE_CHANGELING + ruleset_flags = RULESET_INVADER + weight = 5 + min_pop = 15 + max_antag_cap = 1 + signup_atom_appearance = /obj/effect/meteor/meaty/changeling + +/datum/dynamic_ruleset/midround/from_ghosts/space_changeling/create_ruleset_body() + return // handled by generate_changeling_meteor() entirely + +/datum/dynamic_ruleset/midround/from_ghosts/space_changeling/assign_role(datum/mind/candidate) + generate_changeling_meteor(candidate) + +/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone + name = "Paradox Clone" + config_tag = "Paradox Clone" + preview_antag_datum = /datum/antagonist/paradox_clone + midround_type = LIGHT_MIDROUND + pref_flag = ROLE_PARADOX_CLONE + ruleset_flags = RULESET_INVADER + weight = 5 + min_pop = 10 + max_antag_cap = 1 + signup_atom_appearance = /obj/effect/bluespace_stream + +/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/can_be_selected() + return ..() && !isnull(find_clone()) && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE)) + +/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/create_ruleset_body() + return // handled by assign_role() entirely + +/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/assign_role(datum/mind/candidate) + var/mob/living/carbon/human/good_version = find_clone() + var/mob/living/carbon/human/bad_version = good_version.make_full_human_copy(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE)) + candidate.transfer_to(bad_version, force_key_move = TRUE) + + var/datum/antagonist/paradox_clone/antag = candidate.add_antag_datum(/datum/antagonist/paradox_clone) + antag.original_ref = WEAKREF(good_version.mind) + antag.setup_clone() + + playsound(bad_version, 'sound/items/weapons/zapbang.ogg', 30, TRUE) + bad_version.put_in_hands(new /obj/item/storage/toolbox/mechanical()) //so they dont get stuck in maints + +/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/proc/find_clone() + var/list/possible_targets = list() + + for(var/mob/living/carbon/human/player in GLOB.player_list) + if(!player.client || !player.mind || player.stat != CONSCIOUS) + continue + if(!(player.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) + continue + possible_targets += player + + if(length(possible_targets)) + return pick(possible_targets) + return null + +/datum/dynamic_ruleset/midround/from_ghosts/voidwalker + name = "Voidwalker" + config_tag = "Voidwalker" + preview_antag_datum = /datum/antagonist/voidwalker + midround_type = LIGHT_MIDROUND + pref_flag = ROLE_VOIDWALKER + ruleset_flags = RULESET_INVADER + weight = 5 + min_pop = 30 // Ensures there's a lot of people near windows + max_antag_cap = 1 + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_VOIDWALKER_VOID) + signup_atom_appearance = /obj/item/clothing/head/helmet/skull/cosmic + +/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/can_be_selected() + return ..() && !SSmapping.is_planetary() && !isnull(find_space_spawn()) + +/datum/dynamic_ruleset/midround/from_ghosts/voidwalker/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/voidwalker) + candidate.current.set_species(/datum/species/voidwalker) + candidate.current.forceMove(find_space_spawn()) + playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1) + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives + name = "Fugitive" + config_tag = "Fugitives" + preview_antag_datum = /datum/antagonist/fugitive + midround_type = LIGHT_MIDROUND + pref_flag = ROLE_FUGITIVE + ruleset_flags = RULESET_INVADER|RULESET_ADMIN_CONFIGURABLE + weight = 3 + min_pop = 20 + max_antag_cap = 4 + min_antag_cap = 3 + repeatable = FALSE + signup_atom_appearance = /obj/item/card/id/advanced/prisoner + /// What backstory is the fugitive(s)? + VAR_FINAL/fugitive_backstory + /// What backstory is the hunter(s)? + VAR_FINAL/hunter_backstory + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/can_be_selected() + return ..() && !SSmapping.is_planetary() && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE)) + +// If less than a certain number of candidates accept the poll, it varies how many antags are spawned +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/collect_candidates() + . = ..() + if(length(.) <= 1 || prob(30 - (length(.) * 2))) + min_antag_cap = 1 + max_antag_cap = 1 + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/create_execute_args() + return list( + new /datum/team/fugitive(), + find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE), + ) + +#define RANDOM_BACKSTORY "Random" + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/configure_ruleset(mob/admin) + var/list/fugitive_backstories = list( + FUGITIVE_BACKSTORY_CULTIST, + FUGITIVE_BACKSTORY_INVISIBLE, + FUGITIVE_BACKSTORY_PRISONER, + FUGITIVE_BACKSTORY_SYNTH, + FUGITIVE_BACKSTORY_WALDO, + RANDOM_BACKSTORY, + RULESET_CONFIG_CANCEL, + ) + var/list/hunter_backstories = list( + HUNTER_PACK_BOUNTY, + HUNTER_PACK_COPS, + HUNTER_PACK_MI13, + HUNTER_PACK_PSYKER, + HUNTER_PACK_RUSSIAN, + RANDOM_BACKSTORY, + RULESET_CONFIG_CANCEL, + ) + + var/picked_fugitive_backstory = tgui_input_list(admin, "Select a fugitive backstory", "Fugitive Backstory", fugitive_backstories) + if(!picked_fugitive_backstory || picked_fugitive_backstory == RULESET_CONFIG_CANCEL) + return RULESET_CONFIG_CANCEL + if(picked_fugitive_backstory != RANDOM_BACKSTORY) + fugitive_backstory = picked_fugitive_backstory + + var/picked_hunter_backstory = tgui_input_list(admin, "Select a hunter backstory", "Hunter Backstory", hunter_backstories) + if(!picked_hunter_backstory || picked_hunter_backstory == RULESET_CONFIG_CANCEL) + return RULESET_CONFIG_CANCEL + if(picked_hunter_backstory != RANDOM_BACKSTORY) + hunter_backstory = picked_hunter_backstory + + return null + +#undef RANDOM_BACKSTORY + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/execute() + if(length(selected_minds) == 1) + fugitive_backstory ||= pick( + FUGITIVE_BACKSTORY_INVISIBLE, + FUGITIVE_BACKSTORY_WALDO, + ) + else + fugitive_backstory ||= pick( + FUGITIVE_BACKSTORY_CULTIST, + FUGITIVE_BACKSTORY_PRISONER, + FUGITIVE_BACKSTORY_SYNTH, + ) + + hunter_backstory ||= pick( + HUNTER_PACK_COPS, + HUNTER_PACK_RUSSIAN, + HUNTER_PACK_BOUNTY, + HUNTER_PACK_PSYKER, + HUNTER_PACK_MI13, + ) + . = ..() + addtimer(CALLBACK(src, PROC_REF(check_spawn_hunters), hunter_backstory, 10 MINUTES), 1 MINUTES) + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/assign_role(datum/mind/candidate, datum/team/fugitive/team, turf/team_spawn) + candidate.current.forceMove(team_spawn) + equip_fugitive(candidate.current, team) + if(length(selected_minds) > 1 && candidate == selected_minds[1]) + equip_fugitive_leader(candidate.current) + playsound(candidate.current, 'sound/items/weapons/emitter.ogg', 50, TRUE) + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/equip_fugitive(mob/living/carbon/human/fugitive, datum/team/fugitive/team) + fugitive.set_species(/datum/species/human) + randomize_human_normie(fugitive) + + var/datum/antagonist/fugitive/antag = new() + antag.backstory = fugitive_backstory + fugitive.mind.add_antag_datum(antag, team) + // Should really datumize this at some point + switch(fugitive_backstory) + if(FUGITIVE_BACKSTORY_PRISONER) + fugitive.equipOutfit(/datum/outfit/prisoner) + if(FUGITIVE_BACKSTORY_CULTIST) + fugitive.equipOutfit(/datum/outfit/yalp_cultist) + if(FUGITIVE_BACKSTORY_WALDO) + fugitive.equipOutfit(/datum/outfit/waldo) + if(FUGITIVE_BACKSTORY_SYNTH) + fugitive.equipOutfit(/datum/outfit/synthetic) + if(FUGITIVE_BACKSTORY_INVISIBLE) + fugitive.equipOutfit(/datum/outfit/invisible_man) + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/equip_fugitive_leader(mob/living/carbon/human/fugitive) + var/turf/leader_turf = get_turf(fugitive) + var/obj/item/storage/toolbox/mechanical/toolbox = new(leader_turf) + fugitive.put_in_hands(toolbox) + + switch(fugitive_backstory) + if(FUGITIVE_BACKSTORY_SYNTH) + new /obj/item/choice_beacon/augments(leader_turf) + new /obj/item/autosurgeon(leader_turf) + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/check_spawn_hunters(remaining_time) + //if the emergency shuttle has been called, spawn hunters now to give them a chance + if(remaining_time == 0 || !EMERGENCY_IDLE_OR_RECALLED) + spawn_hunters() + return + addtimer(CALLBACK(src, PROC_REF(check_spawn_hunters), remaining_time - 1 MINUTES), 1 MINUTES) + +/datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/spawn_hunters() + var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a group of [span_notice(hunter_backstory)]?", check_jobban = list(ROLE_FUGITIVE_HUNTER, ROLE_SYNDICATE), alert_pic = /obj/machinery/sleeper, role_name_text = hunter_backstory) + shuffle_inplace(candidates) + + var/datum/map_template/shuttle/hunter/ship + switch(hunter_backstory) + if(HUNTER_PACK_COPS) + ship = new /datum/map_template/shuttle/hunter/space_cop + if(HUNTER_PACK_RUSSIAN) + ship = new /datum/map_template/shuttle/hunter/russian + if(HUNTER_PACK_BOUNTY) + ship = new /datum/map_template/shuttle/hunter/bounty + if(HUNTER_PACK_PSYKER) + ship = new /datum/map_template/shuttle/hunter/psyker + if(HUNTER_PACK_MI13) + ship = new/datum/map_template/shuttle/hunter/mi13_foodtruck + + var/x = rand(TRANSITIONEDGE, world.maxx - TRANSITIONEDGE - ship.width) + var/y = rand(TRANSITIONEDGE, world.maxy - TRANSITIONEDGE - ship.height) + var/z = SSmapping.empty_space.z_value + var/turf/placement_turf = locate(x, y ,z) + if(!placement_turf) + CRASH("Fugitive Hunters (Created from fugitive event) found no turf to load in") + if(!ship.load(placement_turf)) + CRASH("Loading [hunter_backstory] ship failed!") + + for(var/turf/shuttle_turf in ship.get_affected_turfs(placement_turf)) + for(var/obj/effect/mob_spawn/ghost_role/human/fugitive/spawner in shuttle_turf) + if(length(candidates)) + var/mob/our_candidate = candidates[1] + var/mob/spawned_mob = spawner.create_from_ghost(our_candidate) + candidates -= our_candidate + notify_ghosts( + "[spawner.prompt_name] has awoken: [spawned_mob]!", + source = spawned_mob, + header = "Come look!", + ) + else + notify_ghosts( + "[spawner.prompt_name] spawner has been created!", + source = spawner, + header = "Spawn Here!", + ) + + var/list/announcement_text_list = list() + var/announcement_title = "" + switch(hunter_backstory) + if(HUNTER_PACK_COPS) + announcement_text_list += "Attention Crew of [station_name()], this is the Police. A wanted criminal has been reported taking refuge on your station." + announcement_text_list += "We have a warrant from the SSC authorities to take them into custody. Officers have been dispatched to your location." + announcement_text_list += "We demand your cooperation in bringing this criminal to justice." + announcement_title += "Spacepol Command" + if(HUNTER_PACK_RUSSIAN) + announcement_text_list += "Zdraviya zhelaju, [station_name()] crew. We are coming to your station." + announcement_text_list += "There is a criminal aboard. We will arrest them and return them to the gulag. That's good, yes?" + announcement_title += "Russian Freighter" + if(HUNTER_PACK_BOUNTY) + announcement_text_list += "[station_name()]. One of our bounty marks has ended up on your station. We will be arriving to collect shortly." + announcement_text_list += "Let's make this quick. If you don't want trouble, stay the hell out of our way." + announcement_title += "Unregistered Signal" + if(HUNTER_PACK_PSYKER) + announcement_text_list += "HEY, CAN YOU HEAR US? We're coming to your station. There's a bad guy down there, really bad guy. We need to arrest them." + announcement_text_list += "We're also offering fortune telling services out of the front door if you have paying customers." + announcement_title += "Fortune-Telling Entertainment Shuttle" + if(HUNTER_PACK_MI13) + announcement_text_list += "Illegal intrusion detected in the crew monitoring network. Central Command has been informed." + announcement_text_list += "Please report any suspicious individuals or behaviour to your local security team." + announcement_title += "Nanotrasen Intrusion Countermeasures Electronics" + + if(!length(announcement_text_list)) + announcement_text_list += "Unidentified ship detected near the station." + stack_trace("Fugitive hunter announcement was unable to generate an announcement text based on backstory: [hunter_backstory]") + + if(!length(announcement_title)) + announcement_title += "Unknown Signal" + stack_trace("Fugitive hunter announcement was unable to generate an announcement title based on backstory: [hunter_backstory]") + + priority_announce(jointext(announcement_text_list, " "), announcement_title) + +/datum/dynamic_ruleset/midround/from_ghosts/morph + name = "Morph" + config_tag = "Morph" + // preview_antag_datum = /datum/antagonist/morph // Doesn't actually have its own pref + midround_type = LIGHT_MIDROUND + candidate_role = "Morphling" + jobban_flag = ROLE_ALIEN + ruleset_flags = RULESET_INVADER + weight = 0 + max_antag_cap = 1 + signup_atom_appearance = /mob/living/basic/morph + +/datum/dynamic_ruleset/midround/from_ghosts/morph/can_be_selected() + return ..() && !isnull(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE)) + +/datum/dynamic_ruleset/midround/from_ghosts/morph/create_ruleset_body() + return new /mob/living/basic/morph(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE)) + +/datum/dynamic_ruleset/midround/from_ghosts/morph/assign_role(datum/mind/candidate) + candidate.set_assigned_role(SSjob.get_job_type(/datum/job/morph)) + candidate.add_antag_datum(/datum/antagonist/morph) + +/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon + name = "Slaughter Demon" + config_tag = "Slaughter Demon" + candidate_role = "Slaughter Demon" + // preview_antag_datum = /datum/antagonist/slaughter // Doesn't actually have its own pref + midround_type = HEAVY_MIDROUND + jobban_flag = ROLE_ALIEN + ruleset_flags = RULESET_INVADER + weight = 0 + min_pop = 20 + max_antag_cap = 1 + signup_atom_appearance = /mob/living/basic/demon/slaughter + +/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/can_be_selected() + return ..() && !isnull(find_space_spawn()) + +/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/create_ruleset_body() + var/turf/spawnloc = find_space_spawn() + . = new /mob/living/basic/demon/slaughter(spawnloc) + new /obj/effect/dummy/phased_mob/blood(spawnloc, .) + +/datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/assign_role(datum/mind/candidate) + return // handled by new() entirely + +/datum/dynamic_ruleset/midround/from_living + min_antag_cap = 1 + max_antag_cap = 1 + repeatable = TRUE + +/datum/dynamic_ruleset/midround/from_living/set_config_value(nvar, nval) + if(nvar == NAMEOF(src, min_antag_cap) || nvar == NAMEOF(src, max_antag_cap)) + return FALSE + return ..() + +/datum/dynamic_ruleset/midround/from_living/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, min_antag_cap) || var_name == NAMEOF(src, max_antag_cap)) + return FALSE + return ..() + +/datum/dynamic_ruleset/midround/from_living/collect_candidates() + return GLOB.alive_player_list + +/datum/dynamic_ruleset/midround/from_living/is_valid_candidate(mob/candidate, client/candidate_client) + if(candidate.stat == DEAD || isnull(candidate.mind)) + return FALSE + // only pick members of the crew + if(!job_check(candidate)) + return FALSE + if(!antag_check(candidate)) + return FALSE + // checks for stuff like bitrunner avatars and ghost mafia + if(HAS_TRAIT(candidate, TRAIT_MIND_TEMPORARILY_GONE) || HAS_TRAIT(candidate, TRAIT_TEMPORARY_BODY)) + return FALSE + if(SEND_SIGNAL(candidate, COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL, src, pref_flag) & CANCEL_ROLL) + return FALSE + return ..() + +/// Checks if the candidate is a valid job for this ruleset - by default you probably only want crew members. (Return FALSE to mark the candidate invalid) +/datum/dynamic_ruleset/midround/from_living/proc/job_check(mob/candidate) + if(!(candidate.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) + return FALSE + if(candidate.mind.assigned_role.title in get_blacklisted_roles()) + return FALSE + return TRUE + +/// Checks if the candidate is an antag - most of the time you don't want to double dip. (Return FALSE to mark the candidate invalid) +/datum/dynamic_ruleset/midround/from_living/proc/antag_check(mob/candidate) + return !candidate.is_antag() + +/datum/dynamic_ruleset/midround/from_living/traitor + name = "Traitor" + config_tag = "Midround Traitor" + preview_antag_datum = /datum/antagonist/traitor + midround_type = LIGHT_MIDROUND + false_alarm_able = TRUE + pref_flag = ROLE_SLEEPER_AGENT + jobban_flag = ROLE_TRAITOR + weight = 10 + min_pop = 3 + blacklisted_roles = list( + JOB_HEAD_OF_PERSONNEL, + ) + +/datum/dynamic_ruleset/midround/from_living/traitor/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/traitor) + +/datum/dynamic_ruleset/midround/from_living/traitor/false_alarm() + priority_announce( + "Attention crew, it appears that someone on your station has hijacked your telecommunications and broadcasted an unknown signal.", + "[command_name()] High-Priority Update", + ) + +/datum/dynamic_ruleset/midround/from_living/malf_ai + name = "Malfunctioning AI" + config_tag = "Midround Malfunctioning AI" + preview_antag_datum = /datum/antagonist/malf_ai + midround_type = HEAVY_MIDROUND + pref_flag = ROLE_MALF_MIDROUND + jobban_flag = ROLE_MALF + ruleset_flags = RULESET_HIGH_IMPACT + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 30 + repeatable = FALSE + +/datum/dynamic_ruleset/midround/from_living/malf_ai/get_always_blacklisted_roles() + return list() + +/datum/dynamic_ruleset/midround/from_living/malf_ai/job_check(mob/candidate) + return istype(candidate.mind.assigned_role, /datum/job/ai) + +/datum/dynamic_ruleset/midround/from_living/malf_ai/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/malf_ai) + +/datum/dynamic_ruleset/midround/from_living/malf_ai/can_be_selected() + return ..() && !HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI) + +/datum/dynamic_ruleset/midround/from_living/blob + name = "Blob Infection" + config_tag = "Blob Infection" + preview_antag_datum = /datum/antagonist/blob/infection + midround_type = HEAVY_MIDROUND + pref_flag = ROLE_BLOB_INFECTION + jobban_flag = ROLE_BLOB + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 30 + repeatable_weight_decrease = 3 + +/datum/dynamic_ruleset/midround/from_living/blob/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/blob/infection) + notify_ghosts( + "[candidate.current.real_name] has become a blob host!", + source = candidate.current, + header = "So Bulbous...", + ) + +/datum/dynamic_ruleset/midround/from_living/obsesed + name = "Obsession" + config_tag = "Midround Obsessed" + preview_antag_datum = /datum/antagonist/obsessed + midround_type = LIGHT_MIDROUND + pref_flag = ROLE_OBSESSED + blacklisted_roles = list() + weight = list( + DYNAMIC_TIER_LOW = 5, + DYNAMIC_TIER_LOWMEDIUM = 5, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 1, + ) + min_pop = 5 + +/datum/dynamic_ruleset/midround/from_living/obsesed/is_valid_candidate(mob/candidate, client/candidate_client) + return ..() && !!candidate.get_organ_by_type(/obj/item/organ/brain) + +/datum/dynamic_ruleset/midround/from_living/obsesed/antag_check(mob/candidate) + // Obsessed is a special case, it can select other antag players + return !candidate.mind.has_antag_datum(/datum/antagonist/obsessed) + +/datum/dynamic_ruleset/midround/from_living/obsesed/assign_role(datum/mind/candidate) + var/obj/item/organ/brain/brain = candidate.current.get_organ_by_type(__IMPLIED_TYPE__) + brain.brain_gain_trauma(/datum/brain_trauma/special/obsessed) + notify_ghosts( + "[candidate.current.real_name] has developed an obsession with someone!", + source = candidate.current, + header = "Love Can Bloom", + ) diff --git a/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm b/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm new file mode 100644 index 000000000000..1bdfe9476154 --- /dev/null +++ b/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm @@ -0,0 +1,433 @@ +/datum/dynamic_ruleset/roundstart + // We can pick multiple of a roundstart ruleset to "scale up" (spawn more of the same type of antag) + // Set this to FALSE if you DON'T want this ruleset to "scale up" + repeatable = TRUE + /// If TRUE, the ruleset will be the only one selected for roundstart + var/solo = FALSE + +/datum/dynamic_ruleset/roundstart/is_valid_candidate(mob/candidate, client/candidate_client) + if(isnull(candidate.mind)) + return FALSE + // Checks that any other roundstart ruleset hasn't already picked this guy + for(var/datum/dynamic_ruleset/roundstart/ruleset as anything in SSdynamic.queued_rulesets) + if(candidate.mind in ruleset.selected_minds) + return FALSE + return ..() + +/// Helpful proc - to use if your ruleset forces a job - which ensures a candidate can play the passed job typepath +/datum/dynamic_ruleset/roundstart/proc/ruleset_forced_job_check(mob/candidate, client/candidate_client, datum/job/job_typepath) + // Malf AI can only go to people who want to be AI + if(!candidate_client.prefs.job_preferences[job_typepath::title]) + return FALSE + // And only to people who can actually be AI this round + if(SSjob.check_job_eligibility(candidate, SSjob.get_job_type(job_typepath), "[name] Candidacy") != JOB_AVAILABLE) + return FALSE + // (Something else forced us to play a job that isn't AI) + var/forced_job = LAZYACCESS(SSjob.forced_occupations, candidate) + if(forced_job && forced_job != job_typepath) + return FALSE + // (Something else forced us NOT to play AI) + if(job_typepath::title in LAZYACCESS(SSjob.prevented_occupations, candidate)) + return FALSE + return TRUE + +/datum/dynamic_ruleset/roundstart/traitor + name = "Traitors" + config_tag = "Roundstart Traitor" + preview_antag_datum = /datum/antagonist/traitor + pref_flag = ROLE_TRAITOR + weight = 10 + min_pop = 3 + max_antag_cap = list("denominator" = 38) + +/datum/dynamic_ruleset/roundstart/traitor/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/traitor) + +/datum/dynamic_ruleset/roundstart/malf_ai + name = "Malfunctioning AI" + config_tag = "Roundstart Malfunctioning AI" + pref_flag = ROLE_MALF + preview_antag_datum = /datum/antagonist/malf_ai + ruleset_flags = RULESET_HIGH_IMPACT + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 30 + max_antag_cap = 1 + repeatable = FALSE + +/datum/dynamic_ruleset/roundstart/malf_ai/get_always_blacklisted_roles() + return list() + +/datum/dynamic_ruleset/roundstart/malf_ai/is_valid_candidate(mob/candidate, client/candidate_client) + return ..() && ruleset_forced_job_check(candidate, candidate_client, /datum/job/ai) + +/datum/dynamic_ruleset/roundstart/malf_ai/prepare_for_role(datum/mind/candidate) + LAZYSET(SSjob.forced_occupations, candidate, /datum/job/ai) + +/datum/dynamic_ruleset/roundstart/malf_ai/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/malf_ai) + +/datum/dynamic_ruleset/roundstart/malf_ai/can_be_selected() + return ..() && !HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI) + +/datum/dynamic_ruleset/roundstart/blood_brother + name = "Blood Brothers" + config_tag = "Roundstart Blood Brothers" + preview_antag_datum = /datum/antagonist/brother + pref_flag = ROLE_BROTHER + weight = 5 + max_antag_cap = list("denominator" = 29) + min_pop = 10 + +/datum/dynamic_ruleset/roundstart/blood_brother/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/brother) + +/datum/dynamic_ruleset/roundstart/changeling + name = "Changelings" + config_tag = "Roundstart Changeling" + preview_antag_datum = /datum/antagonist/changeling + pref_flag = ROLE_CHANGELING + weight = 3 + min_pop = 15 + max_antag_cap = list("denominator" = 29) + +/datum/dynamic_ruleset/roundstart/changeling/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/changeling) + +/datum/dynamic_ruleset/roundstart/heretic + name = "Heretics" + config_tag = "Roundstart Heretics" + preview_antag_datum = /datum/antagonist/heretic + pref_flag = ROLE_HERETIC + weight = 3 + max_antag_cap = list("denominator" = 24) + min_pop = 30 // Ensures good spread of sacrifice targets + +/datum/dynamic_ruleset/roundstart/heretic/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/heretic) + +/datum/dynamic_ruleset/roundstart/wizard + name = "Wizard" + config_tag = "Roundstart Wizard" + preview_antag_datum = /datum/antagonist/wizard + pref_flag = ROLE_WIZARD + ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 0, + DYNAMIC_TIER_MEDIUMHIGH = 1, + DYNAMIC_TIER_HIGH = 2, + ) + max_antag_cap = 1 + min_pop = 30 + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN) + repeatable = FALSE + +/datum/dynamic_ruleset/roundstart/wizard/prepare_for_role(datum/mind/candidate) + LAZYSET(SSjob.forced_occupations, candidate, /datum/job/space_wizard) + +/datum/dynamic_ruleset/roundstart/wizard/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/wizard) // moves to lair for us + +/datum/dynamic_ruleset/roundstart/wizard/round_result() + for(var/datum/mind/wiz as anything in selected_minds) + if(considered_alive(wiz) && !considered_exiled(wiz)) + return FALSE + + SSticker.news_report = WIZARD_KILLED + return TRUE + +/datum/dynamic_ruleset/roundstart/blood_cult + name = "Blood Cult" + config_tag = "Roundstart Blood Cult" + preview_antag_datum = /datum/antagonist/cult + pref_flag = ROLE_CULTIST + ruleset_flags = RULESET_HIGH_IMPACT + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 30 + blacklisted_roles = list( + JOB_HEAD_OF_PERSONNEL, + ) + min_antag_cap = list("denominator" = 20, "offset" = 1) + repeatable = FALSE + /// Ratio of cultists getting on the shuttle to be considered a minor win + var/ratio_to_be_considered_escaped = 0.5 + +/datum/dynamic_ruleset/roundstart/blood_cult/get_always_blacklisted_roles() + return ..() | JOB_CHAPLAIN + +/datum/dynamic_ruleset/roundstart/blood_cult/create_execute_args() + return list( + new /datum/team/cult(), + get_most_experienced(selected_minds, pref_flag), + ) + +/datum/dynamic_ruleset/roundstart/blood_cult/execute() + . = ..() + // future todo, find a cleaner way to get this from execute args + var/datum/team/cult/main_cult = locate() in GLOB.antagonist_teams + main_cult.setup_objectives() + +/datum/dynamic_ruleset/roundstart/blood_cult/assign_role(datum/mind/candidate, datum/team/cult/cult, datum/mind/most_experienced) + var/datum/antagonist/cult/cultist = new() + cultist.give_equipment = TRUE + candidate.add_antag_datum(cultist, cult) + if(most_experienced == candidate) + cultist.make_cult_leader() + +/datum/dynamic_ruleset/roundstart/blood_cult/round_result() + var/datum/team/cult/main_cult = locate() in GLOB.antagonist_teams + if(main_cult.check_cult_victory()) + SSticker.mode_result = "win - cult win" + SSticker.news_report = CULT_SUMMON + return TRUE + + var/num_cultists = main_cult.size_at_maximum || 100 + var/ratio_to_be_considered_escaped = 0.5 + var/escaped_cultists = 0 + for(var/datum/mind/escapee as anything in main_cult.members) + if(considered_escaped(escapee)) + escaped_cultists++ + + SSticker.mode_result = "loss - staff stopped the cult" + SSticker.news_report = (escaped_cultists / num_cultists) >= ratio_to_be_considered_escaped ? CULT_ESCAPE : CULT_FAILURE + return TRUE + +/datum/dynamic_ruleset/roundstart/nukies + name = "Nuclear Operatives" + config_tag = "Roundstart Nukeops" + preview_antag_datum = /datum/antagonist/nukeop + pref_flag = ROLE_OPERATIVE + ruleset_flags = RULESET_INVADER|RULESET_HIGH_IMPACT + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 30 + min_antag_cap = list("denominator" = 18, "offset" = 1) + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE) + repeatable = FALSE + +/datum/dynamic_ruleset/roundstart/nukies/prepare_for_role(datum/mind/candidate) + LAZYSET(SSjob.forced_occupations, candidate, /datum/job/nuclear_operative) + +/datum/dynamic_ruleset/roundstart/nukies/create_execute_args() + return list( + new /datum/team/nuclear(), + get_most_experienced(selected_minds, pref_flag), + ) + +/datum/dynamic_ruleset/roundstart/nukies/assign_role(datum/mind/candidate, datum/team/nuke_team, datum/mind/most_experienced) + if(most_experienced == candidate) + candidate.add_antag_datum(/datum/antagonist/nukeop/leader, nuke_team) + else + candidate.add_antag_datum(/datum/antagonist/nukeop, nuke_team) + +/datum/dynamic_ruleset/roundstart/nukies/round_result() + var/datum/antagonist/nukeop/nukie = selected_minds[1].has_antag_datum(/datum/antagonist/nukeop) + var/datum/team/nuclear/nuke_team = nukie.get_team() + var/result = nuke_team.get_result() + switch(result) + if(NUKE_RESULT_FLUKE) + SSticker.mode_result = "loss - syndicate nuked - disk secured" + SSticker.news_report = NUKE_SYNDICATE_BASE + if(NUKE_RESULT_NUKE_WIN) + SSticker.mode_result = "win - syndicate nuke" + SSticker.news_report = STATION_DESTROYED_NUKE + if(NUKE_RESULT_NOSURVIVORS) + SSticker.mode_result = "halfwin - syndicate nuke - did not evacuate in time" + SSticker.news_report = STATION_DESTROYED_NUKE + if(NUKE_RESULT_WRONG_STATION) + SSticker.mode_result = "halfwin - blew wrong station" + SSticker.news_report = NUKE_MISS + if(NUKE_RESULT_WRONG_STATION_DEAD) + SSticker.mode_result = "halfwin - blew wrong station - did not evacuate in time" + SSticker.news_report = NUKE_MISS + if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD) + SSticker.mode_result = "loss - evacuation - disk secured - syndi team dead" + SSticker.news_report = OPERATIVES_KILLED + if(NUKE_RESULT_CREW_WIN) + SSticker.mode_result = "loss - evacuation - disk secured" + SSticker.news_report = OPERATIVES_KILLED + if(NUKE_RESULT_DISK_LOST) + SSticker.mode_result = "halfwin - evacuation - disk not secured" + SSticker.news_report = OPERATIVE_SKIRMISH + if(NUKE_RESULT_DISK_STOLEN) + SSticker.mode_result = "halfwin - detonation averted" + SSticker.news_report = OPERATIVE_SKIRMISH + else + SSticker.mode_result = "halfwin - interrupted" + SSticker.news_report = OPERATIVE_SKIRMISH + +/datum/dynamic_ruleset/roundstart/nukies/clown + name = "Clown Operatives" + config_tag = "Roundstart Clownops" + preview_antag_datum = /datum/antagonist/nukeop/clownop + pref_flag = ROLE_CLOWN_OPERATIVE + weight = 0 + +/datum/dynamic_ruleset/roundstart/nukies/clown/prepare_for_role(datum/mind/candidate) + LAZYSET(SSjob.forced_occupations, candidate, /datum/job/nuclear_operative/clown_operative) + +/datum/dynamic_ruleset/roundstart/nukies/clown/assign_role(datum/mind/candidate, datum/team/nuke_team, datum/mind/most_experienced) + if(most_experienced == candidate) + candidate.add_antag_datum(/datum/antagonist/nukeop/leader/clownop) + else + candidate.add_antag_datum(/datum/antagonist/nukeop/clownop) + +/datum/dynamic_ruleset/roundstart/revolution + name = "Revolution" + config_tag = "Roundstart Revolution" + preview_antag_datum = /datum/antagonist/rev/head + pref_flag = ROLE_REV_HEAD + ruleset_flags = RULESET_HIGH_IMPACT + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 30 + min_antag_cap = 1 + max_antag_cap = 3 + repeatable = FALSE + /// If we have fewer heads of staff than this 7 minutes into the round, we'll cancel the revolution + var/heads_necessary = 2 + +/datum/dynamic_ruleset/roundstart/revolution/get_always_blacklisted_roles() + . = ..() + for(var/datum/job/job as anything in SSjob.all_occupations) + if(job.job_flags & JOB_HEAD_OF_STAFF) + . |= job.title + +/datum/dynamic_ruleset/roundstart/revolution/assign_role(datum/mind/candidate) + LAZYADD(candidate.special_roles, "Dormant Head Revolutionary") + addtimer(CALLBACK(src, PROC_REF(reveal_head), candidate), 7 MINUTES, TIMER_DELETE_ME) + +/// Reveals the headrev after a set amount of time +/datum/dynamic_ruleset/roundstart/revolution/proc/reveal_head(datum/mind/candidate) + LAZYREMOVE(candidate.special_roles, "Dormant Head Revolutionary") + + var/head_check = 0 + for(var/mob/player as anything in get_active_player_list(alive_check = TRUE, afk_check = TRUE)) + if(player.mind?.assigned_role.job_flags & JOB_HEAD_OF_STAFF) + head_check++ + + if(head_check < heads_necessary) + log_dynamic("[config_tag]: Not enough heads of staff were present to start a revolution.") + addtimer(CALLBACK(src, PROC_REF(revs_execution_failed)), 1 MINUTES, TIMER_UNIQUE|TIMER_DELETE_ME) + return + + if(!can_be_headrev(candidate)) + log_dynamic("[config_tag]: [key_name(candidate)] was not eligible to be a headrev after the timer expired - finding a replacement.") + find_another_headrev() + return + + GLOB.revolution_handler ||= new() + var/datum/antagonist/rev/head/new_head = new() + new_head.give_flash = TRUE + new_head.give_hud = TRUE + new_head.remove_clumsy = TRUE + candidate.add_antag_datum(new_head, GLOB.revolution_handler.revs) + GLOB.revolution_handler.start_revolution() + +/datum/dynamic_ruleset/roundstart/revolution/proc/find_another_headrev() + for(var/mob/living/carbon/human/upstanding_citizen in GLOB.player_list) + if(!can_be_headrev(upstanding_citizen.mind)) + continue + reveal_head(upstanding_citizen.mind) + log_dynamic("[config_tag]: [key_name(upstanding_citizen)] was selected as a replacement headrev.") + return + + log_dynamic("[config_tag]: Failed to find a replacement headrev.") + addtimer(CALLBACK(src, PROC_REF(revs_execution_failed)), 1 MINUTES, TIMER_UNIQUE|TIMER_DELETE_ME) + +/datum/dynamic_ruleset/roundstart/revolution/proc/revs_execution_failed() + if(GLOB.revolution_handler) + return + // Execution is effectively cancelled by this point, but it's not like we can go back and refund it + SSdynamic.unreported_rulesets += src + name += " (Canceled)" + log_dynamic("[config_tag]: All headrevs were ineligible after the timer expired, and no replacements could be found. Ruleset canceled.") + message_admins("[config_tag]: All headrevs were ineligible after the timer expired, and no replacements could be found. Ruleset canceled.") + +/datum/dynamic_ruleset/roundstart/spies + name = "Spies" + config_tag = "Roundstart Spies" + preview_antag_datum = /datum/antagonist/spy + pref_flag = ROLE_SPY + weight = list( + DYNAMIC_TIER_LOW = 0, + DYNAMIC_TIER_LOWMEDIUM = 1, + DYNAMIC_TIER_MEDIUMHIGH = 3, + DYNAMIC_TIER_HIGH = 3, + ) + min_pop = 10 + min_antag_cap = list("denominator" = 20, "offset" = 1) + +/datum/dynamic_ruleset/roundstart/spies/assign_role(datum/mind/candidate) + candidate.add_antag_datum(/datum/antagonist/spy) + +/datum/dynamic_ruleset/roundstart/extended + name = "Extended" + config_tag = "Extended" + weight = 0 + min_antag_cap = 0 + repeatable = FALSE + solo = TRUE + +/datum/dynamic_ruleset/roundstart/extended/execute() + // No midrounds no latejoins + for(var/category in SSdynamic.rulesets_to_spawn) + SSdynamic.rulesets_to_spawn[category] = 0 + +/datum/dynamic_ruleset/roundstart/meteor + name = "Meteor" + config_tag = "Meteor" + weight = 0 + min_antag_cap = 0 + repeatable = FALSE + +/datum/dynamic_ruleset/roundstart/meteor/execute() + GLOB.meteor_mode ||= new() + GLOB.meteor_mode.start_meteor() + +/datum/dynamic_ruleset/roundstart/nations + name = "Nations" + config_tag = "Nations" + weight = 0 + min_antag_cap = 0 + repeatable = FALSE + solo = TRUE + +/datum/dynamic_ruleset/roundstart/nations/execute() + // No midrounds no latejoins + for(var/category in SSdynamic.rulesets_to_spawn) + SSdynamic.rulesets_to_spawn[category] = 0 + + //notably assistant is not in this list to prevent the round turning into BARBARISM instantly, and silicon is in this list for UN + var/list/department_types = list( + /datum/job_department/silicon, //united nations + /datum/job_department/cargo, + /datum/job_department/engineering, + /datum/job_department/medical, + /datum/job_department/science, + /datum/job_department/security, + /datum/job_department/service, + ) + + for(var/department_type in department_types) + create_separatist_nation(department_type, announcement = FALSE, dangerous = FALSE, message_admins = FALSE) + + GLOB.round_default_lawset = /datum/ai_laws/united_nations diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets.dm deleted file mode 100644 index b5f84176d594..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets.dm +++ /dev/null @@ -1,285 +0,0 @@ -/datum/dynamic_ruleset - /// For admin logging and round end screen. - // If you want to change this variable name, the force latejoin/midround rulesets - // to not use sort_names. - var/name = "" - /// For admin logging and round end screen, do not change this unless making a new rule type. - var/ruletype = "" - /// If set to TRUE, the rule won't be discarded after being executed, and dynamic will call rule_process() every time it ticks. - var/persistent = FALSE - /// If set to TRUE, dynamic will be able to draft this ruleset again later on. (doesn't apply for roundstart rules) - var/repeatable = FALSE - /// If set higher than 0 decreases weight by itself causing the ruleset to appear less often the more it is repeated. - var/repeatable_weight_decrease = 2 - /// List of players that are being drafted for this rule - var/list/mob/candidates = list() - /// List of players that were selected for this rule. This can be minds, or mobs. - var/list/assigned = list() - /// Preferences flag such as ROLE_WIZARD that need to be turned on for players to be antag. - var/antag_flag = null - /// The antagonist datum that is assigned to the mobs mind on ruleset execution. - var/datum/antagonist/antag_datum = null - /// The required minimum account age for this ruleset. - var/minimum_required_age = 7 - /// If set, and config flag protect_roles_from_antagonist is false, then the rule will not pick players from these roles. - var/list/protected_roles = list() - /// If set, rule will deny candidates from those roles always. - var/list/restricted_roles = list() - /// If set, rule will only accept candidates from those roles. If on a roundstart ruleset, requires the player to have the correct antag pref enabled and any of the possible roles enabled. - var/list/exclusive_roles = list() - /// If set, there needs to be a certain amount of players doing those roles (among the players who won't be drafted) for the rule to be drafted IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS. - var/list/enemy_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - /// If enemy_roles was set, this is the amount of enemy job workers needed per threat_level range (0-10,10-20,etc) IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS. - var/required_enemies = list(1,1,0,0,0,0,0,0,0,0) - /// The rule needs this many candidates (post-trimming) to be executed (example: Cult needs 4 players at round start) - var/required_candidates = 0 - /// 0 -> 9, probability for this rule to be picked against other rules. If zero this will effectively disable the rule. - var/weight = 5 - /// Threat cost for this rule, this is decreased from the threat level when the rule is executed. - var/cost = 0 - /// Cost per level the rule scales up. - var/scaling_cost = 0 - /// How many times a rule has scaled up upon getting picked. - var/scaled_times = 0 - /// Used for the roundend report - var/total_cost = 0 - /// A flag that determines how the ruleset is handled. Check __DEFINES/dynamic.dm for an explanation of the accepted values. - var/flags = NONE - /// Pop range per requirement. If zero defaults to dynamic's pop_per_requirement. - var/pop_per_requirement = 0 - /// Requirements are the threat level requirements per pop range. - /// With the default values, The rule will never get drafted below 10 threat level (aka: "peaceful extended"), and it requires a higher threat level at lower pops. - var/list/requirements = list(40,30,20,10,10,10,10,10,10,10) - /// If a role is to be considered another for the purpose of banning. - var/antag_flag_override = null - /// If set, will check this preference instead of antag_flag. - var/antag_preference = null - /// If a ruleset type which is in this list has been executed, then the ruleset will not be executed. - var/list/blocking_rules = list() - /// The minimum amount of players required for the rule to be considered. - var/minimum_players = 0 - /// The maximum amount of players required for the rule to be considered. - /// Anything below zero or exactly zero is ignored. - var/maximum_players = 0 - /// Calculated during acceptable(), used in scaling and team sizes. - var/indice_pop = 0 - /// Base probability used in scaling. The higher it is, the more likely to scale. Kept as a var to allow for config editing._SendSignal(sigtype, list/arguments) - var/base_prob = 60 - /// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin). - /// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure. - var/delay = 0 - - /// Judges the amount of antagonists to apply, for both solo and teams. - /// Note that some antagonists (such as traitors, lings, heretics, etc) will add more based on how many times they've been scaled. - /// Written as a linear equation--ceil(x/denominator) + offset, or as a fixed constant. - /// If written as a linear equation, will be in the form of `list("denominator" = denominator, "offset" = offset). - var/antag_cap = 0 - - /// A list, or null, of templates that the ruleset depends on to function correctly - var/list/ruleset_lazy_templates - -/datum/dynamic_ruleset/New() - // Rulesets can be instantiated more than once, such as when an admin clicks - // "Execute Midround Ruleset". Thus, it would be wrong to perform any - // side effects here. Dynamic rulesets should be stateless anyway. - SHOULD_NOT_OVERRIDE(TRUE) - - ..() - -/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart - ruletype = ROUNDSTART_RULESET - -// Can be drafted when a player joins the server -/datum/dynamic_ruleset/latejoin - ruletype = LATEJOIN_RULESET - -/// By default, a rule is acceptable if it satisfies the threat level/population requirements. -/// If your rule has extra checks, such as counting security officers, do that in ready() instead -/datum/dynamic_ruleset/proc/acceptable(population = 0, threat_level = 0) - var/ruleset_forced = GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED - if (ruleset_forced != RULESET_NOT_FORCED) - if (ruleset_forced == RULESET_FORCE_ENABLED) - return TRUE - else - log_dynamic("FAIL: [src] was disabled in admin panel.") - return FALSE - - if(!is_valid_population(population)) - var/range = maximum_players > 0 ? "([minimum_players] - [maximum_players])" : "(minimum: [minimum_players])" - log_dynamic("FAIL: [src] failed acceptable: min/max players out of range [range] vs population ([population])") - return FALSE - - if (!is_valid_threat(population, threat_level)) - log_dynamic("FAIL: [src] failed acceptable: threat_level ([threat_level]) < requirement ([requirements[indice_pop]])") - return FALSE - - return TRUE - -/// Returns true if we have enough players to run -/datum/dynamic_ruleset/proc/is_valid_population(population) - if(minimum_players > population) - return FALSE - if(maximum_players > 0 && population > maximum_players) - return FALSE - return TRUE - -/// Sets the current threat indices and returns true if we're inside of them -/datum/dynamic_ruleset/proc/is_valid_threat(population, threat_level) - pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : SSdynamic.pop_per_requirement - indice_pop = min(requirements.len,round(population/pop_per_requirement)+1) - return threat_level >= requirements[indice_pop] - -/// When picking rulesets, if dynamic picks the same one multiple times, it will "scale up". -/// However, doing this blindly would result in lowpop rounds (think under 10 people) where over 80% of the crew is antags! -/// This function is here to ensure the antag ratio is kept under control while scaling up. -/// Returns how much threat to actually spend in the end. -/datum/dynamic_ruleset/proc/scale_up(population, max_scale) - if (!scaling_cost) - return 0 - - var/antag_fraction = 0 - for(var/_ruleset in (SSdynamic.executed_rules + list(src))) // we care about the antags we *will* assign, too - var/datum/dynamic_ruleset/ruleset = _ruleset - antag_fraction += ((1 + ruleset.scaled_times) * ruleset.get_antag_cap(population)) / SSdynamic.roundstart_pop_ready - - for(var/i in 1 to max_scale) - if(antag_fraction < 0.25) - scaled_times += 1 - antag_fraction += get_antag_cap(population) / SSdynamic.roundstart_pop_ready // we added new antags, gotta update the % - - return scaled_times * scaling_cost - -/// Returns what the antag cap with the given population is. -/datum/dynamic_ruleset/proc/get_antag_cap(population) - if (isnum(antag_cap)) - return antag_cap - - return CEILING(population / antag_cap["denominator"], 1) + (antag_cap["offset"] || 0) - -/// This is called if persistent variable is true everytime SSTicker ticks. -/datum/dynamic_ruleset/proc/rule_process() - return - -/// Called on pre_setup for roundstart rulesets. -/// Do everything you need to do before job is assigned here. -/// IMPORTANT: ASSIGN special_role HERE -/datum/dynamic_ruleset/proc/pre_execute() - return TRUE - -/// Called on post_setup on roundstart and when the rule executes on midround and latejoin. -/// Give your candidates or assignees equipment and antag datum here. -/datum/dynamic_ruleset/proc/execute() - for(var/datum/mind/M in assigned) - M.add_antag_datum(antag_datum) - GLOB.pre_setup_antags -= M - return TRUE - -/// Rulesets can be reused, so when we're done setting one up we want to wipe its memory of the people it was selecting over -/// This isn't Destroy we aren't deleting it here, rulesets free when nothing holds a ref. This is just to prevent hung refs. -/datum/dynamic_ruleset/proc/forget_startup() - SHOULD_CALL_PARENT(TRUE) - candidates = list() - assigned = list() - antag_datum = null - -/// Here you can perform any additional checks you want. (such as checking the map etc) -/// Remember that on roundstart no one knows what their job is at this point. -/// IMPORTANT: If ready() returns TRUE, that means pre_execute() or execute() should never fail! -/datum/dynamic_ruleset/proc/ready(forced = 0) - return check_candidates() - -/// This should always be called before ready is, to ensure that the ruleset can locate map/template based landmarks as needed -/datum/dynamic_ruleset/proc/load_templates() - for(var/template in ruleset_lazy_templates) - SSmapping.lazy_load_template(template) - -/// Runs from gamemode process() if ruleset fails to start, like delayed rulesets not getting valid candidates. -/// This one only handles refunding the threat, override in ruleset to clean up the rest. -/datum/dynamic_ruleset/proc/clean_up() - SSdynamic.refund_threat(cost + (scaled_times * scaling_cost)) - SSdynamic.threat_log += "[worldtime2text()]: [ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]. Failed to execute." - -/// Gets weight of the ruleset -/// Note that this decreases weight if repeatable is TRUE and repeatable_weight_decrease is higher than 0 -/// Note: If you don't want repeatable rulesets to decrease their weight use the weight variable directly -/datum/dynamic_ruleset/proc/get_weight() - if(repeatable && weight > 1 && repeatable_weight_decrease > 0) - for(var/datum/dynamic_ruleset/DR in SSdynamic.executed_rules) - if(istype(DR, type)) - weight = max(weight-repeatable_weight_decrease,1) - return weight - -/// Checks if there are enough candidates to run, and logs otherwise -/datum/dynamic_ruleset/proc/check_candidates() - if (required_candidates <= candidates.len) - return TRUE - - log_dynamic("FAIL: [src] does not have enough candidates ([required_candidates] needed, [candidates.len] found)") - return FALSE - -/// Here you can remove candidates that do not meet your requirements. -/// This means if their job is not correct or they have disconnected you can remove them from candidates here. -/// Usually this does not need to be changed unless you need some specific requirements from your candidates. -/datum/dynamic_ruleset/proc/trim_candidates() - return - -/// Set mode_result and news report here. -/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET -/datum/dynamic_ruleset/proc/round_result() - -////////////////////////////////////////////// -// // -// ROUNDSTART RULESETS // -// // -////////////////////////////////////////////// - -/// Checks if candidates are connected and if they are banned or don't want to be the antagonist. -/datum/dynamic_ruleset/roundstart/trim_candidates() - for(var/mob/dead/new_player/candidate_player in candidates) - var/client/candidate_client = GET_CLIENT(candidate_player) - if (!candidate_client || !candidate_player.mind) // Are they connected? - candidates.Remove(candidate_player) - continue - - if(candidate_client.get_remaining_days(minimum_required_age) > 0) - candidates.Remove(candidate_player) - continue - - if(candidate_player.mind.special_role) // We really don't want to give antag to an antag. - candidates.Remove(candidate_player) - continue - - if (!((antag_preference || antag_flag) in candidate_client.prefs.be_special)) - candidates.Remove(candidate_player) - continue - - if (is_banned_from(candidate_player.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE))) - candidates.Remove(candidate_player) - continue - - // If this ruleset has exclusive_roles set, we want to only consider players who have those - // job prefs enabled and are eligible to play that job. Otherwise, continue as before. - if(length(exclusive_roles)) - var/exclusive_candidate = FALSE - for(var/role in exclusive_roles) - var/datum/job/job = SSjob.GetJob(role) - - if((role in candidate_client.prefs.job_preferences) && SSjob.check_job_eligibility(candidate_player, job, "Dynamic Roundstart TC", add_job_to_log = TRUE) == JOB_AVAILABLE) - exclusive_candidate = TRUE - break - - // If they didn't have any of the required job prefs enabled or were banned from all enabled prefs, - // they're not eligible for this antag type. - if(!exclusive_candidate) - candidates.Remove(candidate_player) - -/// Do your checks if the ruleset is ready to be executed here. -/// Should ignore certain checks if forced is TRUE -/datum/dynamic_ruleset/roundstart/ready(population, forced = FALSE) - return ..() diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm deleted file mode 100644 index 17b0d34a4a1d..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_latejoin.dm +++ /dev/null @@ -1,254 +0,0 @@ -////////////////////////////////////////////// -// // -// LATEJOIN RULESETS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/trim_candidates() - for(var/mob/P in candidates) - if(!P.client || !P.mind || is_unassigned_job(P.mind.assigned_role)) // Are they connected? - candidates.Remove(P) - else if (P.client.get_remaining_days(minimum_required_age) > 0) - candidates.Remove(P) - else if(P.mind.assigned_role.title in restricted_roles) // Does their job allow for it? - candidates.Remove(P) - else if((exclusive_roles.len > 0) && !(P.mind.assigned_role.title in exclusive_roles)) // Is the rule exclusive to their job? - candidates.Remove(P) - else if (!((antag_preference || antag_flag) in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE))) - candidates.Remove(P) - -/datum/dynamic_ruleset/latejoin/ready(forced = 0) - if (forced) - return ..() - - var/job_check = 0 - if (enemy_roles.len > 0) - for (var/mob/M in GLOB.alive_player_list) - if (M.stat == DEAD) - continue // Dead players cannot count as opponents - if (M.mind && (M.mind.assigned_role.title in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role.title in restricted_roles))) - job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it - - var/threat = round(SSdynamic.threat_level/10) - var/ruleset_forced = (GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED) == RULESET_FORCE_ENABLED - if (!ruleset_forced && job_check < required_enemies[threat]) - log_dynamic("FAIL: [src] is not ready, because there are not enough enemies: [required_enemies[threat]] needed, [job_check] found") - return FALSE - - return ..() - -/datum/dynamic_ruleset/latejoin/execute() - var/mob/M = pick(candidates) - assigned += M.mind - M.mind.special_role = antag_flag - M.mind.add_antag_datum(antag_datum) - return TRUE - -////////////////////////////////////////////// -// // -// SYNDICATE TRAITORS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/infiltrator - name = "Syndicate Infiltrator" - antag_datum = /datum/antagonist/traitor - antag_flag = ROLE_SYNDICATE_INFILTRATOR - antag_flag_override = ROLE_TRAITOR - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 11 - cost = 5 - requirements = list(5,5,5,5,5,5,5,5,5,5) - repeatable = TRUE - -////////////////////////////////////////////// -// // -// REVOLUTIONARY PROVOCATEUR // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/provocateur - name = "Provocateur" - persistent = TRUE - antag_datum = /datum/antagonist/rev/head - antag_flag = ROLE_PROVOCATEUR - antag_flag_override = ROLE_REV_HEAD - restricted_roles = list( - JOB_AI, - JOB_CAPTAIN, - JOB_CHIEF_ENGINEER, - JOB_CHIEF_MEDICAL_OFFICER, - JOB_CYBORG, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_QUARTERMASTER, - JOB_RESEARCH_DIRECTOR, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - enemy_roles = list( - JOB_AI, - JOB_CYBORG, - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 1 - delay = 1 MINUTES // Prevents rule start while head is offstation. - cost = 10 - requirements = list(101,101,70,40,30,20,20,20,20,20) - flags = HIGH_IMPACT_RULESET - blocking_rules = list(/datum/dynamic_ruleset/roundstart/revs) - var/required_heads_of_staff = 3 - var/finished = FALSE - var/datum/team/revolution/revolution - -/datum/dynamic_ruleset/latejoin/provocateur/ready(forced=FALSE) - if (forced) - required_heads_of_staff = 1 - if(!..()) - return FALSE - var/head_check = 0 - for(var/mob/player in GLOB.alive_player_list) - if (player.mind.assigned_role.job_flags & JOB_HEAD_OF_STAFF) - head_check++ - return (head_check >= required_heads_of_staff) - -/datum/dynamic_ruleset/latejoin/provocateur/execute() - var/mob/M = pick(candidates) // This should contain a single player, but in case. - if(check_eligible(M.mind)) // Didnt die/run off z-level/get implanted since leaving shuttle. - assigned += M.mind - M.mind.special_role = antag_flag - revolution = new() - var/datum/antagonist/rev/head/new_head = new() - new_head.give_flash = TRUE - new_head.give_hud = TRUE - new_head.remove_clumsy = TRUE - new_head = M.mind.add_antag_datum(new_head, revolution) - revolution.update_objectives() - revolution.update_rev_heads() - SSshuttle.registerHostileEnvironment(revolution) - return TRUE - else - log_dynamic("[ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.") - log_dynamic("[ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.") - return FALSE - -/datum/dynamic_ruleset/latejoin/provocateur/rule_process() - var/winner = revolution.process_victory() - if (isnull(winner)) - return - - finished = winner - - if(winner == REVOLUTION_VICTORY) - GLOB.revolutionary_win = TRUE - - return RULESET_STOP_PROCESSING - -/// Checks for revhead loss conditions and other antag datums. -/datum/dynamic_ruleset/latejoin/provocateur/proc/check_eligible(datum/mind/M) - var/turf/T = get_turf(M.current) - if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD)) - return TRUE - return FALSE - -/datum/dynamic_ruleset/latejoin/provocateur/round_result() - revolution.round_result(finished) - -////////////////////////////////////////////// -// // -// HERETIC SMUGGLER // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/heretic_smuggler - name = "Heretic Smuggler" - antag_datum = /datum/antagonist/heretic - antag_flag = ROLE_HERETIC_SMUGGLER - antag_flag_override = ROLE_HERETIC - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 8 - cost = 6 - requirements = list(101,101,50,10,10,10,10,10,10,10) - repeatable = TRUE - -/datum/dynamic_ruleset/latejoin/heretic_smuggler/execute() - var/mob/picked_mob = pick(candidates) - assigned += picked_mob.mind - picked_mob.mind.special_role = antag_flag - var/datum/antagonist/heretic/new_heretic = picked_mob.mind.add_antag_datum(antag_datum) - - // Heretics passively gain influence over time. - // As a consequence, latejoin heretics start out at a massive - // disadvantage if the round's been going on for a while. - // Let's give them some influence points when they arrive. - new_heretic.knowledge_points += round((world.time - SSticker.round_start_time) / new_heretic.passive_gain_timer) - // BUT let's not give smugglers a million points on arrival. - // Limit it to four missed passive gain cycles (4 points). - new_heretic.knowledge_points = min(new_heretic.knowledge_points, 5) - - return TRUE - -/// Ruleset for latejoin changelings -/datum/dynamic_ruleset/latejoin/stowaway_changeling - name = "Stowaway Changeling" - antag_datum = /datum/antagonist/changeling - antag_flag = ROLE_STOWAWAY_CHANGELING - antag_flag_override = ROLE_CHANGELING - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 2 - cost = 12 - requirements = list(101,101,60,50,40,20,20,10,10,10) - repeatable = TRUE - -/datum/dynamic_ruleset/latejoin/stowaway_changeling/execute() - var/mob/picked_mob = pick(candidates) - assigned += picked_mob.mind - picked_mob.mind.special_role = antag_flag - picked_mob.mind.add_antag_datum(antag_datum) - return TRUE diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm deleted file mode 100644 index b74ebe47925e..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm +++ /dev/null @@ -1,933 +0,0 @@ -/// Probability the AI going malf will be accompanied by an ion storm announcement and some ion laws. -#define MALF_ION_PROB 33 -/// The probability to replace an existing law with an ion law instead of adding a new ion law. -#define REPLACE_LAW_WITH_ION_PROB 10 - -/// Midround Rulesets -/datum/dynamic_ruleset/midround // Can be drafted once in a while during a round - ruletype = MIDROUND_RULESET - var/midround_ruleset_style - /// If the ruleset should be restricted from ghost roles. - var/restrict_ghost_roles = TRUE - /// What mob type the ruleset is restricted to. - var/required_type = /mob/living/carbon/human - var/list/living_players = list() - var/list/living_antags = list() - var/list/dead_players = list() - var/list/list_observers = list() - - /// The minimum round time before this ruleset will show up - var/minimum_round_time = 0 - /// Abstract root value - var/abstract_type = /datum/dynamic_ruleset/midround - -/datum/dynamic_ruleset/midround/forget_startup() - living_players = list() - living_antags = list() - dead_players = list() - list_observers = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts - weight = 0 - required_type = /mob/dead/observer - abstract_type = /datum/dynamic_ruleset/midround/from_ghosts - /// Whether the ruleset should call generate_ruleset_body or not. - var/makeBody = TRUE - /// The rule needs this many applicants to be properly executed. - var/required_applicants = 1 - -/datum/dynamic_ruleset/midround/from_ghosts/check_candidates() - var/dead_count = dead_players.len + list_observers.len - if (required_candidates <= dead_count) - return TRUE - - log_dynamic("FAIL: [src], a from_ghosts ruleset, did not have enough dead candidates: [required_candidates] needed, [dead_count] found") - - return FALSE - -/datum/dynamic_ruleset/midround/trim_candidates() - living_players = trim_list(GLOB.alive_player_list) - living_antags = trim_list(GLOB.current_living_antags) - dead_players = trim_list(GLOB.dead_player_list) - list_observers = trim_list(GLOB.current_observers_list) - -/datum/dynamic_ruleset/midround/proc/trim_list(list/to_trim = list()) - var/list/trimmed_list = to_trim.Copy() - for(var/mob/creature in trimmed_list) - if (!istype(creature, required_type)) - trimmed_list.Remove(creature) - continue - if (isnull(creature.client)) // Are they connected? - trimmed_list.Remove(creature) - continue - if(creature.client.get_remaining_days(minimum_required_age) > 0) - trimmed_list.Remove(creature) - continue - if (!((antag_preference || antag_flag) in creature.client.prefs.be_special)) - trimmed_list.Remove(creature) - continue - if (is_banned_from(creature.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE))) - trimmed_list.Remove(creature) - continue - - if (isnull(creature.mind)) - continue - - if (restrict_ghost_roles && (creature.mind.assigned_role.title in GLOB.exp_specialmap[EXP_TYPE_SPECIAL])) // Are they playing a ghost role? - trimmed_list.Remove(creature) - continue - if (creature.mind.assigned_role.title in restricted_roles) // Does their job allow it? - trimmed_list.Remove(creature) - continue - if (length(exclusive_roles) && !(creature.mind.assigned_role.title in exclusive_roles)) // Is the rule exclusive to their job? - trimmed_list.Remove(creature) - continue - if(HAS_TRAIT(creature, TRAIT_MIND_TEMPORARILY_GONE)) // are they out of body? - trimmed_list.Remove(creature) - continue - if(HAS_TRAIT(creature, TRAIT_TEMPORARY_BODY)) // are they an avatar? - trimmed_list.Remove(creature) - continue - return trimmed_list - -// You can then for example prompt dead players in execute() to join as strike teams or whatever -// Or autotator someone - -// IMPORTANT, since /datum/dynamic_ruleset/midround may accept candidates from both living, dead, and even antag players -// subtype your midround with /from_ghosts or /from_living to get candidate checking. Or check yourself by subtyping from neither -/datum/dynamic_ruleset/midround/ready(forced = FALSE) - if (forced) - return TRUE - - var/job_check = 0 - if (enemy_roles.len > 0) - for (var/mob/M in GLOB.alive_player_list) - if (M.stat == DEAD || !M.client) - continue // Dead/disconnected players cannot count as opponents - if (M.mind && (M.mind.assigned_role.title in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role.title in restricted_roles))) - job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it - - var/threat = round(SSdynamic.threat_level/10) - var/ruleset_forced = (GLOB.dynamic_forced_rulesets[type] || RULESET_NOT_FORCED) == RULESET_FORCE_ENABLED - if (!ruleset_forced && job_check < required_enemies[threat]) - log_dynamic("FAIL: [src] is not ready, because there are not enough enemies: [required_enemies[threat]] needed, [job_check] found") - return FALSE - - return TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/execute() - var/list/possible_candidates = list() - possible_candidates.Add(dead_players) - possible_candidates.Add(list_observers) - send_applications(possible_candidates) - if(assigned.len > 0) - return TRUE - else - return FALSE - -/// This sends a poll to ghosts if they want to be a ghost spawn from a ruleset. -/datum/dynamic_ruleset/midround/from_ghosts/proc/send_applications(list/possible_volunteers = list()) - if (possible_volunteers.len <= 0) // This shouldn't happen, as ready() should return FALSE if there is not a single valid candidate - message_admins("Possible volunteers was 0. This shouldn't appear, because of ready(), unless you forced it!") - return - - SSdynamic.log_dynamic_and_announce("Polling [possible_volunteers.len] players to apply for the [name] ruleset.") - candidates = SSpolling.poll_ghost_candidates("Looking for volunteers to become [antag_flag] for [name]", check_jobban = antag_flag_override, role = antag_flag || antag_flag_override, poll_time = 30 SECONDS, pic_source = /obj/structure/sign/poster/contraband/syndicate_recruitment, role_name_text = antag_flag) - - if(!candidates || candidates.len <= 0) - SSdynamic.log_dynamic_and_announce("The ruleset [name] received no applications.") - SSdynamic.executed_rules -= src - attempt_replacement() - return - - SSdynamic.log_dynamic_and_announce("[candidates.len] players volunteered for [name].") - review_applications() - -/// Here is where you can check if your ghost applicants are valid for the ruleset. -/// Called by send_applications(). -/datum/dynamic_ruleset/midround/from_ghosts/proc/review_applications() - if(candidates.len < required_applicants) - SSdynamic.executed_rules -= src - return - for (var/i = 1, i <= required_candidates, i++) - if(candidates.len <= 0) - break - var/mob/applicant = pick(candidates) - candidates -= applicant - if(!isobserver(applicant)) - if(applicant.stat == DEAD) // Not an observer? If they're dead, make them one. - applicant = applicant.ghostize(FALSE) - else // Not dead? Disregard them, pick a new applicant - i-- - continue - if(!applicant) - i-- - continue - assigned += applicant - finish_applications() - -/// Here the accepted applications get generated bodies and their setup is finished. -/// Called by review_applications() -/datum/dynamic_ruleset/midround/from_ghosts/proc/finish_applications() - var/i = 0 - for(var/mob/applicant as anything in assigned) - i++ - var/mob/new_character = applicant - if(makeBody) - new_character = generate_ruleset_body(applicant) - finish_setup(new_character, i) - notify_ghosts( - "[applicant.name] has been picked for the ruleset [name]!", - source = new_character, - ) - -/datum/dynamic_ruleset/midround/from_ghosts/proc/generate_ruleset_body(mob/applicant) - var/mob/living/carbon/human/new_character = make_body(applicant) - new_character.dna.remove_all_mutations() - return new_character - -/datum/dynamic_ruleset/midround/from_ghosts/proc/finish_setup(mob/new_character, index) - var/datum/antagonist/new_role = new antag_datum() - setup_role(new_role) - new_character.mind.add_antag_datum(new_role) - new_character.mind.special_role = antag_flag - -/datum/dynamic_ruleset/midround/from_ghosts/proc/setup_role(datum/antagonist/new_role) - return - -/// Fired when there are no valid candidates. Will spawn a sleeper agent or latejoin traitor. -/datum/dynamic_ruleset/midround/from_ghosts/proc/attempt_replacement() - var/datum/dynamic_ruleset/midround/from_living/autotraitor/sleeper_agent = new - - SSdynamic.configure_ruleset(sleeper_agent) - - if (!SSdynamic.picking_specific_rule(sleeper_agent)) - return - - SSdynamic.picking_specific_rule(/datum/dynamic_ruleset/latejoin/infiltrator) - -///subtype to handle checking players -/datum/dynamic_ruleset/midround/from_living - weight = 0 - abstract_type = /datum/dynamic_ruleset/midround/from_living - -/datum/dynamic_ruleset/midround/from_living/ready(forced) - if(!check_candidates()) - return FALSE - return ..() - - -/// Midround Traitor Ruleset (From Living) -/datum/dynamic_ruleset/midround/from_living/autotraitor - name = "Syndicate Sleeper Agent" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/traitor - antag_flag = ROLE_SLEEPER_AGENT - antag_flag_override = ROLE_TRAITOR - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ROLE_POSITRONIC_BRAIN, - ) - required_candidates = 1 - weight = 35 - cost = 3 - requirements = list(3,3,3,3,3,3,3,3,3,3) - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_living/autotraitor/trim_candidates() - ..() - candidates = living_players - for(var/mob/living/player in candidates) - if(issilicon(player)) // Your assigned role doesn't change when you are turned into a silicon. - candidates -= player - else if(is_centcom_level(player.z)) - candidates -= player // We don't autotator people in CentCom - else if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0)) - candidates -= player // We don't autotator people with roles already - -/datum/dynamic_ruleset/midround/from_living/autotraitor/execute() - var/mob/M = pick(candidates) - assigned += M - candidates -= M - var/datum/antagonist/traitor/newTraitor = new - M.mind.add_antag_datum(newTraitor) - message_admins("[ADMIN_LOOKUPFLW(M)] was selected by the [name] ruleset and has been made into a midround traitor.") - log_dynamic("[key_name(M)] was selected by the [name] ruleset and has been made into a midround traitor.") - return TRUE - -/// Midround Malf AI Ruleset (From Living) -/datum/dynamic_ruleset/midround/malf - name = "Malfunctioning AI" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/malf_ai - antag_flag = ROLE_MALF_MIDROUND - antag_flag_override = ROLE_MALF - enemy_roles = list( - JOB_CHEMIST, - JOB_CHIEF_ENGINEER, - JOB_HEAD_OF_SECURITY, - JOB_RESEARCH_DIRECTOR, - JOB_SCIENTIST, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - exclusive_roles = list(JOB_AI) - required_enemies = list(4,4,4,4,4,4,2,2,2,0) - required_candidates = 1 - minimum_players = 25 - weight = 2 - cost = 10 - required_type = /mob/living/silicon/ai - blocking_rules = list(/datum/dynamic_ruleset/roundstart/malf_ai) - -/datum/dynamic_ruleset/midround/malf/trim_candidates() - ..() - candidates = living_players - for(var/mob/living/player in candidates) - if(!isAI(player)) - candidates -= player - continue - - if(is_centcom_level(player.z)) - candidates -= player - continue - - if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0)) - candidates -= player - -/datum/dynamic_ruleset/midround/malf/execute() - if(!candidates || !candidates.len) - return FALSE - var/mob/living/silicon/ai/new_malf_ai = pick_n_take(candidates) - assigned += new_malf_ai.mind - var/datum/antagonist/malf_ai/malf_antag_datum = new - new_malf_ai.mind.special_role = antag_flag - new_malf_ai.mind.add_antag_datum(malf_antag_datum) - if(prob(MALF_ION_PROB)) - priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", ANNOUNCER_IONSTORM) - if(prob(REPLACE_LAW_WITH_ION_PROB)) - new_malf_ai.replace_random_law(generate_ion_law(), list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION), LAW_ION) - else - new_malf_ai.add_ion_law(generate_ion_law()) - return TRUE - -/// Midround Wizard Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/wizard - name = "Wizard" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/wizard - antag_flag = ROLE_WIZARD_MIDROUND - antag_flag_override = ROLE_WIZARD - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 1 - cost = 10 - requirements = REQUIREMENTS_VERY_HIGH_THREAT_NEEDED - flags = HIGH_IMPACT_RULESET - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN) - -/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE) - if(!check_candidates()) - return FALSE - if(!length(GLOB.wizardstart)) - log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") - message_admins("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/wizard/finish_setup(mob/new_character, index) - ..() - new_character.forceMove(pick(GLOB.wizardstart)) - -/// Midround Nuclear Operatives Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/nuclear - name = "Nuclear Assault" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_flag = ROLE_OPERATIVE_MIDROUND - antag_flag_override = ROLE_OPERATIVE - antag_datum = /datum/antagonist/nukeop - enemy_roles = list( - JOB_AI, - JOB_CYBORG, - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_enemies = list(3,3,3,3,3,2,1,1,0,0) - required_candidates = 5 - weight = 5 - cost = 7 - minimum_round_time = 70 MINUTES - requirements = REQUIREMENTS_VERY_HIGH_THREAT_NEEDED - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE) - flags = HIGH_IMPACT_RULESET - - var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5) - -/datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat_level=0) - if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in SSdynamic.executed_rules) - return FALSE // Unavailable if nuke ops were already sent at roundstart - indice_pop = min(operative_cap.len, round(living_players.len/5)+1) - required_candidates = operative_cap[indice_pop] - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/nuclear/ready(forced = FALSE) - if (!check_candidates()) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_applications() - var/mob/leader = get_most_experienced(assigned, ROLE_NUCLEAR_OPERATIVE) - if(leader) - assigned.Remove(leader) - assigned.Insert(1, leader) - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_setup(mob/new_character, index) - new_character.mind.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative)) - new_character.mind.special_role = ROLE_NUCLEAR_OPERATIVE - if(index == 1) - var/datum/antagonist/nukeop/leader/leader_antag_datum = new() - new_character.mind.add_antag_datum(leader_antag_datum) - return - return ..() - -/// Midround Blob Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/blob - name = "Blob" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/blob - antag_flag = ROLE_BLOB - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - minimum_round_time = 35 MINUTES - weight = 3 - cost = 8 - minimum_players = 25 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant) - var/body = applicant.become_overmind() - return body - -/// Midround Blob Infection Ruleset (From Living) -/datum/dynamic_ruleset/midround/from_living/blob_infection - name = "Blob Infection" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/blob/infection - antag_flag = ROLE_BLOB_INFECTION - antag_flag_override = ROLE_BLOB - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ROLE_POSITRONIC_BRAIN, - ) - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - minimum_round_time = 35 MINUTES - weight = 3 - cost = 10 - minimum_players = 25 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_living/blob_infection/trim_candidates() - ..() - candidates = living_players - for(var/mob/living/player as anything in candidates) - var/turf/player_turf = get_turf(player) - if(!player_turf || !is_station_level(player_turf.z)) - candidates -= player - continue - - if(player.mind && (player.mind.special_role || length(player.mind.antag_datums) > 0)) - candidates -= player - -/datum/dynamic_ruleset/midround/from_living/blob_infection/execute() - if(!candidates || !candidates.len) - return FALSE - var/mob/living/carbon/human/blob_antag = pick_n_take(candidates) - assigned += blob_antag.mind - blob_antag.mind.special_role = antag_flag - return ..() - -/// Midround Xenomorph Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/xenomorph - name = "Alien Infestation" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/xeno - antag_flag = ROLE_ALIEN - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - minimum_round_time = 40 MINUTES - weight = 5 - cost = 10 - minimum_players = 25 - repeatable = TRUE - var/list/vents = list() - -/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/forget_startup() - vents = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute() - // 50% chance of being incremented by one - required_candidates += prob(50) - var/list/vent_pumps = SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/vent_pump) - for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent as anything in vent_pumps) - if(QDELETED(temp_vent)) - continue - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] - if(!temp_vent_parent) - continue // No parent vent - // Stops Aliens getting stuck in small networks. - // See: Security, Virology - if(temp_vent_parent.other_atmos_machines.len > 20) - vents += temp_vent - if(!vents.len) - return FALSE - . = ..() - -/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/generate_ruleset_body(mob/applicant) - var/obj/vent = pick_n_take(vents) - var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) - new_xeno.key = applicant.key - new_xeno.move_into_vent(vent) - message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by the midround ruleset.") - log_dynamic("[key_name(new_xeno)] was spawned as an alien by the midround ruleset.") - return new_xeno - -/// Midround Nightmare Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/nightmare - name = "Nightmare" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/nightmare - antag_flag = ROLE_NIGHTMARE - antag_flag_override = ROLE_ALIEN - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 3 - cost = 5 - minimum_players = 15 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/nightmare/acceptable(population = 0, threat_level = 0) - var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) //Checks if there's a single safe, dark tile on station. - if(!spawn_loc) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/nightmare/generate_ruleset_body(mob/applicant) - var/datum/mind/player_mind = new /datum/mind(applicant.key) - player_mind.active = TRUE - - var/mob/living/carbon/human/new_nightmare = new (find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE)) - player_mind.transfer_to(new_nightmare) - player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/nightmare)) - player_mind.special_role = ROLE_NIGHTMARE - player_mind.add_antag_datum(/datum/antagonist/nightmare) - new_nightmare.set_species(/datum/species/shadow/nightmare) - - playsound(new_nightmare, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - message_admins("[ADMIN_LOOKUPFLW(new_nightmare)] has been made into a Nightmare by the midround ruleset.") - log_dynamic("[key_name(new_nightmare)] was spawned as a Nightmare by the midround ruleset.") - return new_nightmare - -/// Midround Space Dragon Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/space_dragon - name = "Space Dragon" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/space_dragon - antag_flag = ROLE_SPACE_DRAGON - antag_flag_override = ROLE_SPACE_DRAGON - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 4 - cost = 7 - minimum_players = 25 - repeatable = TRUE - var/list/spawn_locs = list() - -/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/forget_startup() - spawn_locs = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/execute() - for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) - spawn_locs += (C.loc) - if(!spawn_locs.len) - message_admins("No valid spawn locations found, aborting...") - return MAP_ERROR - . = ..() - -/datum/dynamic_ruleset/midround/from_ghosts/space_dragon/generate_ruleset_body(mob/applicant) - var/datum/mind/player_mind = new /datum/mind(applicant.key) - player_mind.active = TRUE - - var/mob/living/basic/space_dragon/S = new (pick(spawn_locs)) - player_mind.transfer_to(S) - player_mind.add_antag_datum(/datum/antagonist/space_dragon) - - playsound(S, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Space Dragon by the midround ruleset.") - log_dynamic("[key_name(S)] was spawned as a Space Dragon by the midround ruleset.") - priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert") - return S - -/datum/dynamic_ruleset/midround/from_ghosts/abductors - name = "Abductors" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/abductor - antag_flag = ROLE_ABDUCTOR - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 2 - required_applicants = 2 - weight = 4 - cost = 7 - minimum_players = 25 - repeatable = TRUE - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS) - - var/datum/team/abductor_team/new_team - -/datum/dynamic_ruleset/midround/from_ghosts/abductors/forget_startup() - new_team = null - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/abductors/ready(forced = FALSE) - if (required_candidates > (dead_players.len + list_observers.len)) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/abductors/finish_setup(mob/new_character, index) - if (index == 1) // Our first guy is the scientist. We also initialize the team here as well since this should only happen once per pair of abductors. - new_team = new - if(new_team.team_number > ABDUCTOR_MAX_TEAMS) - return MAP_ERROR - var/datum/antagonist/abductor/scientist/new_role = new - new_character.mind.add_antag_datum(new_role, new_team) - else // Our second guy is the agent, team is already created, don't need to make another one. - var/datum/antagonist/abductor/agent/new_role = new - new_character.mind.add_antag_datum(new_role, new_team) - -/// Midround Space Ninja Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/space_ninja - name = "Space Ninja" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/ninja - antag_flag = ROLE_NINJA - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 4 - cost = 8 - minimum_players = 30 - repeatable = TRUE - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY) // I mean, no one uses the nets anymore but whateva - - var/list/spawn_locs = list() - -/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/forget_startup() - spawn_locs = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/execute() - for(var/obj/effect/landmark/carpspawn/carp_spawn in GLOB.landmarks_list) - if(!isturf(carp_spawn.loc)) - stack_trace("Carp spawn found not on a turf: [carp_spawn.type] on [isnull(carp_spawn.loc) ? "null" : carp_spawn.loc.type]") - continue - spawn_locs += carp_spawn.loc - if(!spawn_locs.len) - message_admins("No valid spawn locations found, aborting...") - return MAP_ERROR - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/space_ninja/generate_ruleset_body(mob/applicant) - var/mob/living/carbon/human/ninja = create_space_ninja(pick(spawn_locs)) - ninja.key = applicant.key - ninja.mind.add_antag_datum(/datum/antagonist/ninja) - - message_admins("[ADMIN_LOOKUPFLW(ninja)] has been made into a Space Ninja by the midround ruleset.") - log_dynamic("[key_name(ninja)] was spawned as a Space Ninja by the midround ruleset.") - return ninja - -/// Midround Spiders Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/spiders - name = "Spiders" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_flag = ROLE_SPIDER - required_type = /mob/dead/observer - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 0 - weight = 3 - cost = 8 - minimum_players = 27 - repeatable = TRUE - var/spawncount = 2 - -/datum/dynamic_ruleset/midround/spiders/execute() - create_midwife_eggs(spawncount) - return ..() - -/// Midround Revenant Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/revenant - name = "Revenant" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/revenant - antag_flag = ROLE_REVENANT - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 4 - cost = 5 - minimum_players = 15 - repeatable = TRUE - var/dead_mobs_required = 20 - var/need_extra_spawns_value = 15 - var/list/spawn_locs = list() - -/datum/dynamic_ruleset/midround/from_ghosts/revenant/forget_startup() - spawn_locs = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/revenant/acceptable(population=0, threat_level=0) - if(GLOB.dead_mob_list.len < dead_mobs_required) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/revenant/execute() - for(var/mob/living/corpse in GLOB.dead_mob_list) //look for any dead bodies - var/turf/corpse_turf = get_turf(corpse) - if(corpse_turf && is_station_level(corpse_turf.z)) - spawn_locs += corpse_turf - if(!spawn_locs.len || spawn_locs.len < need_extra_spawns_value) //look for any morgue trays, crematoriums, ect if there weren't alot of dead bodies on the station to pick from - for(var/obj/structure/bodycontainer/corpse_container in GLOB.bodycontainers) - var/turf/container_turf = get_turf(corpse_container) - if(container_turf && is_station_level(container_turf.z)) - spawn_locs += container_turf - if(!spawn_locs.len) //If we can't find any valid spawnpoints, try the carp spawns - for(var/obj/effect/landmark/carpspawn/carp_spawnpoint in GLOB.landmarks_list) - if(isturf(carp_spawnpoint.loc)) - spawn_locs += carp_spawnpoint.loc - if(!spawn_locs.len) //If we can't find THAT, then just give up and cry - return FALSE - . = ..() - -/datum/dynamic_ruleset/midround/from_ghosts/revenant/generate_ruleset_body(mob/applicant) - var/mob/living/basic/revenant/revenant = new(pick(spawn_locs)) - revenant.key = applicant.key - message_admins("[ADMIN_LOOKUPFLW(revenant)] has been made into a revenant by the midround ruleset.") - log_game("[key_name(revenant)] was spawned as a revenant by the midround ruleset.") - return revenant - -/// Midround Sentient Disease Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease - name = "Sentient Disease" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_datum = /datum/antagonist/disease - antag_flag = ROLE_SENTIENT_DISEASE - required_candidates = 1 - minimum_players = 25 - weight = 4 - cost = 8 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/generate_ruleset_body(mob/applicant) - var/mob/camera/disease/virus = new /mob/camera/disease(SSmapping.get_station_center()) - virus.key = applicant.key - INVOKE_ASYNC(virus, TYPE_PROC_REF(/mob/camera/disease, pick_name)) - message_admins("[ADMIN_LOOKUPFLW(virus)] has been made into a sentient disease by the midround ruleset.") - log_game("[key_name(virus)] was spawned as a sentient disease by the midround ruleset.") - return virus - -/// Midround Space Pirates Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/pirates - name = "Space Pirates" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_flag = "Space Pirates" - required_type = /mob/dead/observer - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 0 - weight = 3 - cost = 8 - minimum_players = 20 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/pirates/acceptable(population=0, threat_level=0) - if (SSmapping.is_planetary() || GLOB.light_pirate_gangs.len == 0) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/pirates/execute() - send_pirate_threat(GLOB.light_pirate_gangs) - return ..() - -/// Dangerous Space Pirates ruleset -/datum/dynamic_ruleset/midround/dangerous_pirates - name = "Dangerous Space Pirates" - midround_ruleset_style = MIDROUND_RULESET_STYLE_HEAVY - antag_flag = "Space Pirates" - required_type = /mob/dead/observer - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 0 - weight = 3 - cost = 8 - minimum_players = 25 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/dangerous_pirates/acceptable(population=0, threat_level=0) - if (SSmapping.is_planetary() || GLOB.heavy_pirate_gangs.len == 0) - return FALSE - return ..() - -/datum/dynamic_ruleset/midround/dangerous_pirates/execute() - send_pirate_threat(GLOB.heavy_pirate_gangs) - return ..() - -/// Midround Obsessed Ruleset (From Living) -/datum/dynamic_ruleset/midround/from_living/obsessed - name = "Obsessed" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/obsessed - antag_flag = ROLE_OBSESSED - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ROLE_POSITRONIC_BRAIN, - ) - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 4 - cost = 3 // Doesn't have the same impact on rounds as revenants, dragons, sentient disease (10) or syndicate infiltrators (5). - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_living/obsessed/trim_candidates() - ..() - candidates = living_players - for(var/mob/living/carbon/human/candidate in candidates) - if( \ - !candidate.get_organ_by_type(/obj/item/organ/brain) \ - || candidate.mind.has_antag_datum(/datum/antagonist/obsessed) \ - || candidate.stat == DEAD \ - || !(ROLE_OBSESSED in candidate.client?.prefs?.be_special) \ - || !candidate.mind.assigned_role \ - ) - candidates -= candidate - -/datum/dynamic_ruleset/midround/from_living/obsessed/execute() - var/mob/living/carbon/human/obsessed = pick_n_take(candidates) - obsessed.gain_trauma(/datum/brain_trauma/special/obsessed) - message_admins("[ADMIN_LOOKUPFLW(obsessed)] has been made Obsessed by the midround ruleset.") - log_game("[key_name(obsessed)] was made Obsessed by the midround ruleset.") - return TRUE - -/// Midround Space Changeling Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/changeling_midround - name = "Space Changeling" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/changeling/space - antag_flag = ROLE_CHANGELING_MIDROUND - antag_flag_override = ROLE_CHANGELING - required_type = /mob/dead/observer - required_enemies = list(2,2,1,1,1,1,1,0,0,0) - required_candidates = 1 - weight = 3 - cost = 7 - minimum_players = 15 - repeatable = TRUE - -/datum/dynamic_ruleset/midround/from_ghosts/changeling_midround/generate_ruleset_body(mob/applicant) - var/body = generate_changeling_meteor(applicant) - message_admins("[ADMIN_LOOKUPFLW(body)] has been made into a space changeling by the midround ruleset.") - log_dynamic("[key_name(body)] was spawned as a space changeling by the midround ruleset.") - return body - -/// Midround Paradox Clone Ruleset (From Ghosts) -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone - name = "Paradox Clone" - midround_ruleset_style = MIDROUND_RULESET_STYLE_LIGHT - antag_datum = /datum/antagonist/paradox_clone - antag_flag = ROLE_PARADOX_CLONE - enemy_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_enemies = list(2, 2, 1, 1, 1, 1, 1, 0, 0, 0) - required_candidates = 1 - weight = 4 - cost = 3 - repeatable = TRUE - var/list/possible_spawns = list() ///places the antag can spawn - -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/forget_startup() - possible_spawns = list() - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/execute() - possible_spawns += find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) - if(!possible_spawns.len) - return MAP_ERROR - return ..() - -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/generate_ruleset_body(mob/applicant) - var/datum/mind/player_mind = new /datum/mind(applicant.key) - player_mind.active = TRUE - - var/mob/living/carbon/human/clone_victim = find_original() - var/mob/living/carbon/human/clone = clone_victim.make_full_human_copy(pick(possible_spawns)) - player_mind.transfer_to(clone) - - var/datum/antagonist/paradox_clone/new_datum = player_mind.add_antag_datum(/datum/antagonist/paradox_clone) - new_datum.original_ref = WEAKREF(clone_victim.mind) - new_datum.setup_clone() - - playsound(clone, 'sound/weapons/zapbang.ogg', 30, TRUE) - new /obj/item/storage/toolbox/mechanical(clone.loc) //so they dont get stuck in maints - - message_admins("[ADMIN_LOOKUPFLW(clone)] has been made into a Paradox Clone by the midround ruleset.") - clone.log_message("was spawned as a Paradox Clone of [key_name(clone)] by the midround ruleset.", LOG_GAME) - - return clone - -/** - * Trims through GLOB.player_list and finds a target - * Returns a single human victim, if none is possible then returns null. - */ -/datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/proc/find_original() - var/list/possible_targets = list() - - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(!player.client || !player.mind || player.stat) - continue - if(!(player.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) - continue - possible_targets += player - - if(possible_targets.len) - return pick(possible_targets) - return FALSE - -#undef MALF_ION_PROB -#undef REPLACE_LAW_WITH_ION_PROB diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm deleted file mode 100644 index 290b71161625..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm +++ /dev/null @@ -1,701 +0,0 @@ -GLOBAL_VAR_INIT(revolutionary_win, FALSE) - -////////////////////////////////////////////// -// // -// SYNDICATE TRAITORS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/traitor - name = "Traitors" - antag_flag = ROLE_TRAITOR - antag_datum = /datum/antagonist/traitor - minimum_required_age = 0 - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 5 - cost = 8 // Avoid raising traitor threat above this, as it is the default low cost ruleset. - scaling_cost = 9 - requirements = list(8,8,8,8,8,8,8,8,8,8) - antag_cap = list("denominator" = 38) - var/autotraitor_cooldown = (15 MINUTES) - -/datum/dynamic_ruleset/roundstart/traitor/pre_execute(population) - . = ..() - var/num_traitors = get_antag_cap(population) * (scaled_times + 1) - for (var/i = 1 to num_traitors) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.special_role = ROLE_TRAITOR - M.mind.restricted_roles = restricted_roles - GLOB.pre_setup_antags += M.mind - return TRUE - -////////////////////////////////////////////// -// // -// MALFUNCTIONING AI // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/malf_ai - name = "Malfunctioning AI" - antag_flag = ROLE_MALF - antag_datum = /datum/antagonist/malf_ai - minimum_required_age = 14 - exclusive_roles = list(JOB_AI) - required_candidates = 1 - weight = 3 - cost = 18 - requirements = list(101,101,101,80,60,50,30,20,10,10) - antag_cap = 1 - flags = HIGH_IMPACT_RULESET - -/datum/dynamic_ruleset/roundstart/malf_ai/ready(forced) - var/datum/job/ai_job = SSjob.GetJobType(/datum/job/ai) - - // If we're not forced, we're going to make sure we can actually have an AI in this shift, - if(!forced && min(ai_job.total_positions - ai_job.current_positions, ai_job.spawn_positions) <= 0) - log_dynamic("FAIL: [src] could not run, because there is nobody who wants to be an AI") - return FALSE - - return ..() - -/datum/dynamic_ruleset/roundstart/malf_ai/pre_execute(population) - . = ..() - - var/datum/job/ai_job = SSjob.GetJobType(/datum/job/ai) - // Maybe a bit too pedantic, but there should never be more malf AIs than there are available positions, spawn positions or antag cap allocations. - var/num_malf = min(get_antag_cap(population), min(ai_job.total_positions - ai_job.current_positions, ai_job.spawn_positions)) - for (var/i in 1 to num_malf) - if(candidates.len <= 0) - break - var/mob/new_malf = pick_n_take(candidates) - assigned += new_malf.mind - new_malf.mind.special_role = ROLE_MALF - GLOB.pre_setup_antags += new_malf.mind - // We need an AI for the malf roundstart ruleset to execute. This means that players who get selected as malf AI get priority, because antag selection comes before role selection. - LAZYADDASSOC(SSjob.dynamic_forced_occupations, new_malf, "AI") - return TRUE - -////////////////////////////////////////// -// // -// BLOOD BROTHERS // -// // -////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/traitorbro - name = "Blood Brothers" - antag_flag = ROLE_BROTHER - antag_datum = /datum/antagonist/brother - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - weight = 5 - cost = 8 - scaling_cost = 15 - requirements = list(40,30,30,20,20,15,15,15,10,10) - antag_cap = 1 - -/datum/dynamic_ruleset/roundstart/traitorbro/pre_execute(population) - . = ..() - - for (var/_ in 1 to get_antag_cap(population) * (scaled_times + 1)) - var/mob/candidate = pick_n_take(candidates) - if (isnull(candidate)) - break - - assigned += candidate.mind - candidate.mind.restricted_roles = restricted_roles - candidate.mind.special_role = ROLE_BROTHER - GLOB.pre_setup_antags += candidate.mind - - return TRUE - -/datum/dynamic_ruleset/roundstart/traitorbro/execute() - for (var/datum/mind/mind in assigned) - var/datum/team/brother_team/team = new - team.add_member(mind) - team.forge_brother_objectives() - mind.add_antag_datum(/datum/antagonist/brother, team) - GLOB.pre_setup_antags -= mind - - return TRUE - -////////////////////////////////////////////// -// // -// CHANGELINGS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/changeling - name = "Changelings" - antag_flag = ROLE_CHANGELING - antag_datum = /datum/antagonist/changeling - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 3 - cost = 16 - scaling_cost = 10 - requirements = list(70,70,60,50,40,20,20,10,10,10) - antag_cap = list("denominator" = 29) - -/datum/dynamic_ruleset/roundstart/changeling/pre_execute(population) - . = ..() - var/num_changelings = get_antag_cap(population) * (scaled_times + 1) - for (var/i = 1 to num_changelings) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.restricted_roles = restricted_roles - M.mind.special_role = ROLE_CHANGELING - GLOB.pre_setup_antags += M.mind - return TRUE - -/datum/dynamic_ruleset/roundstart/changeling/execute() - for(var/datum/mind/changeling in assigned) - var/datum/antagonist/changeling/new_antag = new antag_datum() - changeling.add_antag_datum(new_antag) - GLOB.pre_setup_antags -= changeling - return TRUE - -////////////////////////////////////////////// -// // -// HERETICS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/heretics - name = "Heretics" - antag_flag = ROLE_HERETIC - antag_datum = /datum/antagonist/heretic - protected_roles = list( - JOB_CAPTAIN, - JOB_DETECTIVE, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - restricted_roles = list( - JOB_AI, - JOB_CYBORG, - ) - required_candidates = 1 - weight = 3 - cost = 10 - scaling_cost = 9 - requirements = list(101,101,60,30,30,25,20,15,10,10) - antag_cap = list("denominator" = 24) - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE) - - -/datum/dynamic_ruleset/roundstart/heretics/pre_execute(population) - . = ..() - var/num_ecult = get_antag_cap(population) * (scaled_times + 1) - - for (var/i = 1 to num_ecult) - if(candidates.len <= 0) - break - var/mob/picked_candidate = pick_n_take(candidates) - assigned += picked_candidate.mind - picked_candidate.mind.restricted_roles = restricted_roles - picked_candidate.mind.special_role = ROLE_HERETIC - GLOB.pre_setup_antags += picked_candidate.mind - return TRUE - -/datum/dynamic_ruleset/roundstart/heretics/execute() - - for(var/c in assigned) - var/datum/mind/cultie = c - var/datum/antagonist/heretic/new_antag = new antag_datum() - cultie.add_antag_datum(new_antag) - GLOB.pre_setup_antags -= cultie - - return TRUE - - -////////////////////////////////////////////// -// // -// WIZARDS // -// // -////////////////////////////////////////////// - -// Dynamic is a wonderful thing that adds wizards to every round and then adds even more wizards during the round. -/datum/dynamic_ruleset/roundstart/wizard - name = "Wizard" - antag_flag = ROLE_WIZARD - antag_datum = /datum/antagonist/wizard - flags = HIGH_IMPACT_RULESET - minimum_required_age = 14 - 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 - required_candidates = 1 - weight = 2 - cost = 20 - requirements = list(90,90,90,80,60,40,30,20,10,10) - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_WIZARDDEN) - -/datum/dynamic_ruleset/roundstart/wizard/ready(forced = FALSE) - if(!check_candidates()) - return FALSE - if(!length(GLOB.wizardstart)) - log_admin("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") - message_admins("Cannot accept Wizard ruleset. Couldn't find any wizard spawn points.") - return FALSE - return ..() - -/datum/dynamic_ruleset/roundstart/wizard/round_result() - for(var/datum/antagonist/wizard/wiz in GLOB.antagonists) - var/mob/living/real_wiz = wiz.owner?.current - if(isnull(real_wiz)) - continue - - var/turf/wiz_location = get_turf(real_wiz) - // If this wiz is alive AND not in an away level, then we know not all wizards are dead and can leave entirely - if(considered_alive(wiz.owner) && wiz_location && !is_away_level(wiz_location.z)) - return - - SSticker.news_report = WIZARD_KILLED - -/datum/dynamic_ruleset/roundstart/wizard/pre_execute() - . = ..() - if(GLOB.wizardstart.len == 0) - return FALSE - var/mob/M = pick_n_take(candidates) - if (M) - assigned += M.mind - M.mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_wizard)) - M.mind.special_role = ROLE_WIZARD - - return TRUE - -/datum/dynamic_ruleset/roundstart/wizard/execute() - for(var/datum/mind/M in assigned) - M.current.forceMove(pick(GLOB.wizardstart)) - M.add_antag_datum(new antag_datum()) - return TRUE - -////////////////////////////////////////////// -// // -// BLOOD CULT // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/bloodcult - name = "Blood Cult" - antag_flag = ROLE_CULTIST - antag_datum = /datum/antagonist/cult - minimum_required_age = 14 - restricted_roles = list( - JOB_AI, - JOB_CAPTAIN, - JOB_CHAPLAIN, - JOB_CYBORG, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_candidates = 2 - weight = 3 - cost = 20 - requirements = list(100,90,80,60,40,30,10,10,10,10) - flags = HIGH_IMPACT_RULESET - antag_cap = list("denominator" = 20, "offset" = 1) - var/datum/team/cult/main_cult - -/datum/dynamic_ruleset/roundstart/bloodcult/ready(population, forced = FALSE) - required_candidates = get_antag_cap(population) - return ..() - -/datum/dynamic_ruleset/roundstart/bloodcult/pre_execute(population) - . = ..() - var/cultists = get_antag_cap(population) - for(var/cultists_number = 1 to cultists) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.special_role = ROLE_CULTIST - M.mind.restricted_roles = restricted_roles - GLOB.pre_setup_antags += M.mind - return TRUE - -/datum/dynamic_ruleset/roundstart/bloodcult/execute() - main_cult = new - for(var/datum/mind/M in assigned) - var/datum/antagonist/cult/new_cultist = new antag_datum() - new_cultist.cult_team = main_cult - new_cultist.give_equipment = TRUE - M.add_antag_datum(new_cultist) - GLOB.pre_setup_antags -= M - main_cult.setup_objectives() - return TRUE - -/datum/dynamic_ruleset/roundstart/bloodcult/round_result() - if(main_cult.check_cult_victory()) - SSticker.mode_result = "win - cult win" - SSticker.news_report = CULT_SUMMON - return - - SSticker.mode_result = "loss - staff stopped the cult" - - if(main_cult.size_at_maximum == 0) - CRASH("Cult team existed with a size_at_maximum of 0 at round end!") - - // If more than a certain ratio of our cultists have escaped, give the "cult escape" resport. - // Otherwise, give the "cult failure" report. - var/ratio_to_be_considered_escaped = 0.5 - var/escaped_cultists = 0 - for(var/datum/mind/escapee as anything in main_cult.members) - if(considered_escaped(escapee)) - escaped_cultists++ - - SSticker.news_report = (escaped_cultists / main_cult.size_at_maximum) >= ratio_to_be_considered_escaped ? CULT_ESCAPE : CULT_FAILURE - -////////////////////////////////////////////// -// // -// NUCLEAR OPERATIVES // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/nuclear - name = "Nuclear Emergency" - antag_flag = ROLE_OPERATIVE - antag_datum = /datum/antagonist/nukeop - var/datum/antagonist/antag_leader_datum = /datum/antagonist/nukeop/leader - minimum_required_age = 14 - restricted_roles = list( - JOB_CAPTAIN, - JOB_HEAD_OF_SECURITY, - ) // Just to be sure that a nukie getting picked won't ever imply a Captain or HoS not getting drafted - required_candidates = 5 - weight = 3 - cost = 20 - requirements = list(90,90,90,80,60,40,30,20,10,10) - flags = HIGH_IMPACT_RULESET - antag_cap = list("denominator" = 18, "offset" = 1) - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE) - var/required_role = ROLE_NUCLEAR_OPERATIVE - var/datum/team/nuclear/nuke_team - -/datum/dynamic_ruleset/roundstart/nuclear/ready(population, forced = FALSE) - required_candidates = get_antag_cap(population) - return ..() - -/datum/dynamic_ruleset/roundstart/nuclear/pre_execute(population) - . = ..() - // If ready() did its job, candidates should have 5 or more members in it - var/operatives = get_antag_cap(population) - for(var/operatives_number = 1 to operatives) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative)) - M.mind.special_role = ROLE_NUCLEAR_OPERATIVE - return TRUE - -/datum/dynamic_ruleset/roundstart/nuclear/execute() - var/datum/mind/most_experienced = get_most_experienced(assigned, required_role) - if(!most_experienced) - most_experienced = assigned[1] - var/datum/antagonist/nukeop/leader/leader = most_experienced.add_antag_datum(antag_leader_datum) - nuke_team = leader.nuke_team - for(var/datum/mind/assigned_player in assigned) - if(assigned_player == most_experienced) - continue - var/datum/antagonist/nukeop/new_op = new antag_datum() - assigned_player.add_antag_datum(new_op) - return TRUE - -/datum/dynamic_ruleset/roundstart/nuclear/round_result() - var/result = nuke_team.get_result() - switch(result) - if(NUKE_RESULT_FLUKE) - SSticker.mode_result = "loss - syndicate nuked - disk secured" - SSticker.news_report = NUKE_SYNDICATE_BASE - if(NUKE_RESULT_NUKE_WIN) - SSticker.mode_result = "win - syndicate nuke" - SSticker.news_report = STATION_DESTROYED_NUKE - if(NUKE_RESULT_NOSURVIVORS) - SSticker.mode_result = "halfwin - syndicate nuke - did not evacuate in time" - SSticker.news_report = STATION_DESTROYED_NUKE - if(NUKE_RESULT_WRONG_STATION) - SSticker.mode_result = "halfwin - blew wrong station" - SSticker.news_report = NUKE_MISS - if(NUKE_RESULT_WRONG_STATION_DEAD) - SSticker.mode_result = "halfwin - blew wrong station - did not evacuate in time" - SSticker.news_report = NUKE_MISS - if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD) - SSticker.mode_result = "loss - evacuation - disk secured - syndi team dead" - SSticker.news_report = OPERATIVES_KILLED - if(NUKE_RESULT_CREW_WIN) - SSticker.mode_result = "loss - evacuation - disk secured" - SSticker.news_report = OPERATIVES_KILLED - if(NUKE_RESULT_DISK_LOST) - SSticker.mode_result = "halfwin - evacuation - disk not secured" - SSticker.news_report = OPERATIVE_SKIRMISH - if(NUKE_RESULT_DISK_STOLEN) - SSticker.mode_result = "halfwin - detonation averted" - SSticker.news_report = OPERATIVE_SKIRMISH - else - SSticker.mode_result = "halfwin - interrupted" - SSticker.news_report = OPERATIVE_SKIRMISH - -////////////////////////////////////////////// -// // -// REVS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/revs - name = "Revolution" - persistent = TRUE - antag_flag = ROLE_REV_HEAD - antag_flag_override = ROLE_REV_HEAD - antag_datum = /datum/antagonist/rev/head - minimum_required_age = 14 - restricted_roles = list( - JOB_AI, - JOB_CAPTAIN, - JOB_CHIEF_ENGINEER, - JOB_CHIEF_MEDICAL_OFFICER, - JOB_CYBORG, - JOB_DETECTIVE, - JOB_HEAD_OF_PERSONNEL, - JOB_HEAD_OF_SECURITY, - JOB_PRISONER, - JOB_QUARTERMASTER, - JOB_RESEARCH_DIRECTOR, - JOB_SECURITY_OFFICER, - JOB_WARDEN, - ) - required_candidates = 3 - weight = 3 - delay = 7 MINUTES - cost = 20 - requirements = list(101,101,70,40,30,20,10,10,10,10) - antag_cap = 3 - flags = HIGH_IMPACT_RULESET - blocking_rules = list(/datum/dynamic_ruleset/latejoin/provocateur) - // I give up, just there should be enough heads with 35 players... - minimum_players = 35 - var/datum/team/revolution/revolution - var/finished = FALSE - -/datum/dynamic_ruleset/roundstart/revs/pre_execute(population) - . = ..() - var/max_candidates = get_antag_cap(population) - for(var/i = 1 to max_candidates) - if(candidates.len <= 0) - break - var/mob/M = pick_n_take(candidates) - assigned += M.mind - M.mind.restricted_roles = restricted_roles - M.mind.special_role = antag_flag - GLOB.pre_setup_antags += M.mind - return TRUE - -/datum/dynamic_ruleset/roundstart/revs/execute() - revolution = new() - for(var/datum/mind/M in assigned) - GLOB.pre_setup_antags -= M - if(check_eligible(M)) - var/datum/antagonist/rev/head/new_head = new antag_datum() - new_head.give_flash = TRUE - new_head.give_hud = TRUE - new_head.remove_clumsy = TRUE - M.add_antag_datum(new_head,revolution) - else - assigned -= M - log_dynamic("[ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.") - if(revolution.members.len) - revolution.update_objectives() - revolution.update_rev_heads() - SSshuttle.registerHostileEnvironment(revolution) - return TRUE - log_dynamic("[ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.") - return FALSE - -/datum/dynamic_ruleset/roundstart/revs/clean_up() - qdel(revolution) - ..() - -/datum/dynamic_ruleset/roundstart/revs/rule_process() - var/winner = revolution.process_victory() - if (isnull(winner)) - return - - finished = winner - - if(winner == REVOLUTION_VICTORY) - GLOB.revolutionary_win = TRUE - - return RULESET_STOP_PROCESSING - -/// Checks for revhead loss conditions and other antag datums. -/datum/dynamic_ruleset/roundstart/revs/proc/check_eligible(datum/mind/M) - var/turf/T = get_turf(M.current) - if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD)) - return TRUE - return FALSE - -/datum/dynamic_ruleset/roundstart/revs/round_result() - revolution.round_result(finished) - -// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them. - -////////////////////////////////////////////// -// // -// EXTENDED // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/extended - name = "Extended" - antag_flag = null - antag_datum = null - restricted_roles = list() - required_candidates = 0 - weight = 3 - cost = 0 - requirements = list(101,101,101,101,101,101,101,101,101,101) - flags = LONE_RULESET - -/datum/dynamic_ruleset/roundstart/extended/pre_execute() - . = ..() - message_admins("Starting a round of extended.") - log_game("Starting a round of extended.") - SSdynamic.spend_roundstart_budget(SSdynamic.round_start_budget) - SSdynamic.spend_midround_budget(SSdynamic.mid_round_budget) - SSdynamic.threat_log += "[worldtime2text()]: Extended ruleset set threat to 0." - return TRUE - -////////////////////////////////////////////// -// // -// CLOWN OPS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/nuclear/clown_ops - name = "Clown Operatives" - antag_datum = /datum/antagonist/nukeop/clownop - antag_flag = ROLE_CLOWN_OPERATIVE - antag_flag_override = ROLE_OPERATIVE - antag_leader_datum = /datum/antagonist/nukeop/leader/clownop - requirements = list(101,101,101,101,101,101,101,101,101,101) - required_role = ROLE_CLOWN_OPERATIVE - -/datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute() - . = ..() - if(!.) - return - - var/list/nukes = SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/syndicate) - for(var/obj/machinery/nuclearbomb/syndicate/nuke as anything in nukes) - new /obj/machinery/nuclearbomb/syndicate/bananium(nuke.loc) - qdel(nuke) - - for(var/datum/mind/clowns in assigned) - clowns.set_assigned_role(SSjob.GetJobType(/datum/job/clown_operative)) - clowns.special_role = ROLE_CLOWN_OPERATIVE - -////////////////////////////////////////////// -// // -// METEOR // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/meteor - name = "Meteor" - persistent = TRUE - required_candidates = 0 - weight = 3 - cost = 0 - requirements = list(101,101,101,101,101,101,101,101,101,101) - flags = LONE_RULESET - var/meteordelay = 2000 - var/nometeors = FALSE - var/rampupdelta = 5 - -/datum/dynamic_ruleset/roundstart/meteor/rule_process() - if(nometeors || meteordelay > world.time - SSticker.round_start_time) - return - - var/list/wavetype = GLOB.meteors_normal - var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 - - if (prob(meteorminutes)) - wavetype = GLOB.meteors_threatening - - if (prob(meteorminutes/2)) - wavetype = GLOB.meteors_catastrophic - - var/ramp_up_final = clamp(round(meteorminutes/rampupdelta), 1, 10) - - spawn_meteors(ramp_up_final, wavetype) - -/// Ruleset for Nations -/datum/dynamic_ruleset/roundstart/nations - name = "Nations" - required_candidates = 0 - weight = 0 //admin only (and for good reason) - cost = 0 - flags = LONE_RULESET | ONLY_RULESET - -/datum/dynamic_ruleset/roundstart/nations/execute() - . = ..() - //notably assistant is not in this list to prevent the round turning into BARBARISM instantly, and silicon is in this list for UN - var/list/department_types = list( - /datum/job_department/silicon, //united nations - /datum/job_department/cargo, - /datum/job_department/engineering, - /datum/job_department/medical, - /datum/job_department/science, - /datum/job_department/security, - /datum/job_department/service, - ) - - for(var/department_type in department_types) - create_separatist_nation(department_type, announcement = FALSE, dangerous = FALSE, message_admins = FALSE) - - GLOB.round_default_lawset = /datum/ai_laws/united_nations diff --git a/code/controllers/subsystem/dynamic/dynamic_testing.dm b/code/controllers/subsystem/dynamic/dynamic_testing.dm new file mode 100644 index 000000000000..5df1e3d29775 --- /dev/null +++ b/code/controllers/subsystem/dynamic/dynamic_testing.dm @@ -0,0 +1,111 @@ + +/// Verb to open the create command report window and send command reports. +ADMIN_VERB(dynamic_tester, R_DEBUG, "Dynamic Tester", "See dynamic probabilities.", ADMIN_CATEGORY_DEBUG) + BLACKBOX_LOG_ADMIN_VERB("Dynamic Tester") + var/datum/dynamic_tester/tgui = new() + tgui.ui_interact(user.mob) + +/datum/dynamic_tester + /// Instances of every roundstart ruleset + var/list/roundstart_rulesets = list() + /// Instances of every midround ruleset + var/list/midround_rulesets = list() + + /// A formatted report of the weights of each roundstart ruleset, refreshed occasionally and sent to the UI. + var/list/roundstart_ruleset_report = list() + /// A formatted report of the weights of each midround ruleset, refreshed occasionally and sent to the UI. + var/list/midround_ruleset_report = list() + + /// What is the tier we are testing? + var/tier = 1 + /// How many players are we testing with? + var/num_players = 10 + +/datum/dynamic_tester/New() + for(var/datum/dynamic_ruleset/rtype as anything in subtypesof(/datum/dynamic_ruleset/roundstart)) + if(!initial(rtype.config_tag)) + continue + var/datum/dynamic_ruleset/roundstart/created = new rtype(SSdynamic.get_config()) + roundstart_rulesets += created + // snowflake so we can see headrev stats + if(istype(created, /datum/dynamic_ruleset/roundstart/revolution)) + var/datum/dynamic_ruleset/roundstart/revolution/revs = created + revs.heads_necessary = 0 + + for(var/datum/dynamic_ruleset/rtype as anything in subtypesof(/datum/dynamic_ruleset/midround)) + if(!initial(rtype.config_tag)) + continue + var/datum/dynamic_ruleset/midround/created = new rtype(SSdynamic.get_config()) + midround_rulesets += created + + update_reports() + +/datum/dynamic_tester/ui_state(mob/user) + return ADMIN_STATE(R_DEBUG) + +/datum/dynamic_tester/ui_close() + qdel(src) + +/datum/dynamic_tester/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DynamicTester") + ui.open() + +/datum/dynamic_tester/ui_static_data(mob/user) + var/list/data = list() + + data["tier"] = tier + data["num_players"] = num_players + data["roundstart_ruleset_report"] = flatten_list(roundstart_ruleset_report) + data["midround_ruleset_report"] = flatten_list(midround_ruleset_report) + + return data + +/datum/dynamic_tester/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if("set_num_players") + var/old_num = num_players + num_players = text2num(params["num_players"]) + if(old_num != num_players) + update_reports() + return TRUE + + if("set_tier") + var/old_tier = tier + tier = text2num(params["tier"]) + if(old_tier != tier) + update_reports() + return TRUE + +/datum/dynamic_tester/proc/update_reports() + roundstart_ruleset_report.Cut() + for(var/datum/dynamic_ruleset/roundstart/ruleset as anything in roundstart_rulesets) + var/comment = "" + if(istype(ruleset, /datum/dynamic_ruleset/roundstart/revolution)) + var/datum/dynamic_ruleset/roundstart/revolution/revs = ruleset + comment = " (Assuming [initial(revs.heads_necessary)] heads of staff)" + + roundstart_ruleset_report[ruleset] = list( + "name" = ruleset.name, + "weight" = ruleset.get_weight(num_players, tier), + "max_candidates" = ruleset.get_antag_cap(num_players, ruleset.max_antag_cap || ruleset.min_antag_cap), + "min_candidates" = ruleset.get_antag_cap(num_players, ruleset.min_antag_cap), + "comment" = comment, + ) + + midround_ruleset_report.Cut() + for(var/datum/dynamic_ruleset/midround/ruleset as anything in midround_rulesets) + midround_ruleset_report[ruleset] = list( + "name" = ruleset.name, + "weight" = ruleset.get_weight(num_players, tier), + "max_candidates" = ruleset.get_antag_cap(num_players, ruleset.max_antag_cap || ruleset.min_antag_cap), + "min_candidates" = ruleset.get_antag_cap(num_players, ruleset.min_antag_cap), + "comment" = ruleset.midround_type, + ) + + update_static_data_for_all_viewers() diff --git a/code/controllers/subsystem/dynamic/dynamic_unfavorable_situation.dm b/code/controllers/subsystem/dynamic/dynamic_unfavorable_situation.dm deleted file mode 100644 index 994f2e3f5de7..000000000000 --- a/code/controllers/subsystem/dynamic/dynamic_unfavorable_situation.dm +++ /dev/null @@ -1,74 +0,0 @@ -/// An easy interface to make...*waves hands* bad things happen. -/// This is used for impactful events like traitors hacking and creating more threat, or a revolutions victory. -/// It tries to spawn a heavy midround if possible, otherwise it will trigger a "bad" random event after a short period. -/// Calling this function will not use up any threat. -/datum/controller/subsystem/dynamic/proc/unfavorable_situation() - SHOULD_NOT_SLEEP(TRUE) - - INVOKE_ASYNC(src, PROC_REF(_unfavorable_situation)) - -/datum/controller/subsystem/dynamic/proc/_unfavorable_situation() - var/static/list/unfavorable_random_events = list() - if (!length(unfavorable_random_events)) - unfavorable_random_events = generate_unfavourable_events() - var/list/possible_heavies = generate_unfavourable_heavy_rulesets() - if (!length(possible_heavies)) - var/datum/round_event_control/round_event_control_type = pick(unfavorable_random_events) - var/delay = rand(20 SECONDS, 1 MINUTES) - - log_dynamic_and_announce("An unfavorable situation was requested, but no heavy rulesets could be drafted. Spawning [initial(round_event_control_type.name)] in [DisplayTimeText(delay)] instead.") - force_event_after(round_event_control_type, "an unfavorable situation", delay) - else - var/datum/dynamic_ruleset/midround/heavy_ruleset = pick_weight(possible_heavies) - log_dynamic_and_announce("An unfavorable situation was requested, spawning [initial(heavy_ruleset.name)]") - picking_specific_rule(heavy_ruleset, forced = TRUE, ignore_cost = TRUE) - -/// Return a valid heavy dynamic ruleset, or an empty list if there's no time to run any rulesets -/datum/controller/subsystem/dynamic/proc/generate_unfavourable_heavy_rulesets() - if (EMERGENCY_PAST_POINT_OF_NO_RETURN) - return list() - - var/list/possible_heavies = list() - for (var/datum/dynamic_ruleset/midround/ruleset as anything in midround_rules) - if (ruleset.midround_ruleset_style != MIDROUND_RULESET_STYLE_HEAVY) - continue - - if (ruleset.weight == 0) - continue - - if (ruleset.cost > max_threat_level) - continue - - if (!ruleset.acceptable(GLOB.alive_player_list.len, threat_level)) - continue - - if (ruleset.minimum_round_time > world.time - SSticker.round_start_time) - continue - - if(istype(ruleset, /datum/dynamic_ruleset/midround/from_ghosts) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) - continue - - ruleset.trim_candidates() - - ruleset.load_templates() - if (!ruleset.ready()) - continue - - possible_heavies[ruleset] = ruleset.get_weight() - return possible_heavies - -/// Filter the below list by which events can actually run on this map -/datum/controller/subsystem/dynamic/proc/generate_unfavourable_events() - var/static/list/unfavorable_random_events = list( - /datum/round_event_control/earthquake, - /datum/round_event_control/immovable_rod, - /datum/round_event_control/meteor_wave, - /datum/round_event_control/portal_storm_syndicate, - ) - var/list/picked_events = list() - for(var/type in unfavorable_random_events) - var/datum/round_event_control/event = new type() - if(!event.valid_for_map()) - continue - picked_events += type - return picked_events diff --git a/code/controllers/subsystem/dynamic/readme.md b/code/controllers/subsystem/dynamic/readme.md deleted file mode 100644 index ec21626945ea..000000000000 --- a/code/controllers/subsystem/dynamic/readme.md +++ /dev/null @@ -1,194 +0,0 @@ -# Dynamic Mode - -## Roundstart - -Dynamic rolls threat based on a special sauce formula: - -> [dynamic_curve_width][/datum/controller/global_vars/var/dynamic_curve_width] \* tan((3.1416 \* (rand() - 0.5) \* 57.2957795)) + [dynamic_curve_centre][/datum/controller/global_vars/var/dynamic_curve_centre] - -This threat is split into two separate budgets--`round_start_budget` and `mid_round_budget`. For example, a round with 50 threat might be split into a 30 roundstart budget, and a 20 midround budget. The roundstart budget is used to apply antagonists applied on readied players when the roundstarts (`/datum/dynamic_ruleset/roundstart`). The midround budget is used for two types of rulesets: -- `/datum/dynamic_ruleset/midround` - Rulesets that apply to either existing alive players, or to ghosts. Think Blob or Space Ninja, which poll ghosts asking if they want to play as these roles. -- `/datum/dynamic_ruleset/latejoin` - Rulesets that apply to the next player that joins. Think Syndicate Infiltrator, which converts a player just joining an existing round into traitor. - -This split is done with a similar method, known as the ["lorentz distribution"](https://en.wikipedia.org/wiki/Cauchy_distribution), exists to create a bell curve that ensures that while most rounds will have a threat level around ~50, chaotic and tame rounds still exist for variety. - -The process of creating these numbers occurs in `/datum/controller/subsystem/dynamic/proc/generate_threat` (for creating the threat level) and `/datum/controller/subsystem/dynamic/proc/generate_budgets` (for splitting the threat level into budgets). - -## Deciding roundstart threats -In `/datum/controller/subsystem/dynamic/proc/roundstart()` (called when no admin chooses the rulesets explicitly), Dynamic uses the available roundstart budget to pick threats. This is done through the following system: - -- All roundstart rulesets (remember, `/datum/dynamic_ruleset/roundstart`) are put into an associative list with their weight as the values (`drafted_rules`). -- Until there is either no roundstart budget left, or until there is no ruleset we can choose from with the available threat, a `pickweight` is done based on the drafted_rules. If the same threat is picked twice, it will "scale up". The meaning of this depends on the ruleset itself, using the `scaled_times` variable; traitors for instance will create more the higher they scale. - - If a ruleset is chosen with the `HIGH_IMPACT_RULESET` in its `flags`, then all other `HIGH_IMPACT_RULESET`s will be removed from `drafted_rules`. This is so that only one can ever be chosen. - - If a ruleset has `LONE_RULESET` in its `flags`, then it will be removed from `drafted_rules`. This is to ensure it will only ever be picked once. An example of this in use is Wizard, to avoid creating multiple wizards. -- After all roundstart threats are chosen, `/datum/dynamic_ruleset/proc/picking_roundstart_rule` is called for each, passing in the ruleset and the number of times it is scaled. - - In this stage, `pre_execute` is called, which is the function that will determine what players get what antagonists. If this function returns FALSE for whatever reason (in the case of an error), then its threat is refunded. - -After this process is done, any leftover roundstart threat will be given to the existing midround budget (done in `/datum/controller/subsystem/dynamic/pre_setup()`). - -## Deciding midround threats - -### Frequency - -The frequency of midround threats is based on the midround threat of the round. The number of midround threats that will roll is `threat_level` / `threat_per_midround_roll` (configurable), rounded up. For example, if `threat_per_midround_roll` is set to 5, then for every 5 threat, one midround roll will be added. If you have 6 threat, with this configuration, you will get 2 midround rolls. - -These midround roll points are then equidistantly spaced across the round, starting from `midround_lower_bound` (configurable) to `midround_upper_bound` (configurable), with a +/- of `midround_roll_distance` (configurable). - -For example, if: -1. `midround_lower_bound` is `10 MINUTES` -2. `midround_upper_bound` is `100 MINUTES` -3. `midround_roll_distance` is `3 MINUTES` -4. You have 5 midround rolls for the round - -...then those 5 midround rolls will be placed equidistantly (meaning equally apart) across the first 10-100 minutes of the round. Every individual roll will then be adjusted to either be 3 minutes earlier, or 3 minutes later. - -### Threat variety - -Threats are split between **heavy** rulesets and **light** rulesets. A heavy ruleset includes major threats like space dragons or blobs, while light rulesets are ones that don't often cause shuttle calls when rolled, such as revenants or traitors (sleeper agents). - -When a midround roll occurs, the decision to choose between light or heavy depends on the current round time. If it is less than `midround_light_upper_bound` (configurable), then it is guaranteed to be a light ruleset. If it is more than `midround_heavy_lower_bound`, then it is guaranteed to be a heavy ruleset. If it is any point in between, it will interpolate the value between those. This means that the longer the round goes on, the more likely you are to get a heavy ruleset. - -If no heavy ruleset can run, such as not having enough threat, then a light ruleset is guaranteed to run. - -## Rule Processing - -Calls [rule_process][/datum/dynamic_ruleset/proc/rule_process] on every rule which is in the current_rules list. -Every sixty seconds, update_playercounts() -Midround injection time is checked against world.time to see if an injection should happen. -If midround injection time is lower than world.time, it updates playercounts again, then tries to inject and generates a new cooldown regardless of whether a rule is picked. - -## Latejoin - -make_antag_chance(newPlayer) -> (For each latespawn rule...) --> acceptable(living players, threat_level) -> trim_candidates() -> ready(forced=FALSE) -**If true, add to drafted rules -**NOTE that acceptable uses threat_level not threat! -**NOTE Latejoin timer is ONLY reset if at least one rule was drafted. -**NOTE the new_player.dm AttemptLateSpawn() calls OnPostSetup for all roles (unless assigned role is MODE) - -(After collecting all draftble rules...) --> picking_latejoin_ruleset(drafted_rules) -> spend threat -> ruleset.execute() - -## Midround - -process() -> (For each midround rule... --> acceptable(living players, threat_level) -> trim_candidates() -> ready(forced=FALSE) -(After collecting all draftble rules...) --> picking_midround_ruleset(drafted_rules) -> spend threat -> ruleset.execute() - -## Forced - -For latejoin, it simply sets forced_latejoin_rule -make_antag_chance(newPlayer) -> trim_candidates() -> ready(forced=TRUE) **NOTE no acceptable() call - -For midround, calls the below proc with forced = TRUE -picking_specific_rule(ruletype,forced) -> forced OR acceptable(living_players, threat_level) -> trim_candidates() -> ready(forced) -> spend threat -> execute() -**NOTE specific rule can be called by RS traitor->MR autotraitor w/ forced=FALSE -**NOTE that due to short circuiting acceptable() need not be called if forced. - -## Ruleset - -acceptable(population,threat) just checks if enough threat_level for population indice. -**NOTE that we currently only send threat_level as the second arg, not threat. -ready(forced) checks if enough candidates and calls the map's map_ruleset(dynamic_ruleset) at the parent level - -trim_candidates() varies significantly according to the ruleset type -Roundstart: All candidates are new_player mobs. Check them for standard stuff: connected, desire role, not banned, etc. -**NOTE Roundstart deals with both candidates (trimmed list of valid players) and mode.candidates (everyone readied up). Don't confuse them! -Latejoin: Only one candidate, the latejoiner. Standard checks. -Midround: Instead of building a single list candidates, candidates contains four lists: living, dead, observing, and living antags. Standard checks in trim_list(list). - -Midround - Rulesets have additional types -/from_ghosts: execute() -> send_applications() -> review_applications() -> finish_applications() -> finish_setup(mob/newcharacter, index) -> setup_role(role) -**NOTE: execute() here adds dead players and observers to candidates list - -## Configuration and variables - -### Configuration -Configuration can be done through a `config/dynamic.json` file. One is provided as example in the codebase. This config file, loaded in `/datum/controller/subsystem/dynamic/pre_setup()`, directly overrides the values in the codebase, and so is perfect for making some rulesets harder/easier to get, turning them off completely, changing how much they cost, etc. - -The format of this file is: -```json -{ - "Dynamic": { - /* Configuration in here will directly override `/datum/controller/subsystem/dynamic` itself. */ - /* Keys are variable names, values are their new values. */ - }, - - "Roundstart": { - /* Configuration in here will apply to `/datum/dynamic_ruleset/roundstart` instances. */ - /* Keys are the ruleset names, values are another associative list with keys being variable names and values being new values. */ - "Wizard": { - /* I, a head admin, have died to wizard, and so I made it cost a lot more threat than it does in the codebase. */ - "cost": 80 - } - }, - - "Midround": { - /* Same as "Roundstart", but for `/datum/dynamic_ruleset/midround` instead. */ - }, - - "Latejoin": { - /* Same as "Roundstart", but for `/datum/dynamic_ruleset/latejoin` instead. */ - }, - - "Station": { - /* Special threat reductions for dangerous station traits. Traits are selected before dynamic, so traits will always */ - /* reduce threat even if there's no threat for it available. Only "cost" can be modified */ - } -} -``` - -Note: Comments are not possible in this format, and are just in this document for the sake of readability. - -### Rulesets -Rulesets have the following variables notable to developers and those interested in tuning. - -- `required_candidates` - The number of people that *must be willing* (in their preferences) to be an antagonist with this ruleset. If the candidates do not meet this requirement, then the ruleset will not bother to be drafted. -- `antag_cap` - Judges the amount of antagonists to apply, for both solo and teams. Note that some antagonists (such as traitors, lings, heretics, etc) will add more based on how many times they've been scaled. Written as a linear equation--ceil(x/denominator) + offset, or as a fixed constant. If written as a linear equation, will be in the form of `list("denominator" = denominator, "offset" = offset)`. - - Examples include: - - Traitor: `antag_cap = list("denominator" = 24)`. This means that for every 24 players, 1 traitor will be added (assuming no scaling). - - Nuclear Emergency: `antag_cap = list("denominator" = 18, "offset" = 1)`. For every 18 players, 1 nuke op will be added. Starts at 1, meaning at 30 players, 3 nuke ops will be created, rather than 2. - - Revolution: `antag_cap = 3`. There will always be 3 rev-heads, no matter what. -- `minimum_required_age` - The minimum age in order to apply for the ruleset. -- `weight` - How likely this ruleset is to be picked. A higher weight results in a higher chance of drafting. -- `cost` - The initial cost of the ruleset. This cost is taken from either the roundstart or midround budget, depending on the ruleset. -- `scaling_cost` - Cost for every *additional* application of this ruleset. - - Suppose traitors has a `cost` of 8, and a `scaling_cost` of 5. This means that buying 1 application of the traitor ruleset costs 8 threat, but buying two costs 13 (8 + 5). Buying it a third time is 18 (8 + 5 + 5), etc. -- `pop_per_requirement` - The range of population each value in `requirements` represents. By default, this is 6. - - If the value is five the range is 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+. - - If it is six the range is 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+. - - If it is seven the range is 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+. -- `requirements` - A list that represents, per population range (see: `pop_per_requirement`), how much threat is required to *consider* this ruleset. This is independent of how much it'll actually cost. This uses *threat level*, not the budget--meaning if a round has 50 threat level, but only 10 points of round start threat, a ruleset with a requirement of 40 can still be picked if it can be bought. - - Suppose wizard has a `requirements` of `list(90,90,70,40,30,20,10,10,10,10)`. This means that, at 0-5 and 6-11 players, A station must have 90 threat in order for a wizard to be possible. At 12-17, 70 threat is required instead, etc. -- `restricted_roles` - A list of jobs that *can't* be drafted by this ruleset. For example, cyborgs cannot be changelings, and so are in the `restricted_roles`. -- `protected_roles` - Serves the same purpose of `restricted_roles`, except it can be turned off through configuration (`protect_roles_from_antagonist`). For example, security officers *shouldn't* be made traitor, so they are in Traitor's `protected_roles`. - - When considering putting a role in `protected_roles` or `restricted_roles`, the rule of thumb is if it is *technically infeasible* to support that job in that role. There's no *technical* reason a security officer can't be a traitor, and so they are simply in `protected_roles`. There *are* technical reasons a cyborg can't be a changeling, so they are in `restricted_roles` instead. - -This is not a complete list--search "configurable" in this README to learn more. - -### Dynamic - -The "Dynamic" key has the following configurable values: -- `pop_per_requirement` - The default value of `pop_per_requirement` for any ruleset that does not explicitly set it. Defaults to 6. -- `latejoin_delay_min`, `latejoin_delay_max` - The time range, in deciseconds (take your seconds, and multiply by 10), for a latejoin to attempt rolling. Once this timer is finished, a new one will be created within the same range. - - Suppose you have a `latejoin_delay_min` of 600 (60 seconds, 1 minute) and a `latejoin_delay_max` of 1800 (180 seconds, 3 minutes). Once the round starts, a random number in this range will be picked--let's suppose 1.5 minutes. After 1.5 minutes, Dynamic will decide if a latejoin threat should be created (a probability of `/datum/controller/subsystem/dynamic/proc/get_injection_chance()`). Regardless of its decision, a new timer will be started within the range of 1 to 3 minutes, repeatedly. -- `threat_curve_centre` - A number between -5 and +5. A negative value will give a more peaceful round and a positive value will give a round with higher threat. -- `threat_curve_width` - A number between 0.5 and 4. Higher value will favour extreme rounds and lower value rounds closer to the average. -- `roundstart_split_curve_centre` - A number between -5 and +5. Equivalent to threat_curve_centre, but for the budget split. A negative value will weigh towards midround rulesets, and a positive value will weight towards roundstart ones. -- `roundstart_split_curve_width` - A number between 0.5 and 4. Equivalent to threat_curve_width, but for the budget split. Higher value will favour more variance in splits and lower value rounds closer to the average. -- `random_event_hijack_minimum` - The minimum amount of time for antag random events to be hijacked. (See [Random Event Hijacking](#random-event-hijacking)) -- `random_event_hijack_maximum` - The maximum amount of time for antag random events to be hijacked. (See [Random Event Hijacking](#random-event-hijacking)) -- `hijacked_random_event_injection_chance` - The amount of injection chance to give to Dynamic when a random event is hijacked. (See [Random Event Hijacking](#random-event-hijacking)) -- `max_threat_level` - Sets the maximum amount of threat that can be rolled. Defaults to 100. You should only use this to *lower* the maximum threat, as raising it higher will not do anything. - -## Random Event "Hijacking" -Random events have the potential to be hijacked by Dynamic to keep the pace of midround injections, while also allowing greenshifts to contain some antagonists. - -`/datum/round_event_control/dynamic_should_hijack` is a variable to random events to allow Dynamic to hijack them, and defaults to FALSE. This is set to TRUE for random events that spawn antagonists. - -In `/datum/controller/subsystem/dynamic/on_pre_random_event` (in `dynamic_hijacking.dm`), Dynamic hooks to random events. If the `dynamic_should_hijack` variable is TRUE, the following sequence of events occurs: - -![Flow chart to describe the chain of events for Dynamic 2021 to take](https://github.com/tgstation/documentation-assets/blob/main/dynamic/random_event_hijacking.png) - -`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Heavy injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance_modifier`. diff --git a/code/controllers/subsystem/dynamic/ruleset_picking.dm b/code/controllers/subsystem/dynamic/ruleset_picking.dm deleted file mode 100644 index f22ce3315740..000000000000 --- a/code/controllers/subsystem/dynamic/ruleset_picking.dm +++ /dev/null @@ -1,139 +0,0 @@ -#define ADMIN_CANCEL_MIDROUND_TIME (10 SECONDS) - -/// -/// -/** - * From a list of rulesets, returns one based on weight and availability. - * Mutates the list that is passed into it to remove invalid rules. - * - * * max_allowed_attempts - Allows you to configure how many times the proc will attempt to pick a ruleset before giving up. - */ -/datum/controller/subsystem/dynamic/proc/pick_ruleset(list/drafted_rules, max_allowed_attempts = INFINITY) - if (only_ruleset_executed) - log_dynamic("FAIL: only_ruleset_executed") - return null - - if(!length(drafted_rules)) - log_dynamic("FAIL: pick ruleset supplied with an empty list of drafted rules.") - return null - - var/attempts = 0 - while (attempts < max_allowed_attempts) - attempts++ - var/datum/dynamic_ruleset/rule = pick_weight(drafted_rules) - if (!rule) - var/list/leftover_rules = list() - for (var/leftover_rule in drafted_rules) - leftover_rules += "[leftover_rule]" - - log_dynamic("FAIL: No rulesets left to pick. Leftover rules: [leftover_rules.Join(", ")]") - return null - - if (check_blocking(rule.blocking_rules, executed_rules)) - log_dynamic("FAIL: [rule] can't execute as another rulset is blocking it.") - drafted_rules -= rule - if(drafted_rules.len <= 0) - return null - continue - else if ( - rule.flags & HIGH_IMPACT_RULESET \ - && threat_level < GLOB.dynamic_stacking_limit \ - && GLOB.dynamic_no_stacking \ - && high_impact_ruleset_executed \ - ) - log_dynamic("FAIL: [rule] can't execute as a high impact ruleset was already executed.") - drafted_rules -= rule - if(drafted_rules.len <= 0) - return null - continue - - return rule - - return null - -/// Executes a random midround ruleset from the list of drafted rules. -/datum/controller/subsystem/dynamic/proc/pick_midround_rule(list/drafted_rules, description) - log_dynamic("Rolling [drafted_rules.len] [description]") - - var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules) - if (isnull(rule)) - return null - - current_midround_rulesets = drafted_rules - rule - - midround_injection_timer_id = addtimer( - CALLBACK(src, PROC_REF(execute_midround_rule), rule), \ - ADMIN_CANCEL_MIDROUND_TIME, \ - TIMER_STOPPABLE, \ - ) - - log_dynamic("[rule] ruleset executing...") - message_admins("DYNAMIC: Executing midround ruleset [rule] in [DisplayTimeText(ADMIN_CANCEL_MIDROUND_TIME)]. \ - CANCEL | \ - SOMETHING ELSE") - - return rule - -/// Fired after admins do not cancel a midround injection. -/datum/controller/subsystem/dynamic/proc/execute_midround_rule(datum/dynamic_ruleset/rule) - current_midround_rulesets = null - midround_injection_timer_id = null - if (!rule.repeatable) - midround_rules = remove_from_list(midround_rules, rule.type) - addtimer(CALLBACK(src, PROC_REF(execute_midround_latejoin_rule), rule), rule.delay) - -/// Mainly here to facilitate delayed rulesets. All midround/latejoin rulesets are executed with a timered callback to this proc. -/datum/controller/subsystem/dynamic/proc/execute_midround_latejoin_rule(sent_rule) - var/datum/dynamic_ruleset/rule = sent_rule - spend_midround_budget(rule.cost, threat_log, "[worldtime2text()]: [rule.ruletype] [rule.name]") - rule.pre_execute(GLOB.alive_player_list.len) - if (rule.execute()) - log_dynamic("Injected a [rule.ruletype] ruleset [rule.name].") - if(rule.flags & HIGH_IMPACT_RULESET) - high_impact_ruleset_executed = TRUE - else if(rule.flags & ONLY_RULESET) - only_ruleset_executed = TRUE - if(rule.ruletype == LATEJOIN_RULESET) - var/mob/M = pick(rule.candidates) - message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") - log_dynamic("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.") - executed_rules += rule - if (rule.persistent) - current_rules += rule - new_snapshot(rule) - rule.forget_startup() - return TRUE - rule.forget_startup() - rule.clean_up() - stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.") - return FALSE - -/// Fired when an admin cancels the current midround injection. -/datum/controller/subsystem/dynamic/proc/admin_cancel_midround(mob/user, timer_id) - if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id)) - to_chat(user, span_notice("Too late!")) - return - - log_admin("[key_name(user)] cancelled the next midround injection.") - message_admins("[key_name(user)] cancelled the next midround injection.") - midround_injection_timer_id = null - current_midround_rulesets = null - -/// Fired when an admin requests a different midround injection. -/datum/controller/subsystem/dynamic/proc/admin_different_midround(mob/user, timer_id) - if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id)) - to_chat(user, span_notice("Too late!")) - return - - midround_injection_timer_id = null - - if (isnull(current_midround_rulesets) || current_midround_rulesets.len == 0) - log_admin("[key_name(user)] asked for a different midround injection, but there were none left.") - message_admins("[key_name(user)] asked for a different midround injection, but there were none left.") - return - - log_admin("[key_name(user)] asked for a different midround injection.") - message_admins("[key_name(user)] asked for a different midround injection.") - pick_midround_rule(current_midround_rulesets, "different midround rulesets") - -#undef ADMIN_CANCEL_MIDROUND_TIME diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index f12ec3f288cb..dab3ad8463d6 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -24,7 +24,7 @@ SUBSYSTEM_DEF(job) var/list/unassigned = list() //Players who need jobs var/initial_players_to_assign = 0 //used for checking against population caps - // Whether to run DivideOccupations pure so that there are no side-effects from calling it other than + // Whether to run divide_occupations pure so that there are no side-effects from calling it other than // a player's assigned_role being set to some value. var/run_divide_occupation_pure = FALSE @@ -35,8 +35,10 @@ SUBSYSTEM_DEF(job) var/list/level_order = list(JP_HIGH,JP_MEDIUM,JP_LOW) - /// Lazylist of mob:occupation_string pairs. - var/list/dynamic_forced_occupations + /// Lazylist of mob:occupation_string pairs. Forces mobs into certain occupations with highest priority. + var/list/forced_occupations + /// Lazylist of mob:list(occupation_string) pairs. Prevents mobs from taking certain occupations at all. + var/list/prevented_occupations /** * Keys should be assigned job roles. Values should be >= 1. @@ -90,7 +92,7 @@ SUBSYSTEM_DEF(job) setup_job_lists() job_config_datum_singletons = generate_config_singletons() // we set this up here regardless in case someone wants to use the verb to generate the config file. if(!length(all_occupations)) - SetupOccupations() + setup_occupations() if(CONFIG_GET(flag/load_jobs_from_txt)) load_jobs_from_config() set_overflow_role(CONFIG_GET(string/overflow_job)) // this must always go after load_jobs_from_config() due to how the legacy systems operate, this always takes precedent. @@ -110,7 +112,7 @@ SUBSYSTEM_DEF(job) return overflow_jobs /datum/controller/subsystem/job/proc/set_overflow_role(new_overflow_role) - var/datum/job/new_overflow = ispath(new_overflow_role) ? GetJobType(new_overflow_role) : GetJob(new_overflow_role) + var/datum/job/new_overflow = ispath(new_overflow_role) ? get_job_type(new_overflow_role) : get_job(new_overflow_role) if(!new_overflow) JobDebug("Failed to set new overflow role: [new_overflow_role]") CRASH("set_overflow_role failed | new_overflow_role: [isnull(new_overflow_role) ? "null" : new_overflow_role]") @@ -123,7 +125,7 @@ SUBSYSTEM_DEF(job) if(new_overflow.type == overflow_role) return - var/datum/job/old_overflow = GetJobType(overflow_role) + var/datum/job/old_overflow = get_job_type(overflow_role) old_overflow.allow_bureaucratic_error = initial(old_overflow.allow_bureaucratic_error) old_overflow.spawn_positions = initial(old_overflow.spawn_positions) old_overflow.total_positions = initial(old_overflow.total_positions) @@ -133,7 +135,7 @@ SUBSYSTEM_DEF(job) JobDebug("Overflow role set to : [new_overflow.type]") -/datum/controller/subsystem/job/proc/SetupOccupations() +/datum/controller/subsystem/job/proc/setup_occupations() name_occupations = list() type_occupations = list() @@ -203,20 +205,20 @@ SUBSYSTEM_DEF(job) return TRUE -/datum/controller/subsystem/job/proc/GetJob(rank) +/datum/controller/subsystem/job/proc/get_job(rank) if(!length(all_occupations)) - SetupOccupations() + setup_occupations() return name_occupations[rank] -/datum/controller/subsystem/job/proc/GetJobType(jobtype) +/datum/controller/subsystem/job/proc/get_job_type(jobtype) RETURN_TYPE(/datum/job) if(!length(all_occupations)) - SetupOccupations() + setup_occupations() return type_occupations[jobtype] /datum/controller/subsystem/job/proc/get_department_type(department_type) if(!length(all_occupations)) - SetupOccupations() + setup_occupations() return joinable_departments_by_type[department_type] /** @@ -228,7 +230,7 @@ SUBSYSTEM_DEF(job) * * latejoin - Set to TRUE if this is a latejoin role assignment. * * do_eligibility_checks - Set to TRUE to conduct all job eligibility tests and reject on failure. Set to FALSE if job eligibility has been tested elsewhere and they can be safely skipped. */ -/datum/controller/subsystem/job/proc/AssignRole(mob/dead/new_player/player, datum/job/job, latejoin = FALSE, do_eligibility_checks = TRUE) +/datum/controller/subsystem/job/proc/assign_role(mob/dead/new_player/player, datum/job/job, latejoin = FALSE, do_eligibility_checks = TRUE) JobDebug("Running AR, Player: [player], Job: [isnull(job) ? "null" : job], LateJoin: [latejoin]") if(!player?.mind || !job) JobDebug("AR has failed, player has no mind or job is null, Player: [player], Rank: [isnull(job) ? "null" : job.type]") @@ -243,7 +245,7 @@ SUBSYSTEM_DEF(job) job.current_positions++ return TRUE -/datum/controller/subsystem/job/proc/FindOccupationCandidates(datum/job/job, level) +/datum/controller/subsystem/job/proc/find_occupation_candidates(datum/job/job, level) JobDebug("Running FOC, Job: [job], Level: [job_priority_level_to_string(level)]") var/list/candidates = list() for(var/mob/dead/new_player/player in unassigned) @@ -271,8 +273,6 @@ SUBSYSTEM_DEF(job) candidates += player return candidates - -/datum/controller/subsystem/job/proc/GiveRandomJob(mob/dead/new_player/player) JobDebug("GRJ Giving random job, Player: [player]") . = FALSE for(var/datum/job/job as anything in shuffle(joinable_occupations)) @@ -284,7 +284,7 @@ SUBSYSTEM_DEF(job) JobDebug("GRJ job lacks spawn positions to be eligible, Player: [player], Job: [job]") continue - if(istype(job, GetJobType(overflow_role))) // We don't want to give him assistant, that's boring! + if(istype(job, get_job_type(overflow_role))) // We don't want to give him assistant, that's boring! JobDebug("GRJ skipping overflow role, Player: [player], Job: [job]") continue @@ -303,14 +303,13 @@ SUBSYSTEM_DEF(job) JobDebug("GRJ Player eligible but AssignRole failed, Player: [player], Job: [job]") -/datum/controller/subsystem/job/proc/ResetOccupations() +/datum/controller/subsystem/job/proc/reset_occupations() JobDebug("Occupations reset.") for(var/mob/dead/new_player/player as anything in GLOB.new_player_list) if(!player?.mind) continue - player.mind.set_assigned_role(GetJobType(/datum/job/unassigned)) - player.mind.special_role = null - SetupOccupations() + player.mind.set_assigned_role(get_job_type(/datum/job/unassigned)) + setup_occupations() unassigned = list() if(CONFIG_GET(flag/load_jobs_from_txt)) // Any errors with the configs has already been said, we don't need to repeat them here. @@ -318,251 +317,222 @@ SUBSYSTEM_DEF(job) set_overflow_role(overflow_role) return - -/** - * Will try to select a head, ignoring ALL non-head preferences for every level until. - * - * Basically tries to ensure there is at least one head in every shift if anyone has that job preference enabled at all. - */ -/datum/controller/subsystem/job/proc/FillHeadPosition() - var/datum/job_department/command_department = get_department_type(/datum/job_department/command) - if(!command_department) - return FALSE - for(var/level in level_order) - for(var/datum/job/job as anything in command_department.department_jobs) - if((job.current_positions >= job.total_positions) && job.total_positions != -1) - continue - var/list/candidates = FindOccupationCandidates(job, level) - if(!candidates.len) - continue - var/mob/dead/new_player/candidate = pick(candidates) - // Eligibility checks done as part of FindOccupationCandidates. - if(AssignRole(candidate, job, do_eligibility_checks = FALSE)) - return TRUE - return FALSE - - -/** - * Attempts to fill out all possible head positions for players with that job at a a given job priority level. - * - * Arguments: - * * level - One of the JP_LOW, JP_MEDIUM or JP_HIGH defines. Attempts to find candidates with head jobs at this priority only. - */ -/datum/controller/subsystem/job/proc/CheckHeadPositions(level) - var/datum/job_department/command_department = get_department_type(/datum/job_department/command) - if(!command_department) - return - for(var/datum/job/job as anything in command_department.department_jobs) - if((job.current_positions >= job.total_positions) && job.total_positions != -1) - continue - var/list/candidates = FindOccupationCandidates(job, level) - if(!candidates.len) - continue - var/mob/dead/new_player/candidate = pick(candidates) - // Eligibility checks done as part of FindOccupationCandidates - AssignRole(candidate, job, do_eligibility_checks = FALSE) - /// Attempts to fill out all available AI positions. /datum/controller/subsystem/job/proc/fill_ai_positions() - var/datum/job/ai_job = GetJob(JOB_AI) + var/datum/job/ai_job = get_job(JOB_AI) if(!ai_job) return // In byond for(in to) loops, the iteration is inclusive so we need to stop at ai_job.total_positions - 1 for(var/i in ai_job.current_positions to ai_job.total_positions - 1) for(var/level in level_order) var/list/candidates = list() - candidates = FindOccupationCandidates(ai_job, level) + candidates = find_occupation_candidates(ai_job, level) if(candidates.len) var/mob/dead/new_player/candidate = pick(candidates) - // Eligibility checks done as part of FindOccupationCandidates - if(AssignRole(candidate, GetJobType(/datum/job/ai), do_eligibility_checks = FALSE)) + // Eligibility checks done as part of find_occupation_candidates + if(AssignRole(candidate, get_job_type(/datum/job/ai), do_eligibility_checks = FALSE)) break -/** Proc DivideOccupations +/** Proc divide_occupations * fills var "assigned_role" for all ready players. * This proc must not have any side effect besides of modifying "assigned_role". **/ -/datum/controller/subsystem/job/proc/DivideOccupations(pure = FALSE, allow_all = FALSE) +/datum/controller/subsystem/job/proc/divide_occupations(pure = FALSE, allow_all = FALSE) //Setup new player list and get the jobs list - JobDebug("Running DO, allow_all = [allow_all], pure = [pure]") + job_debug("DO: Running, allow_all = [allow_all], pure = [pure]") run_divide_occupation_pure = pure SEND_SIGNAL(src, COMSIG_OCCUPATIONS_DIVIDED, pure, allow_all) //Get the players who are ready - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/player = i - if(player.ready == PLAYER_READY_TO_PLAY && player.check_preferences() && player.mind && is_unassigned_job(player.mind.assigned_role)) + for(var/mob/dead/new_player/player as anything in GLOB.new_player_list) + if(player.ready == PLAYER_READY_TO_PLAY && player.check_job_preferences(!pure) && player.mind && is_unassigned_job(player.mind.assigned_role)) unassigned += player - initial_players_to_assign = unassigned.len + initial_players_to_assign = length(unassigned) - JobDebug("DO, Len: [unassigned.len]") + job_debug("DO: Player count to assign roles to: [initial_players_to_assign]") //Scale number of open security officer slots to population setup_officer_positions() //Jobs will have fewer access permissions if the number of players exceeds the threshold defined in game_options.txt - var/mat = CONFIG_GET(number/minimal_access_threshold) - if(mat) - if(mat > unassigned.len) + var/min_access_threshold = CONFIG_GET(number/minimal_access_threshold) + if(min_access_threshold) + if(min_access_threshold > initial_players_to_assign) CONFIG_SET(flag/jobs_have_minimal_access, FALSE) else CONFIG_SET(flag/jobs_have_minimal_access, TRUE) - //Shuffle players and jobs - unassigned = shuffle(unassigned) + //Shuffle player list. + shuffle_inplace(unassigned) - HandleFeedbackGathering() + handle_feedback_gathering() - // Dynamic has picked a ruleset that requires enforcing some jobs before others. - JobDebug("DO, Assigning Priority Positions: [length(dynamic_forced_occupations)]") + // Assign any priority positions before all other standard job selections. + job_debug("DO: Assigning priority positions") assign_priority_positions() + job_debug("DO: Priority assignment complete") + + // The overflow role has limitless slots, plus having the Overflow box ticked in prefs should (with one exception) set the priority to JP_HIGH. + // So everyone with overflow enabled will get that job. Thus we can assign it immediately to all players that have it enabled. + job_debug("DO: Assigning early overflow roles") + assign_all_overflow_positions() + job_debug("DO: Early overflow roles assigned.") + + // At this point we can assume the following: + // From assign_priority_positions() + // 1. If possible, any necessary job roles to allow Dynamic rulesets to execute (such as an AI for malf AI) are satisfied. + // 2. All Head of Staff roles with any player pref set to JP_HIGH are filled out. + // 3. If any player not selected by the above has any Head of Staff preference enabled at any JP_ level, there is at least one Head of Staff. + // + // From assign_all_overflow_positions() + // 4. Anyone with the overflow role enabled has been given the overflow role. + + // Copy the joinable occupation list and filter out ineligible occupations due to above job assignments. + var/list/available_occupations = joinable_occupations.Copy() + var/datum/job_department/command_department = get_department_type(/datum/job_department/command) - //People who wants to be the overflow role, sure, go on. - JobDebug("DO, Running Overflow Check 1") - var/datum/job/overflow_datum = GetJobType(overflow_role) - var/list/overflow_candidates = FindOccupationCandidates(overflow_datum, JP_LOW) - JobDebug("AC1, Candidates: [overflow_candidates.len]") - for(var/mob/dead/new_player/player in overflow_candidates) - JobDebug("AC1 pass, Player: [player]") - // Eligibility checks done as part of FindOccupationCandidates - AssignRole(player, GetJobType(overflow_role), do_eligibility_checks = FALSE) - overflow_candidates -= player - JobDebug("DO, AC1 end") - - //Select one head - JobDebug("DO, Running Head Check") - FillHeadPosition() - JobDebug("DO, Head Check end") - - // Fill out any remaining AI positions. - JobDebug("DO, Running AI Check") - fill_ai_positions() - JobDebug("DO, AI Check end") + for(var/datum/job/job in available_occupations) + // Make sure the job isn't filled. If it is, remove it from the list so it doesn't get checked. + if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1) + job_debug("DO: Job is now filled, Job: [job], Current: [job.current_positions], Limit: [job.spawn_positions]") + available_occupations -= job + continue - //Other jobs are now checked - JobDebug("DO, Running standard job assignment") - // New job giving system by Donkie - // This will cause lots of more loops, but since it's only done once it shouldn't really matter much at all. - // Hopefully this will add more randomness and fairness to job giving. + // Command jobs are handled via fill_all_head_positions_at_priority(...) + // Remove these jobs from the list of available occupations to prevent multiple players being assigned to the same + // limited role without constantly having to iterate over the available_occupations list and re-check them. + if(job in command_department?.department_jobs) + available_occupations -= job + + job_debug("DO: Running standard job assignment") - // Loop through all levels from high to low - var/list/shuffledoccupations = shuffle(joinable_occupations) for(var/level in level_order) - //Check the head jobs first each level - CheckHeadPositions(level) + job_debug("JOBS: Filling in head roles, Level: [job_priority_level_to_string(level)]") + // Fill the head jobs first each level + fill_all_head_positions_at_priority(level) // Loop through all unassigned players for(var/mob/dead/new_player/player in unassigned) if(!allow_all) - if(PopcapReached()) - RejectPlayer(player) - - // Loop through all jobs - for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY - if(!job) - JobDebug("FOC invalid/null job in occupations, Player: [player], Job: [job]") - shuffledoccupations -= job - continue + if(popcap_reached()) + job_debug("JOBS: Popcap reached, trying to reject player: [player]") + try_reject_player(player) - // Make sure the job isn't filled. If it is, remove it from the list so it doesn't get checked again. - if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1) - JobDebug("FOC job filled and not overflow, Player: [player], Job: [job], Current: [job.current_positions], Limit: [job.spawn_positions]") - shuffledoccupations -= job - continue + job_debug("JOBS: Finding a job for player: [player], at job priority pref: [job_priority_level_to_string(level)]") + // Loop through all jobs and build a list of jobs this player could be eligible for. + var/list/possible_jobs = list() + for(var/datum/job/job in available_occupations) // Filter any job that doesn't fit the current level. var/player_job_level = player.client?.prefs.job_preferences[job.title] if(isnull(player_job_level)) - JobDebug("FOC player job not enabled, Player: [player]") + job_debug("JOBS: Job not enabled, Job: [job]") continue - else if(player_job_level != level) - JobDebug("FOC player job enabled but at different level, Player: [player], TheirLevel: [job_priority_level_to_string(player_job_level)], ReqLevel: [job_priority_level_to_string(level)]") + if(player_job_level != level) + job_debug("JOBS: Job enabled at different priority pref, Job: [job], TheirLevel: [job_priority_level_to_string(player_job_level)], ReqLevel: [job_priority_level_to_string(level)]") continue - if(check_job_eligibility(player, job, "DO", add_job_to_log = TRUE) != JOB_AVAILABLE) + if(check_job_eligibility(player, job, "JOBS", add_job_to_log = TRUE) != JOB_AVAILABLE) continue - JobDebug("DO pass, Player: [player], Level:[level], Job:[job.title]") - AssignRole(player, job, do_eligibility_checks = FALSE) - unassigned -= player - break + possible_jobs += job + + // If there are no possible jobs for them at this priority, skip them. + if(!length(possible_jobs)) + job_debug("JOBS: Player not eligible for any available jobs at this priority level: [player]") + continue - JobDebug("DO, Ending standard job assignment") + // Otherwise, pick one of those jobs at random. + var/datum/job/picked_job = pick(possible_jobs) - JobDebug("DO, Handle unassigned.") - // Hand out random jobs to the people who didn't get any in the last check - // Also makes sure that they got their preference correct + job_debug("JOBS: Now assigning role to player: [player], Job:[picked_job.title]") + assign_role(player, picked_job, do_eligibility_checks = FALSE) + if((picked_job.current_positions >= picked_job.spawn_positions) && picked_job.spawn_positions != -1) + job_debug("JOBS: Job is now full, Job: [picked_job], Positions: [picked_job.current_positions], Limit: [picked_job.spawn_positions]") + available_occupations -= picked_job + + job_debug("DO: Ending standard job assignment") + + job_debug("DO: Handle unassigned") + // For any players that didn't get a job, fall back on their pref setting for what to do. for(var/mob/dead/new_player/player in unassigned) - HandleUnassigned(player, allow_all) - JobDebug("DO, Ending handle unassigned.") + handle_unassigned(player, allow_all) + job_debug("DO: Ending handle unassigned") - JobDebug("DO, Handle unrejectable unassigned") + job_debug("DO: Handle unrejectable unassigned") //Mop up people who can't leave. for(var/mob/dead/new_player/player in unassigned) //Players that wanted to back out but couldn't because they're antags (can you feel the edge case?) - if(!GiveRandomJob(player)) - if(!AssignRole(player, GetJobType(overflow_role))) //If everything is already filled, make them an assistant - JobDebug("DO, Forced antagonist could not be assigned any random job or the overflow role. DivideOccupations failed.") - JobDebug("---------------------------------------------------") + if(!give_random_job(player)) + if(!assign_role(player, get_job_type(overflow_role))) //If everything is already filled, make them an assistant + job_debug("DO: Forced antagonist could not be assigned any random job or the overflow role. divide_occupations failed.") + job_debug("---------------------------------------------------") run_divide_occupation_pure = FALSE return FALSE //Living on the edge, the forced antagonist couldn't be assigned to overflow role (bans, client age) - just reroll - JobDebug("DO, Ending handle unrejectable unassigned") + job_debug("DO: Ending handle unrejectable unassigned") - JobDebug("All divide occupations tasks completed.") - JobDebug("---------------------------------------------------") + job_debug("All divide occupations tasks completed.") + job_debug("---------------------------------------------------") run_divide_occupation_pure = FALSE return TRUE //We couldn't find a job from prefs for this guy. -/datum/controller/subsystem/job/proc/HandleUnassigned(mob/dead/new_player/player, allow_all = FALSE) +/datum/controller/subsystem/job/proc/handle_unassigned(mob/dead/new_player/player, allow_all = FALSE) var/jobless_role = player.client.prefs.read_preference(/datum/preference/choiced/jobless_role) if(!allow_all) - if(PopcapReached()) - RejectPlayer(player) + if(popcap_reached()) + job_debug("HU: Popcap reached, trying to reject player: [player]") + try_reject_player(player) return switch (jobless_role) if (BEOVERFLOW) - var/datum/job/overflow_role_datum = GetJobType(overflow_role) + var/datum/job/overflow_role_datum = get_job_type(overflow_role) + + if((overflow_role_datum.current_positions >= overflow_role_datum.spawn_positions) && overflow_role_datum.spawn_positions != -1) + job_debug("HU: Overflow role player cap reached, trying to reject: [player]") + try_reject_player(player) + return if(check_job_eligibility(player, overflow_role_datum, debug_prefix = "HU", add_job_to_log = TRUE) != JOB_AVAILABLE) - RejectPlayer(player) + job_debug("HU: Player cannot be overflow, trying to reject: [player]") + try_reject_player(player) return - if(!AssignRole(player, overflow_role_datum, do_eligibility_checks = FALSE)) - RejectPlayer(player) + if(!assign_role(player, overflow_role_datum, do_eligibility_checks = FALSE)) + job_debug("HU: Player could not be assigned overflow role, trying to reject: [player]") + try_reject_player(player) return if (BERANDOMJOB) - if(!GiveRandomJob(player)) - RejectPlayer(player) + if(!give_random_job(player)) + job_debug("HU: Player cannot be given a random job, trying to reject: [player]") + try_reject_player(player) return if (RETURNTOLOBBY) - RejectPlayer(player) + job_debug("HU: Player unable to be assigned job, return to lobby enabled: [player]") + try_reject_player(player) return else //Something gone wrong if we got here. - var/message = "HU: [player] fell through handling unassigned" - JobDebug(message) - log_game(message) - message_admins(message) - RejectPlayer(player) + job_debug("HU: [player] has an invalid jobless_role var: [jobless_role]") + log_game("[player] has an invalid jobless_role var: [jobless_role]") + message_admins("[player] has an invalid jobless_role, this shouldn't happen.") + try_reject_player(player) //Gives the player the stuff he should have with his rank -/datum/controller/subsystem/job/proc/EquipRank(mob/living/equipping, datum/job/job, client/player_client) +/datum/controller/subsystem/job/proc/equip_rank(mob/living/equipping, datum/job/job, client/player_client) equipping.job = job.title SEND_SIGNAL(equipping, COMSIG_JOB_RECEIVED, job) equipping.mind?.set_assigned_role_with_greeting(job, player_client) - equipping.on_job_equipping(job, player_client) - job.announce_job(equipping) if(player_client?.holder) - if(CONFIG_GET(flag/auto_deadmin_players) || (player_client.prefs?.toggles & DEADMIN_ALWAYS)) + if(CONFIG_GET(flag/auto_deadmin_always) || (player_client.prefs?.toggles & DEADMIN_ALWAYS)) player_client.holder.auto_deadmin() else handle_auto_deadmin_roles(player_client, job.title) @@ -572,7 +542,7 @@ SUBSYSTEM_DEF(job) /datum/controller/subsystem/job/proc/handle_auto_deadmin_roles(client/C, rank) if(!C?.holder) return TRUE - var/datum/job/job = GetJob(rank) + var/datum/job/job = get_job(rank) var/timegate_expired = FALSE // allow only forcing deadminning in the first X seconds of the round if auto_deadmin_timegate is set in config @@ -590,7 +560,7 @@ SUBSYSTEM_DEF(job) return C.holder.auto_deadmin() /datum/controller/subsystem/job/proc/setup_officer_positions() - var/datum/job/J = SSjob.GetJob(JOB_SECURITY_OFFICER) + var/datum/job/J = SSjob.get_job(JOB_SECURITY_OFFICER) if(!J) CRASH("setup_officer_positions(): Security officer job is missing") @@ -598,7 +568,7 @@ SUBSYSTEM_DEF(job) if(ssc > 0) if(J.spawn_positions > 0) var/officer_positions = min(12, max(J.spawn_positions, round(unassigned.len / ssc))) //Scale between configured minimum and 12 officers - JobDebug("Setting open security officer positions to [officer_positions]") + job_debug("SOP: Setting open security officer positions to [officer_positions]") J.total_positions = officer_positions J.spawn_positions = officer_positions @@ -614,7 +584,7 @@ SUBSYSTEM_DEF(job) else //We ran out of spare locker spawns! break -/datum/controller/subsystem/job/proc/HandleFeedbackGathering() +/datum/controller/subsystem/job/proc/handle_feedback_gathering() for(var/datum/job/job as anything in joinable_occupations) var/high = 0 //high var/medium = 0 //medium @@ -622,6 +592,7 @@ SUBSYSTEM_DEF(job) var/never = 0 //never var/banned = 0 //banned var/young = 0 //account too young + var/newbie = 0 //exp too low for(var/i in GLOB.new_player_list) var/mob/dead/new_player/player = i if(!(player.ready == PLAYER_READY_TO_PLAY && player.mind && is_unassigned_job(player.mind.assigned_role))) @@ -633,7 +604,7 @@ SUBSYSTEM_DEF(job) young++ continue if(job.required_playtime_remaining(player.client)) - young++ + newbie++ continue switch(player.client.prefs.job_preferences[job.title]) if(JP_HIGH) @@ -650,8 +621,9 @@ SUBSYSTEM_DEF(job) SSblackbox.record_feedback("nested tally", "job_preferences", never, list("[job.title]", "never")) SSblackbox.record_feedback("nested tally", "job_preferences", banned, list("[job.title]", "banned")) SSblackbox.record_feedback("nested tally", "job_preferences", young, list("[job.title]", "young")) + SSblackbox.record_feedback("nested tally", "job_preferences", newbie, list("[job.title]", "newbie")) -/datum/controller/subsystem/job/proc/PopcapReached() +/datum/controller/subsystem/job/proc/popcap_reached() var/hpc = CONFIG_GET(number/hard_popcap) var/epc = CONFIG_GET(number/extreme_popcap) if(hpc || epc) @@ -660,16 +632,17 @@ SUBSYSTEM_DEF(job) return 1 return 0 -/datum/controller/subsystem/job/proc/RejectPlayer(mob/dead/new_player/player) - if(player.mind && player.mind.special_role) - return - if(PopcapReached()) - JobDebug("Popcap overflow Check observer located, Player: [player]") - JobDebug("Player rejected :[player]") +/datum/controller/subsystem/job/proc/try_reject_player(mob/dead/new_player/player) + for(var/datum/dynamic_ruleset/roundstart/ruleset in SSdynamic.queued_rulesets) + if(player.mind in ruleset.selected_minds) + job_debug("RJCT: Player unable to be rejected due to being selected by dynamic, Player: [player], Ruleset: [ruleset]") + return FALSE + + job_debug("RJCT: Player rejected, Player: [player]") unassigned -= player if(!run_divide_occupation_pure) - to_chat(player, "You have failed to qualify for any job you desired.") - player.unready() + to_chat(player, span_infoplain("You have failed to qualify for any job you desired.")) + player.ready = PLAYER_NOT_READY /datum/controller/subsystem/job/Recover() @@ -677,10 +650,10 @@ SUBSYSTEM_DEF(job) var/oldjobs = SSjob.all_occupations sleep(2 SECONDS) for (var/datum/job/job as anything in oldjobs) - INVOKE_ASYNC(src, PROC_REF(RecoverJob), job) + INVOKE_ASYNC(src, PROC_REF(recover_job), job) -/datum/controller/subsystem/job/proc/RecoverJob(datum/job/J) - var/datum/job/newjob = GetJob(J.title) +/datum/controller/subsystem/job/proc/recover_job(datum/job/J) + var/datum/job/newjob = get_job(J.title) if (!istype(newjob)) return newjob.total_positions = J.total_positions @@ -689,15 +662,21 @@ SUBSYSTEM_DEF(job) /atom/proc/JoinPlayerHere(mob/joining_mob, buckle) // By default, just place the mob on the same turf as the marker or whatever. - joining_mob.forceMove(get_turf(src)) + // Set joining_mob as the new mob so subtypes can use it as a proper mob. + if(ispath(joining_mob)) + joining_mob = new joining_mob(get_turf(src)) + else + joining_mob.forceMove(get_turf(src)) + return joining_mob /obj/structure/chair/JoinPlayerHere(mob/joining_mob, buckle) - . = ..() + var/mob/created_joining_mob = ..() // Placing a mob in a chair will attempt to buckle it, or else fall back to default. - if(buckle && isliving(joining_mob)) - buckle_mob(joining_mob, FALSE, FALSE) + if(buckle && isliving(created_joining_mob)) + buckle_mob(created_joining_mob, FALSE, FALSE) + return created_joining_mob -/datum/controller/subsystem/job/proc/SendToLateJoin(mob/M, buckle = TRUE) +/datum/controller/subsystem/job/proc/send_to_late_join(mob/M, buckle = TRUE) var/atom/destination if(M.mind && !is_unassigned_job(M.mind.assigned_role) && length(GLOB.jobspawn_overrides[M.mind.assigned_role.title])) //We're doing something special today. destination = pick(GLOB.jobspawn_overrides[M.mind.assigned_role.title]) @@ -732,17 +711,6 @@ SUBSYSTEM_DEF(job) stack_trace("Unable to find last resort spawn point.") return GET_ERROR_ROOM -///Lands specified mob at a random spot in the hallways -/datum/controller/subsystem/job/proc/DropLandAtRandomHallwayPoint(mob/living/living_mob) - var/turf/spawn_turf = get_safe_random_station_turf(typesof(/area/station/hallway)) - - if(!spawn_turf) - SendToLateJoin(living_mob) - else - var/obj/structure/closet/supplypod/centcompod/toLaunch = new() - living_mob.forceMove(toLaunch) - new /obj/effect/pod_landingzone(spawn_turf, toLaunch) - /// Returns a list of minds of all heads of staff who are alive /datum/controller/subsystem/job/proc/get_living_heads() . = list() @@ -777,7 +745,7 @@ SUBSYSTEM_DEF(job) if(sec.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY) . += sec -/datum/controller/subsystem/job/proc/JobDebug(message) +/datum/controller/subsystem/job/proc/job_debug(message) log_job_debug(message) /// Builds various lists of jobs based on station, centcom and additional jobs with icons associated with them. @@ -842,12 +810,54 @@ SUBSYSTEM_DEF(job) safe_code_timer_id = null safe_code_request_loc = null -/// Blindly assigns the required roles to every player in the dynamic_forced_occupations list. +/// Assigns roles that are considered high priority, either due to dynamic needing to force a specific role for a specific ruleset +/// or making sure roles critical to round progression exist where possible every shift. /datum/controller/subsystem/job/proc/assign_priority_positions() - for(var/mob/new_player in dynamic_forced_occupations) - // Eligibility checks already carried out as part of the dynamic ruleset trim_candidates proc.area - // However no guarantee of game state between then and now, so don't skip eligibility checks on AssignRole. - AssignRole(new_player, GetJob(dynamic_forced_occupations[new_player])) + job_debug("APP: Assigning Dynamic ruleset forced occupations: [LAZYLEN(forced_occupations)]") + for(var/datum/mind/mind as anything in forced_occupations) + var/mob/dead/new_player = mind.current + // Eligibility checks already carried out as part of the dynamic ruleset trim_candidates proc. + // However no guarantee of game state between then and now, so don't skip eligibility checks on assign_role. + assign_role(new_player, get_job_type(LAZYACCESS(forced_occupations, mind))) + + // Get JP_HIGH department Heads of Staff in place. Indirectly useful for the Revolution ruleset to have as many Heads as possible. + job_debug("APP: Assigning all JP_HIGH head of staff roles.") + var/head_count = fill_all_head_positions_at_priority(JP_HIGH) + + // If nobody has JP_HIGH on a Head role, try to force at least one Head of Staff so every shift has the best chance + // of having at least one leadership role. + if(head_count == 0) + force_one_head_assignment() + + // Fill out all AI positions. + job_debug("APP: Filling all AI positions") + fill_ai_positions() + +/datum/controller/subsystem/job/proc/assign_all_overflow_positions() + job_debug("OVRFLW: Assigning all overflow roles.") + job_debug("OVRFLW: This shift's overflow role: [overflow_role]") + var/datum/job/overflow_datum = get_job_type(overflow_role) + + // When the Overflow role changes for any reason, this allows players to set otherwise invalid job priority pref states. + // So if Assistant is the "usual" Overflow but it gets changed to Clown for a shift, players can set the Assistant role's priorities + // to JP_MEDIUM and JP_LOW. When the "usual" Overflow role comes back, it returns to an On option in the prefs menu but still + // keeps its old JP_MEDIUM or JP_LOW value in the background. + + // Due to this prefs quirk, we actually don't want to find JP_HIGH candidates as it may exclude people with abnormal pref states that + // appear normal from the UI. By passing in JP_ANY, it will return all players that have the overflow job pref (which should be a toggle) + // set to any level. + var/list/overflow_candidates = find_occupation_candidates(overflow_datum, JP_ANY) + job_debug("OVRFLW: Attempting to assign the overflow role to [length(overflow_candidates)] players.") + for(var/mob/dead/new_player/player in overflow_candidates) + if((overflow_datum.current_positions >= overflow_datum.spawn_positions) && overflow_datum.spawn_positions != -1) + job_debug("OVRFLW: Overflow role cap reached, role only assigned to [overflow_datum.current_positions] players.") + job_debug("OVRFLW: Overflow Job is now full, Job: [overflow_datum], Positions: [overflow_datum.current_positions], Limit: [overflow_datum.spawn_positions]") + return + + // Eligibility checks done as part of find_occupation_candidates, so skip them. + assign_role(player, get_job_type(overflow_role), do_eligibility_checks = FALSE) + job_debug("OVRFLW: Assigned overflow to player: [player]") + job_debug("OVRFLW: All overflow roles assigned.") /// Takes a job priority #define such as JP_LOW and gets its string representation for logging. /datum/controller/subsystem/job/proc/job_priority_level_to_string(priority) @@ -865,40 +875,41 @@ SUBSYSTEM_DEF(job) * Arguments: * * player - The player to check for job eligibility. * * possible_job - The job to check for eligibility against. - * * debug_prefix - Logging prefix for the JobDebug log entries. For example, GRJ during GiveRandomJob or DO during DivideOccupations. + * * debug_prefix - Logging prefix for the job_debug log entries. For example, GRJ during give_random_job or DO during divide_occupations. * * add_job_to_log - If TRUE, appends the job type to the log entry. If FALSE, does not. Set to FALSE when check is part of iterating over players for a specific job, set to TRUE when check is part of iterating over jobs for a specific player and you don't want extra log entry spam. */ /datum/controller/subsystem/job/proc/check_job_eligibility(mob/dead/new_player/player, datum/job/possible_job, debug_prefix = "", add_job_to_log = FALSE) if(!player.mind) - JobDebug("[debug_prefix] player has no mind, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + job_debug("[debug_prefix]: Player has no mind, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") return JOB_UNAVAILABLE_GENERIC - if(possible_job.title in player.mind.restricted_roles) - JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ANTAG_INCOMPAT, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + if(possible_job.title in LAZYACCESS(prevented_occupations, player.mind)) + job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ANTAG_INCOMPAT, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") return JOB_UNAVAILABLE_ANTAG_INCOMPAT if(!possible_job.player_old_enough(player.client)) - JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ACCOUNTAGE, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ACCOUNTAGE, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") return JOB_UNAVAILABLE_ACCOUNTAGE var/required_playtime_remaining = possible_job.required_playtime_remaining(player.client) if(required_playtime_remaining) - JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_PLAYTIME, possible_job.title)], Player: [player], MissingTime: [required_playtime_remaining][add_job_to_log ? ", Job: [possible_job]" : ""]") + job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_PLAYTIME, possible_job.title)], Player: [player], MissingTime: [required_playtime_remaining][add_job_to_log ? ", Job: [possible_job]" : ""]") return JOB_UNAVAILABLE_PLAYTIME // Run the banned check last since it should be the rarest check to fail and can access the database. if(is_banned_from(player.ckey, possible_job.title)) - JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_BANNED, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_BANNED, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") return JOB_UNAVAILABLE_BANNED // Check for character age - if(possible_job.required_character_age > player.client.prefs.read_preference(/datum/preference/numeric/age) && possible_job.required_character_age != null) - JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_AGE)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + var/client/player_client = GET_CLIENT(player) + if(isnum(possible_job.required_character_age) && possible_job.required_character_age > player_client.prefs.read_preference(/datum/preference/numeric/age)) + job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_AGE)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") return JOB_UNAVAILABLE_AGE // Need to recheck the player exists after is_banned_from since it can query the DB which may sleep. if(QDELETED(player)) - JobDebug("[debug_prefix] player is qdeleted, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + job_debug("[debug_prefix]: Player is qdeleted, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") return JOB_UNAVAILABLE_GENERIC return JOB_AVAILABLE diff --git a/code/controllers/subsystem/polling.dm b/code/controllers/subsystem/polling.dm index 279c6f7bad70..eecda9b08295 100644 --- a/code/controllers/subsystem/polling.dm +++ b/code/controllers/subsystem/polling.dm @@ -204,11 +204,10 @@ SUBSYSTEM_DEF(polling) if(the_ignore_category) if(potential_candidate.ckey in GLOB.poll_ignore[the_ignore_category]) return FALSE - if(role) + if(role && potential_candidate.client) if(!(role in potential_candidate.client.prefs.be_special)) return FALSE - var/required_time = GLOB.special_roles[role] || 0 - if(potential_candidate.client && potential_candidate.client.get_remaining_days(required_time) > 0) + if(potential_candidate.client.get_days_to_play_antag(role) > 0) return FALSE if(check_jobban) diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 165a068a9547..47309b5b4212 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -234,7 +234,7 @@ SUBSYSTEM_DEF(ticker) return TRUE if(GLOB.station_was_nuked) return TRUE - if(GLOB.revolutionary_win) + if(GLOB.revolution_handler?.result == REVOLUTION_VICTORY) return TRUE return FALSE @@ -245,17 +245,17 @@ SUBSYSTEM_DEF(ticker) CHECK_TICK //Configure mode and assign player to antagonists var/can_continue = FALSE - can_continue = SSdynamic.pre_setup() //Choose antagonists + can_continue = SSdynamic.select_roundstart_antagonists() //Choose antagonists CHECK_TICK SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_JOBS_ASSIGNED, src) - can_continue = can_continue && SSjob.DivideOccupations() //Distribute jobs + can_continue = can_continue && SSjob.divide_occupations() //Distribute jobs CHECK_TICK if(!GLOB.Debug2) if(!can_continue) log_game("Game failed pre_setup") to_chat(world, "Error setting up game. Reverting to pre-game lobby.") - SSjob.ResetOccupations() + SSjob.reset_occupations() return FALSE else message_admins(span_notice("DEBUG: Bypassing prestart checks...")) @@ -311,7 +311,37 @@ SUBSYSTEM_DEF(ticker) /datum/controller/subsystem/ticker/proc/PostSetup() set waitfor = FALSE - SSdynamic.post_setup() + + // Spawn traitors and stuff + for(var/datum/dynamic_ruleset/roundstart/ruleset in SSdynamic.queued_rulesets) + ruleset.execute() + SSdynamic.queued_rulesets -= ruleset + SSdynamic.executed_rulesets += ruleset + // Queue roundstart intercept report + if(!CONFIG_GET(flag/no_intercept_report)) + GLOB.communications_controller.queue_roundstart_report() + // Queue admin logout report + addtimer(CALLBACK(src, PROC_REF(display_roundstart_logout_report)), ROUNDSTART_LOGOUT_REPORT_TIME) + // Queue suicide slot handling + if(CONFIG_GET(flag/reopen_roundstart_suicide_roles)) + var/delay = (CONFIG_GET(number/reopen_roundstart_suicide_roles_delay) * 1 SECONDS) || 4 MINUTES + addtimer(CALLBACK(src, PROC_REF(reopen_roundstart_suicide_roles)), delay) + // Handle database + if(SSdbcore.Connect()) + var/list/to_set = list() + var/arguments = list() + 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) + GLOB.start_state = new /datum/station_state() GLOB.start_state.count() @@ -338,7 +368,7 @@ SUBSYSTEM_DEF(ticker) if(!iter_human.hardcore_survival_score) continue - if(iter_human.mind?.special_role) + if(iter_human.is_antag()) to_chat(iter_human, span_notice("You will gain [round(iter_human.hardcore_survival_score) * 2] hardcore random points if you greentext this round!")) else to_chat(iter_human, span_notice("You will gain [round(iter_human.hardcore_survival_score)] hardcore random points if you survive this round!")) @@ -355,6 +385,92 @@ SUBSYSTEM_DEF(ticker) SSvote.initiate_vote(/datum/vote/round_chaos, "the server", forced = TRUE) +/datum/controller/subsystem/ticker/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_AFK_THRESHOLD) //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(HAS_TRAIT(L, TRAIT_SUICIDED)) //Suicider + msg += "[L.name] ([L.key]), the [L.job] ([span_bolddanger("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(HAS_TRAIT(L, TRAIT_SUICIDED)) //Suicider + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] ([span_bolddanger("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_bolddanger("Ghosted")])\n" + continue //Ghosted while alive + + var/concatenated_message = msg.Join() + log_admin(concatenated_message) + to_chat(GLOB.admins, concatenated_message) + +/datum/controller/subsystem/ticker/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.get_job(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 = {" + [command_name()] 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") + //These callbacks will fire after roundstart key transfer /datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/cb) if(!HasRoundStarted()) diff --git a/code/datums/ai_laws/ai_laws.dm b/code/datums/ai_laws/ai_laws.dm index 9b9f9c628411..d93966195a1f 100644 --- a/code/datums/ai_laws/ai_laws.dm +++ b/code/datums/ai_laws/ai_laws.dm @@ -234,11 +234,11 @@ GLOBAL_VAR(round_default_lawset) return FALSE // If the owner is an antag (has a special role) they also shouldn't be wiped - if(owner?.mind?.special_role) + if(owner?.is_antag()) return FALSE if (isAI(owner)) var/mob/living/silicon/ai/ai_owner = owner - if(ai_owner.deployed_shell?.mind?.special_role) + if(ai_owner.deployed_shell?.is_antag()) return FALSE zeroth = null diff --git a/code/datums/brain_damage/creepy_trauma.dm b/code/datums/brain_damage/creepy_trauma.dm index 8b4921c24315..88904110fa6a 100644 --- a/code/datums/brain_damage/creepy_trauma.dm +++ b/code/datums/brain_damage/creepy_trauma.dm @@ -67,7 +67,8 @@ /datum/brain_trauma/special/obsessed/on_lose() ..() - owner.mind.remove_antag_datum(/datum/antagonist/obsessed) + if (owner.mind.remove_antag_datum(/datum/antagonist/obsessed)) + owner.mind.add_antag_datum(/datum/antagonist/former_obsessed) owner.clear_mood_event("creeping") if(obsession) log_game("[key_name(owner)] is no longer obsessed with [key_name(obsession)].") diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 18e74abdeba9..ea07db402fc1 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -153,11 +153,11 @@ for(var/job in appearance_from_prefs.job_preferences) var/this_pref = appearance_from_prefs.job_preferences[job] if(this_pref > highest_pref) - appearance_job = SSjob.GetJob(job) + appearance_job = SSjob.get_job(job) highest_pref = this_pref if(!appearance_job) - appearance_job = SSjob.GetJob(JOB_ASSISTANT) + appearance_job = SSjob.get_job(JOB_ASSISTANT) if(istype(appearance_job, /datum/job/ai)) human_image = icon('icons/mob/silicon/ai.dmi', icon_state = resolve_ai_icon(appearance_from_prefs.read_preference(/datum/preference/choiced/ai_core_display)), dir = SOUTH) diff --git a/code/datums/communications.dm b/code/datums/communications.dm new file mode 100644 index 000000000000..af5021feec4f --- /dev/null +++ b/code/datums/communications.dm @@ -0,0 +1,135 @@ +#define COMMUNICATION_COOLDOWN (30 SECONDS) +#define COMMUNICATION_COOLDOWN_AI (30 SECONDS) + +GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, new) + +/datum/communciations_controller + COOLDOWN_DECLARE(silicon_message_cooldown) + COOLDOWN_DECLARE(nonsilicon_message_cooldown) + + /// Are we trying to send a cross-station message that contains soft-filtered words? If so, flip to TRUE to extend the time admins have to cancel the message. + var/soft_filtering = FALSE + + /// A list of footnote datums, to be added to the bottom of the roundstart command report. + var/list/command_report_footnotes = list() + /// A counter of conditions that are blocking the command report from printing. Counter incremements up for every blocking condition, and de-incrememnts when it is complete. + var/block_command_report = 0 + /// Has a special xenomorph egg been delivered? + var/xenomorph_egg_delivered = FALSE + /// The location where the special xenomorph egg was planted + var/area/captivity_area + + /// What is the lower bound of when the roundstart announcement is sent out? + var/waittime_l = 60 SECONDS + /// What is the higher bound of when the roundstart announcement is sent out? + var/waittime_h = 180 SECONDS + +/datum/communciations_controller/proc/can_announce(mob/living/user, is_silicon) + if(is_silicon && COOLDOWN_FINISHED(src, silicon_message_cooldown)) + return TRUE + else if(!is_silicon && COOLDOWN_FINISHED(src, nonsilicon_message_cooldown)) + return TRUE + else + return FALSE + +/datum/communciations_controller/proc/make_announcement(mob/living/user, is_silicon, input, syndicate, list/players) + if(!can_announce(user, is_silicon)) + return FALSE + if(is_silicon) + minor_announce(html_decode(input),"[user.name] announces:", players = players) + COOLDOWN_START(src, silicon_message_cooldown, COMMUNICATION_COOLDOWN_AI) + else + var/list/message_data = user.treat_message(input) + if(syndicate) + priority_announce(html_decode(message_data["message"]), null, 'sound/announcer/announcement/announce_syndi.ogg', ANNOUNCEMENT_TYPE_SYNDICATE, has_important_message = TRUE, players = players, color_override = "red") + else + priority_announce(html_decode(message_data["message"]), null, 'sound/announcer/announcement/announce.ogg', ANNOUNCEMENT_TYPE_CAPTAIN, has_important_message = TRUE, players = players) + COOLDOWN_START(src, nonsilicon_message_cooldown, COMMUNICATION_COOLDOWN) + user.log_talk(input, LOG_SAY, tag="priority announcement") + message_admins("[ADMIN_LOOKUPFLW(user)] has made a priority announcement.") + +/datum/communciations_controller/proc/send_message(datum/comm_message/sending,print = TRUE,unique = FALSE) + for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list) + if(!(C.machine_stat & (BROKEN|NOPOWER)) && is_station_level(C.z)) + if(unique) + C.add_message(sending) + else //We copy the message for each console, answers and deletions won't be shared + var/datum/comm_message/M = new(sending.title,sending.content,sending.possible_answers.Copy()) + C.add_message(M) + if(print) + var/obj/item/paper/printed_paper = new /obj/item/paper(C.loc) + printed_paper.name = "paper - '[sending.title]'" + printed_paper.add_raw_text(sending.content) + printed_paper.update_appearance() + +// Called AFTER everyone is equipped with their job +/datum/communciations_controller/proc/queue_roundstart_report() + addtimer(CALLBACK(src, PROC_REF(send_roundstart_report)), rand(waittime_l, waittime_h)) + +/datum/communciations_controller/proc/send_roundstart_report(greenshift) + if(block_command_report) //If we don't want the report to be printed just yet, we put it off until it's ready + addtimer(CALLBACK(src, PROC_REF(send_roundstart_report), greenshift), 10 SECONDS) + return + + var/dynamic_report = SSdynamic.get_advisory_report() + if(isnull(greenshift)) // if we're not forced to be greenshift or not - check if we are an actual greenshift + greenshift = SSdynamic.current_tier.tier == 0 && dynamic_report == /datum/dynamic_tier/greenshift::advisory_report + + . = "Nanotrasen Department of Intelligence Threat Advisory, Spinward Sector, TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]:
" + . += dynamic_report + + SSstation.generate_station_goals(greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget)) + + var/list/datum/station_goal/goals = SSstation.get_station_goals() + if(length(goals)) + var/list/texts = list("
Special Orders for [station_name()]:
") + for(var/datum/station_goal/station_goal as anything in goals) + station_goal.on_report() + texts += station_goal.get_report() + . += texts.Join("
") + + var/list/trait_list_strings = list() + for(var/datum/station_trait/station_trait as anything in SSstation.station_traits) + if(!station_trait.show_in_report) + continue + trait_list_strings += "[station_trait.get_report()]
" + if(trait_list_strings.len > 0) + . += "
Identified shift divergencies:
" + trait_list_strings.Join() + + if(length(command_report_footnotes)) + var/footnote_pile = "" + + for(var/datum/command_footnote/footnote as anything in command_report_footnotes) + footnote_pile += "[footnote.message]
" + footnote_pile += "[footnote.signature]
" + footnote_pile += "
" + + . += "
Additional Notes:

" + footnote_pile + +#ifndef MAP_TEST + print_command_report(., "[command_name()] Status Summary", announce=FALSE) + if(greenshift) + priority_announce( + "Thanks to the tireless efforts of our security and intelligence divisions, \ + there are currently no credible threats to [station_name()]. \ + All station construction projects have been authorized. Have a secure shift!", + "Security Report", + SSstation.announcer.get_rand_report_sound(), + color_override = "green", + ) + else + if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE) + SSsecurity_level.set_level(SEC_LEVEL_BLUE, announce = FALSE) + priority_announce( + "[SSsecurity_level.current_security_level.elevating_to_announcement]\n\n\ + A summary has been copied and printed to all communications consoles.", + "Security level elevated.", + ANNOUNCER_INTERCEPT, + color_override = SSsecurity_level.current_security_level.announcement_color, + ) +#endif + + return . + +#undef COMMUNICATION_COOLDOWN +#undef COMMUNICATION_COOLDOWN_AI diff --git a/code/datums/elements/art.dm b/code/datums/elements/art.dm index 81d388aa94af..d5a642c23d0b 100644 --- a/code/datums/elements/art.dm +++ b/code/datums/elements/art.dm @@ -74,7 +74,7 @@ var/datum/job_department/hater_department = SSjob.get_department_type(hater_department_type) for(var/datum/job/hater_job as anything in hater_department.department_jobs) haters += hater_job.title - var/datum/job/quartermaster/fucking_quartermaster = SSjob.GetJobType(/datum/job/quartermaster) + var/datum/job/quartermaster/fucking_quartermaster = SSjob.get_job_type(/datum/job/quartermaster) haters += fucking_quartermaster.title if(!(user.mind.assigned_role.title in haters)) diff --git a/code/datums/id_trim/jobs.dm b/code/datums/id_trim/jobs.dm index 6e9341f57f35..a2079a16f43e 100644 --- a/code/datums/id_trim/jobs.dm +++ b/code/datums/id_trim/jobs.dm @@ -24,7 +24,7 @@ /datum/id_trim/job/New() if(ispath(job)) - job = SSjob.GetJobType(job) + job = SSjob.get_job_type(job) if(isnull(job_changes)) job_changes = SSmapping.config.job_changes @@ -1154,7 +1154,7 @@ if(CONFIG_GET(number/depsec_access_level) == POPULATION_SCALED_ACCESS) var/minimal_security_officers = 3 // We do not spawn in any more lockers if there are 5 or less security officers, so let's keep it lower than that number. - var/datum/job/J = SSjob.GetJob(JOB_SECURITY_OFFICER) + var/datum/job/J = SSjob.get_job(JOB_SECURITY_OFFICER) if((J.spawn_positions - minimal_security_officers) <= 0) access |= elevated_access diff --git a/code/datums/mind/_mind.dm b/code/datums/mind/_mind.dm index 4f662d1df97d..843139000a03 100644 --- a/code/datums/mind/_mind.dm +++ b/code/datums/mind/_mind.dm @@ -48,8 +48,6 @@ /// Job datum indicating the mind's role. This should always exist after initialization, as a reference to a singleton. var/datum/job/assigned_role - var/special_role - var/list/restricted_roles = list() /// List of antag datums on this mind var/list/antag_datums @@ -90,7 +88,9 @@ ///Skill multiplier list, just slap your multiplier change onto this with the type it is coming from as key. var/list/experience_multiplier_reasons = list() - /// A lazy list of statuses to add next to this mind in the traitor panel + /// A lazy list of roles to display that this mind has, stuff like "Traitor" or "Special Creature" + var/list/special_roles + /// A lazy list of statuses to display that this mind has, stuff like "Infected" or "Mindshielded" var/list/special_statuses ///Assoc list of addiction values, key is the type of withdrawal (as singleton type), and the value is the amount of addiction points (as number) @@ -105,7 +105,7 @@ /datum/mind/New(_key) key = _key init_known_skills() - set_assigned_role(SSjob.GetJobType(/datum/job/unassigned)) // Unassigned by default. + set_assigned_role(SSjob.get_job_type(/datum/job/unassigned)) // Unassigned by default. /datum/mind/Destroy() SSticker.minds -= src @@ -125,7 +125,7 @@ .["memories"] = memories .["antag_datums"] = antag_datums .["holy_role"] = holy_role - .["special_role"] = special_role + .["special_role"] = jointext(get_special_roles(), " | ") .["assigned_role"] = assigned_role.title .["current"] = current @@ -249,7 +249,7 @@ var/new_role = input("Select new role", "Assigned role", assigned_role.title) as null|anything in sort_list(SSjob.name_occupations) if(isnull(new_role)) return - var/datum/job/new_job = SSjob.GetJob(new_role) + var/datum/job/new_job = SSjob.get_job(new_role) if (!new_job) to_chat(usr, span_warning("Job not found.")) return @@ -478,7 +478,6 @@ var/datum/addiction/affected_addiction = SSaddiction.all_addictions[type] return affected_addiction.on_lose_addiction_points(src) - /// Setter for the assigned_role job datum. /datum/mind/proc/set_assigned_role(datum/job/new_role) if(assigned_role == new_role) diff --git a/code/datums/mind/antag.dm b/code/datums/mind/antag.dm index 647821935cff..f0eff835f892 100644 --- a/code/datums/mind/antag.dm +++ b/code/datums/mind/antag.dm @@ -60,37 +60,6 @@ return TRUE return FALSE -/* - Removes antag type's references from a mind. - objectives, uplinks, powers etc are all handled. -*/ - -/datum/mind/proc/remove_changeling() - var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) - if(C) - remove_antag_datum(/datum/antagonist/changeling) - special_role = null - -/datum/mind/proc/remove_traitor() - remove_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/remove_nukeop() - var/datum/antagonist/nukeop/nuke = has_antag_datum(/datum/antagonist/nukeop,TRUE) - if(nuke) - remove_antag_datum(nuke.type) - special_role = null - -/datum/mind/proc/remove_wizard() - remove_antag_datum(/datum/antagonist/wizard) - special_role = null - -/datum/mind/proc/remove_rev() - var/datum/antagonist/rev/rev = has_antag_datum(/datum/antagonist/rev) - if(rev) - remove_antag_datum(rev.type) - special_role = null - - /datum/mind/proc/remove_antag_equip() var/list/Mob_Contents = current.get_contents() for(var/obj/item/I in Mob_Contents) @@ -220,7 +189,7 @@ current.log_message("has been enslaved to [key_name(creator)].", LOG_GAME) log_admin("[key_name(current)] has been enslaved to [key_name(creator)].") - if(creator.mind?.special_role) + if(creator.is_antag()) message_admins("[ADMIN_LOOKUPFLW(current)] has been created by [ADMIN_LOOKUPFLW(creator)], an antagonist.") to_chat(current, span_userdanger("Despite your creator's current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.")) @@ -267,33 +236,12 @@ /datum/mind/proc/take_uplink() qdel(find_syndicate_uplink()) -/datum/mind/proc/make_traitor() - if(!(has_antag_datum(/datum/antagonist/traitor))) - add_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/make_changeling() - var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) - if(!C) - C = add_antag_datum(/datum/antagonist/changeling) - special_role = ROLE_CHANGELING - return C - - /datum/mind/proc/make_wizard() if(has_antag_datum(/datum/antagonist/wizard)) return - set_assigned_role(SSjob.GetJobType(/datum/job/space_wizard)) - special_role = ROLE_WIZARD + set_assigned_role(SSjob.get_job_type(/datum/job/space_wizard)) add_antag_datum(/datum/antagonist/wizard) - -/datum/mind/proc/make_rev() - var/datum/antagonist/rev/head/head = new() - head.give_flash = TRUE - head.give_hud = TRUE - add_antag_datum(head) - special_role = ROLE_REV_HEAD - /// Sets our can_hijack to the fastest speed our antag datums allow. /datum/mind/proc/get_hijack_speed() . = 0 diff --git a/code/datums/mind/initialization.dm b/code/datums/mind/initialization.dm index eb622cc5af54..0e9a40f7f639 100644 --- a/code/datums/mind/initialization.dm +++ b/code/datums/mind/initialization.dm @@ -21,17 +21,16 @@ //AI /mob/living/silicon/ai/mind_initialize() . = ..() - mind.set_assigned_role(SSjob.GetJobType(/datum/job/ai)) + mind.set_assigned_role(SSjob.get_job_type(/datum/job/ai)) //BORG /mob/living/silicon/robot/mind_initialize() . = ..() - mind.set_assigned_role(SSjob.GetJobType(/datum/job/cyborg)) + mind.set_assigned_role(SSjob.get_job_type(/datum/job/cyborg)) //PAI /mob/living/silicon/pai/mind_initialize() . = ..() - mind.set_assigned_role(SSjob.GetJobType(/datum/job/personal_ai)) - mind.special_role = "" + mind.set_assigned_role(SSjob.get_job_type(/datum/job/personal_ai)) diff --git a/code/datums/records/manifest.dm b/code/datums/records/manifest.dm index ac0e3e19e46e..7dabb7c49b37 100644 --- a/code/datums/records/manifest.dm +++ b/code/datums/records/manifest.dm @@ -32,7 +32,7 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new) var/name = target.name var/rank = target.rank // user-visible job var/trim = target.trim // internal jobs by trim type - var/datum/job/job = SSjob.GetJob(trim) + var/datum/job/job = SSjob.get_job(trim) if(!job || !(job.job_flags & JOB_CREW_MANIFEST) || !LAZYLEN(job.departments_list)) // In case an unlawful custom rank is added. var/list/misc_list = manifest_out[DEPARTMENT_UNASSIGNED] misc_list[++misc_list.len] = list( diff --git a/code/datums/station_traits/_station_trait.dm b/code/datums/station_traits/_station_trait.dm index 757dcb21d8b7..6bfdaa01a175 100644 --- a/code/datums/station_traits/_station_trait.dm +++ b/code/datums/station_traits/_station_trait.dm @@ -31,8 +31,6 @@ GLOBAL_LIST_EMPTY(lobby_station_traits) var/list/lobby_buttons = list() /// The ID that we look for in dynamic.json. Not synced with 'name' because I can already see this go wrong var/dynamic_threat_id - /// If ran during dynamic, do we reduce the total threat? Will be overriden by config if set - var/threat_reduction = 0 /// Trait should not be instantiated in a round if its type matches this type var/abstract_type = /datum/station_trait @@ -41,8 +39,6 @@ GLOBAL_LIST_EMPTY(lobby_station_traits) RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, PROC_REF(on_round_start)) - if(threat_reduction) - GLOB.dynamic_station_traits[src] = threat_reduction if(sign_up_button) GLOB.lobby_station_traits += src if(trait_processes) @@ -52,8 +48,9 @@ GLOBAL_LIST_EMPTY(lobby_station_traits) /datum/station_trait/Destroy() SSstation.station_traits -= src - GLOB.dynamic_station_traits.Remove(src) destroy_lobby_buttons() + GLOB.lobby_station_traits -= src + REMOVE_TRAIT(SSstation, trait_to_give, STATION_TRAIT) return ..() /// Returns the type of info the centcom report has on this trait, if any. diff --git a/code/datums/station_traits/job_traits.dm b/code/datums/station_traits/job_traits.dm index dc207d17e59a..a5d6125adeea 100644 --- a/code/datums/station_traits/job_traits.dm +++ b/code/datums/station_traits/job_traits.dm @@ -1,7 +1,3 @@ -#define CAN_ROLL_ALWAYS 1 //always can roll for antag -#define CAN_ROLL_PROTECTED 2 //can roll if config lets protected roles roll -#define CAN_ROLL_NEVER 3 //never roll antag - /** * A station trait which enables a temporary job * Generally speaking these should always all be mutually exclusive, don't have too many at once @@ -11,8 +7,6 @@ abstract_type = /datum/station_trait/job /// What tooltip to show on the button var/button_desc = "Sign up to gain some kind of unusual job, not available in most rounds." - /// Can this job roll antag? - var/can_roll_antag = CAN_ROLL_ALWAYS /// How many positions to spawn? var/position_amount = 1 /// Type of job to enable @@ -22,11 +16,6 @@ /datum/station_trait/job/New() . = ..() - switch(can_roll_antag) - if(CAN_ROLL_PROTECTED) - SSstation.antag_protected_roles += job_to_add::title - if(CAN_ROLL_NEVER) - SSstation.antag_restricted_roles += job_to_add::title blacklist += subtypesof(/datum/station_trait/job) - type // All but ourselves RegisterSignal(SSdcs, COMSIG_GLOB_PRE_JOBS_ASSIGNED, PROC_REF(pre_jobs_assigned)) @@ -61,7 +50,7 @@ if (isnull(signee) || !signee.client || !signee.mind || signee.ready != PLAYER_READY_TO_PLAY) LAZYREMOVE(lobby_candidates, signee) - var/datum/job/our_job = SSjob.GetJobType(job_to_add) + var/datum/job/our_job = SSjob.get_job_type(job_to_add) while(length(lobby_candidates) && position_amount > 0) var/mob/dead/new_player/picked_player = pick_n_take(lobby_candidates) picked_player.mind.set_assigned_role(our_job) @@ -71,7 +60,7 @@ lobby_candidates = null /datum/station_trait/job/can_display_lobby_button(client/player) - var/datum/job/our_job = SSjob.GetJobType(job_to_add) + var/datum/job/our_job = SSjob.get_job_type(job_to_add) return our_job.player_old_enough(player) && ..() /// Adds a gorilla to the cargo department, replacing the sloth and the mech @@ -80,7 +69,6 @@ button_desc = "Sign up to become the Cargo Gorilla, a peaceful shepherd of boxes." weight = 1 show_in_report = FALSE // Selective attention test. Did you spot the gorilla? - can_roll_antag = CAN_ROLL_NEVER job_to_add = /datum/job/cargo_gorilla /datum/station_trait/job/cargorilla/New() @@ -113,7 +101,6 @@ weight = 2 report_message = "We have installed a Bridge Assistant on your station." show_in_report = TRUE - can_roll_antag = CAN_ROLL_PROTECTED job_to_add = /datum/job/bridge_assistant /datum/station_trait/job/bridge_assistant/New() @@ -167,13 +154,8 @@ weight = 2 report_message = "Veteran Security Advisor has been assigned to your station to help with Security matters." show_in_report = TRUE - can_roll_antag = CAN_ROLL_PROTECTED job_to_add = /datum/job/veteran_advisor /datum/station_trait/job/veteran_advisor/on_lobby_button_update_overlays(atom/movable/screen/lobby/button/sign_up/lobby_button, list/overlays) . = ..() overlays += "veteran_advisor" - -#undef CAN_ROLL_ALWAYS -#undef CAN_ROLL_PROTECTED -#undef CAN_ROLL_NEVER diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm index 498f120fe7fa..ff41592dfb4d 100644 --- a/code/datums/station_traits/negative_traits.dm +++ b/code/datums/station_traits/negative_traits.dm @@ -556,7 +556,6 @@ trait_to_give = STATION_TRAIT_RADIOACTIVE_NEBULA blacklist = list(/datum/station_trait/random_event_weight_modifier/rad_storms) - threat_reduction = 30 dynamic_threat_id = "Radioactive Nebula" intensity_increment_time = 5 MINUTES diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index fbf1dc1c29ea..d0632112a0f6 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -226,7 +226,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list /datum/objective/assassinate/update_explanation_text() ..() if(target?.current) - explanation_text = "Assassinate [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role]." + explanation_text = "Assassinate [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())]." else explanation_text = "Free objective." @@ -276,7 +276,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list /datum/objective/mutiny/update_explanation_text() ..() if(target?.current) - explanation_text = "Assassinate or exile [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role]." + explanation_text = "Assassinate or exile [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())]." else explanation_text = "Free objective." @@ -298,7 +298,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list /datum/objective/maroon/update_explanation_text() if(target?.current) - explanation_text = "Prevent [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role], from escaping alive." + explanation_text = "Prevent [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())], from escaping alive." else explanation_text = "Free objective." @@ -329,7 +329,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list /datum/objective/debrain/update_explanation_text() ..() if(target?.current) - explanation_text = "Steal the brain of [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role]." + explanation_text = "Steal the brain of [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())]." else explanation_text = "Free objective." @@ -353,7 +353,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list /datum/objective/protect/update_explanation_text() ..() if(target?.current) - explanation_text = "Protect [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role]." + explanation_text = "Protect [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())]." else explanation_text = "Free objective." @@ -378,7 +378,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list /datum/objective/jailbreak/update_explanation_text() ..() if(target?.current) - explanation_text = "Ensure that [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role] escapes alive and out of custody." + explanation_text = "Ensure that [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())] escapes alive and out of custody." else explanation_text = "Free objective." @@ -394,7 +394,7 @@ GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list /datum/objective/jailbreak/detain/update_explanation_text() ..() if(target?.current) - explanation_text = "Ensure that [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role] is delivered to nanotrasen alive and in custody." + explanation_text = "Ensure that [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())] is delivered to Nanotrasen alive and in custody." else explanation_text = "Free objective." diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 8471fd7a0675..cd8c66d44a84 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -341,7 +341,7 @@ if (!message) return - SScommunications.soft_filtering = FALSE + GLOB.communications_controller.soft_filtering = FALSE var/list/hard_filter_result = is_ic_filtered(message) if(hard_filter_result) tgui_alert(usr, "Your message contains: (\"[hard_filter_result[CHAT_FILTER_INDEX_WORD]]\"), which is not allowed on this server.") @@ -353,7 +353,7 @@ return message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". They may be using a disallowed term for a cross-station message. Increasing delay time to reject.\n\n Message: \"[html_encode(message)]\"") log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". They may be using a disallowed term for a cross-station message. Increasing delay time to reject.\n\n Message: \"[message]\"") - SScommunications.soft_filtering = TRUE + GLOB.communications_controller.soft_filtering = TRUE playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) @@ -364,13 +364,13 @@ GLOB.admins, span_adminnotice( \ "CROSS-SECTOR MESSAGE (OUTGOING): [ADMIN_LOOKUPFLW(usr)] is about to send \ - the following message to [destination] (will autoapprove in [SScommunications.soft_filtering ? DisplayTimeText(EXTENDED_CROSS_SECTOR_CANCEL_TIME) : DisplayTimeText(CROSS_SECTOR_CANCEL_TIME)]): \ + the following message to [destination] (will autoapprove in [GLOB.communications_controller.soft_filtering ? DisplayTimeText(EXTENDED_CROSS_SECTOR_CANCEL_TIME) : DisplayTimeText(CROSS_SECTOR_CANCEL_TIME)]): \ REJECT
\ [html_encode(message)]" \ ) ) - send_cross_comms_message_timer = addtimer(CALLBACK(src, PROC_REF(send_cross_comms_message), usr, destination, message), SScommunications.soft_filtering ? EXTENDED_CROSS_SECTOR_CANCEL_TIME : CROSS_SECTOR_CANCEL_TIME, TIMER_STOPPABLE) + send_cross_comms_message_timer = addtimer(CALLBACK(src, PROC_REF(send_cross_comms_message), usr, destination, message), GLOB.communications_controller.soft_filtering ? EXTENDED_CROSS_SECTOR_CANCEL_TIME : CROSS_SECTOR_CANCEL_TIME, TIMER_STOPPABLE) COOLDOWN_START(src, important_action_cooldown, IMPORTANT_ACTION_COOLDOWN) if ("setState") @@ -500,7 +500,7 @@ var/network_name = CONFIG_GET(string/cross_comms_network) if(network_name) payload["network"] = network_name - if(SScommunications.soft_filtering) + if(GLOB.communications_controller.soft_filtering) payload["is_filtered"] = TRUE send2otherserver(html_decode(station_name()), message, "Comms_Console", destination == "all" ? null : list(destination), additional_data = payload) @@ -508,7 +508,7 @@ usr.log_talk(message, LOG_SAY, tag = "message to the other server") message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server\[s].") deadchat_broadcast(" has sent an outgoing message to the other station(s).", "[usr.real_name]", usr, message_type = DEADCHAT_ANNOUNCEMENT) - SScommunications.soft_filtering = FALSE // set it to false at the end of the proc to ensure that everything prior reads as intended + GLOB.communications_controller.soft_filtering = FALSE // set it to false at the end of the proc to ensure that everything prior reads as intended /obj/machinery/computer/communications/ui_data(mob/user) var/list/data = list( @@ -670,7 +670,7 @@ return deltimer(send_cross_comms_message_timer) - SScommunications.soft_filtering = FALSE + GLOB.communications_controller.soft_filtering = FALSE send_cross_comms_message_timer = null log_admin("[key_name(usr)] has cancelled the outgoing cross-comms message.") @@ -743,7 +743,7 @@ /obj/machinery/computer/communications/proc/make_announcement(mob/living/user) var/is_ai = issilicon(user) - if(!SScommunications.can_announce(user, is_ai)) + if(!GLOB.communications_controller.can_announce(user, is_ai)) to_chat(user, span_alert("Intercomms recharging. Please stand by.")) return var/input = tgui_input_text(user, "Message to announce to the station crew", "Announcement") @@ -771,7 +771,7 @@ input += "\n\n - [authorize_job] [authorize_name]" var/list/players = get_communication_players() - SScommunications.make_announcement(user, is_ai, input, syndicate || (obj_flags & EMAGGED), players) + GLOB.communications_controller.make_announcement(user, is_ai, input, syndicate || (obj_flags & EMAGGED), players) deadchat_broadcast(" made a priority announcement from [span_name("[get_area_name(usr, TRUE)]")].", span_name("[user.real_name]"), user, message_type=DEADCHAT_ANNOUNCEMENT) /obj/machinery/computer/communications/proc/get_communication_players() @@ -815,7 +815,6 @@ #define HACK_PIRATE "Pirates" #define HACK_FUGITIVES "Fugitives" #define HACK_SLEEPER "Sleeper Agents" -#define HACK_THREAT "Threat Boost" /// The minimum number of ghosts / observers to have the chance of spawning pirates. #define MIN_GHOSTS_FOR_PIRATES 4 @@ -859,7 +858,7 @@ */ /obj/machinery/computer/communications/proc/hack_console(mob/living/hacker) // All hack results we'll choose from. - var/list/hack_options = list(HACK_THREAT) + var/list/hack_options = list(HACK_SLEEPER) // If we have a certain amount of ghosts, we'll add some more !!fun!! options to the list var/num_ghosts = length(GLOB.current_observers_list) + length(GLOB.dead_player_list) @@ -874,11 +873,6 @@ if(num_ghosts >= MIN_GHOSTS_FOR_FUGITIVES) hack_options += HACK_FUGITIVES - if (!EMERGENCY_PAST_POINT_OF_NO_RETURN) - // If less than a certain percent of the population is ghosts, consider sleeper agents - if(num_ghosts < (length(GLOB.clients) * MAX_PERCENT_GHOSTS_FOR_SLEEPER)) - hack_options += HACK_SLEEPER - var/picked_option = pick(hack_options) message_admins("[ADMIN_LOOKUPFLW(hacker)] hacked a [name] located at [ADMIN_VERBOSEJMP(src)], resulting in: [picked_option]!") hacker.log_message("hacked a communications console, resulting in: [picked_option].", LOG_GAME, log_globally = TRUE) @@ -886,59 +880,29 @@ if(HACK_PIRATE) // Triggers pirates, which the crew may be able to pay off to prevent var/list/pirate_rulesets = list( /datum/dynamic_ruleset/midround/pirates, - /datum/dynamic_ruleset/midround/dangerous_pirates, - ) - priority_announce( - "Attention crew: sector monitoring reports a massive jump-trace from an enemy vessel destined for your system. Prepare for imminent hostile contact.", - "[command_name()] High-Priority Update", + /datum/dynamic_ruleset/midround/pirates/heavy, ) - SSdynamic.picking_specific_rule(pick(pirate_rulesets), forced = TRUE, ignore_cost = TRUE) + SSdynamic.force_run_midround(pick(pirate_rulesets)) if(HACK_FUGITIVES) // Triggers fugitives, which can cause confusion / chaos as the crew decides which side help priority_announce( "Attention crew: sector monitoring reports a jump-trace from an unidentified vessel destined for your system. Prepare for probable contact.", "[command_name()] High-Priority Update", ) + SSdynamic.force_run_midround(/datum/dynamic_ruleset/midround/from_ghosts/fugitives) - force_event_after(/datum/round_event_control/fugitives, "[hacker] hacking a communications console", rand(20 SECONDS, 1 MINUTES)) - - if(HACK_THREAT) // Force an unfavorable situation on the crew + if(HACK_SLEEPER) // Trigger one or multiple sleeper agents with the crew (or for latejoining crew) priority_announce( - "Attention crew, the Nanotrasen Department of Intelligence has received intel suggesting increased enemy activity in your sector beyond that initially reported in today's threat advisory.", + "Attention crew, it appears that someone on your station has hijacked your telecommunications and broadcasted an unknown signal.", "[command_name()] High-Priority Update", ) - - for(var/mob/crew_member as anything in GLOB.player_list) - if(!is_station_level(crew_member.z)) - continue - shake_camera(crew_member, 15, 1) - - SSdynamic.unfavorable_situation() - - if(HACK_SLEEPER) // Trigger one or multiple sleeper agents with the crew (or for latejoining crew) - var/datum/dynamic_ruleset/midround/sleeper_agent_type = /datum/dynamic_ruleset/midround/from_living/autotraitor - var/max_number_of_sleepers = clamp(round(length(GLOB.alive_player_list) / 20), 1, 3) - var/num_agents_created = 0 - for(var/num_agents in 1 to rand(1, max_number_of_sleepers)) - if(!SSdynamic.picking_specific_rule(sleeper_agent_type, forced = TRUE, ignore_cost = TRUE)) - break - num_agents_created++ - - if(num_agents_created <= 0) - // We failed to run any midround sleeper agents, so let's be patient and run latejoin traitor - SSdynamic.picking_specific_rule(/datum/dynamic_ruleset/latejoin/infiltrator, forced = TRUE, ignore_cost = TRUE) - - else - // We spawned some sleeper agents, nice - give them a report to kickstart the paranoia - priority_announce( - "Attention crew, it appears that someone on your station has hijacked your telecommunications and broadcasted an unknown signal.", - "[command_name()] High-Priority Update", - ) + var/max_number_of_sleepers = clamp(round(length(GLOB.alive_player_list) / 40), 1, 3) + if(!SSdynamic.force_run_midround(/datum/dynamic_ruleset/midround/from_living/traitor, forced_max_cap = max_number_of_sleepers)) + SSdynamic.queue_ruleset(/datum/dynamic_ruleset/latejoin/traitor) #undef HACK_PIRATE #undef HACK_FUGITIVES #undef HACK_SLEEPER -#undef HACK_THREAT #undef MIN_GHOSTS_FOR_PIRATES #undef MIN_GHOSTS_FOR_FUGITIVES diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index 6be717f29aa6..24159e1b98a6 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -166,7 +166,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) SIGNAL_HANDLER for(var/datum/job/jobtype as anything in subtypesof(/datum/job)) - var/datum/job/job = SSjob.GetJobType(jobtype) + var/datum/job/job = SSjob.get_job_type(jobtype) if(isnull(job)) continue var/job_prio = isnum(job.crewmonitor_priority) ? job.crewmonitor_priority : jobs[job.title] diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 7fa0f24d628b..e51059657126 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -573,7 +573,7 @@ DEFINE_BITFIELD(turret_flags, list( // If we aren't shooting heads then return a threatcount of 0 if (!(turret_flags & TURRET_FLAG_SHOOT_HEADS)) - var/datum/job/apparent_job = SSjob.GetJob(perp.get_assignment()) + var/datum/job/apparent_job = SSjob.get_job(perp.get_assignment()) if(apparent_job?.job_flags & JOB_HEAD_OF_STAFF) return 0 diff --git a/code/game/machinery/wishgranter.dm b/code/game/machinery/wishgranter.dm index 7d03fc8efaeb..6bfbb57d4116 100644 --- a/code/game/machinery/wishgranter.dm +++ b/code/game/machinery/wishgranter.dm @@ -22,7 +22,7 @@ to_chat(user, span_boldnotice("You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.")) return - else if(is_special_character(user)) + else if(user.is_antag()) to_chat(user, span_boldnotice("Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.")) else if (!insisting) diff --git a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm index 87221eeec9fa..c403fb06cbf0 100644 --- a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm +++ b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm @@ -45,7 +45,6 @@ var/mob/dead/observer/chosen = pick(candidates) pyro.key = chosen.key - pyro.mind.special_role = ROLE_PYROCLASTIC_SLIME pyro.mind.add_antag_datum(/datum/antagonist/pyro_slime) pyro.log_message("was made into a slime by pyroclastic anomaly", LOG_GAME) diff --git a/code/game/objects/effects/poster_motivational.dm b/code/game/objects/effects/poster_motivational.dm index 1d5fa6b6ad2c..0e9444190af2 100644 --- a/code/game/objects/effects/poster_motivational.dm +++ b/code/game/objects/effects/poster_motivational.dm @@ -17,8 +17,8 @@ department_grab.quirk_poster_department = quirk_poster_department /// You can use any spraypaint can on a quirk poster to turn it into a contraband poster from the traitor objective -/obj/item/poster/quirk/attackby(obj/item/postertool, mob/user, params) - if(!is_special_character(user) || !HAS_TRAIT(user, TRAIT_POSTERBOY) || !istype(postertool, /obj/item/toy/crayon)) +/obj/item/poster/quirk/attackby(obj/item/postertool, mob/user, list/modifiers) + if(!user.is_antag() || !HAS_TRAIT(user, TRAIT_POSTERBOY) || !istype(postertool, /obj/item/toy/crayon)) return ..() balloon_alert(user, "converting poster...") if(!do_after(user, 5 SECONDS, user)) @@ -32,7 +32,7 @@ /// Screentip for the above /obj/item/poster/quirk/add_context(atom/source, list/context, obj/item/held_item, mob/user) - if(!is_special_character(user) || !HAS_TRAIT(user, TRAIT_POSTERBOY) || !istype(held_item, /obj/item/toy/crayon)) + if(!user.is_antag() || !HAS_TRAIT(user, TRAIT_POSTERBOY) || !istype(held_item, /obj/item/toy/crayon)) return NONE context[SCREENTIP_CONTEXT_LMB] = "Turn into Demoralizing Poster" return CONTEXTUAL_SCREENTIP_SET diff --git a/code/game/objects/effects/spawners/xeno_egg_delivery.dm b/code/game/objects/effects/spawners/xeno_egg_delivery.dm index b09a37f14b12..eb5bb62df5c5 100644 --- a/code/game/objects/effects/spawners/xeno_egg_delivery.dm +++ b/code/game/objects/effects/spawners/xeno_egg_delivery.dm @@ -25,5 +25,5 @@ /obj/structure/alien/egg/delivery/Initialize(mapload) . = ..() - SScommunications.xenomorph_egg_delivered = TRUE - SScommunications.captivity_area = get_area(src) + GLOB.communications_controller.xenomorph_egg_delivered = TRUE + GLOB.communications_controller.captivity_area = get_area(src) diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index d1d4856823b6..d88901dcf867 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -133,7 +133,7 @@ /obj/item/card/id/Initialize(mapload) . = ..() - var/datum/bank_account/blank_bank_account = new("Unassigned", SSjob.GetJobType(/datum/job/unassigned), player_account = FALSE) + var/datum/bank_account/blank_bank_account = new("Unassigned", SSjob.get_job_type(/datum/job/unassigned), player_account = FALSE) registered_account = blank_bank_account registered_account.replaceable = TRUE @@ -1398,7 +1398,7 @@ /obj/item/card/id/advanced/debug/Initialize(mapload) . = ..() registered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) - registered_account.account_job = SSjob.GetJobType(/datum/job/admin) // so we can actually use this account without being filtered as a "departmental" card + registered_account.account_job = SSjob.get_job_type(/datum/job/admin) // so we can actually use this account without being filtered as a "departmental" card /obj/item/card/id/advanced/prisoner name = "prisoner ID card" diff --git a/code/game/objects/items/devices/aicard_evil.dm b/code/game/objects/items/devices/aicard_evil.dm index 2e8e23ec5fee..5bfff8bfcf61 100644 --- a/code/game/objects/items/devices/aicard_evil.dm +++ b/code/game/objects/items/devices/aicard_evil.dm @@ -55,7 +55,7 @@ var/datum/antagonist/nukeop/nuke_datum = new() nuke_datum.send_to_spawnpoint = FALSE new_ai.mind.add_antag_datum(nuke_datum, op_datum.nuke_team) - new_ai.mind.special_role = "Syndicate AI" + LAZYADD(new_ai.mind.special_roles, "Syndicate AI") new_ai.faction |= ROLE_SYNDICATE // Make it look evil!!! new_ai.set_hologram_appearance(mutable_appearance('icons/mob/silicon/ai.dmi', "xeno_queen")) //good enough diff --git a/code/game/objects/items/religion.dm b/code/game/objects/items/religion.dm index 40d17f415b2b..60f83e29f357 100644 --- a/code/game/objects/items/religion.dm +++ b/code/game/objects/items/religion.dm @@ -49,7 +49,7 @@ if(H.mind && (has_job_loyalties || has_role_loyalties)) if(has_job_loyalties && (H.mind.assigned_role.departments_bitflags & job_loyalties)) inspired += H - else if(has_role_loyalties && (H.mind.special_role in role_loyalties)) + else if(has_role_loyalties && length(H.mind.get_special_roles() & role_loyalties)) inspired += H else if(check_inspiration(H)) inspired += H diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm index 93c94033cb2a..be5d494e39a4 100644 --- a/code/game/objects/items/robot/robot_parts.dm +++ b/code/game/objects/items/robot/robot_parts.dm @@ -321,7 +321,7 @@ brainmob.mind.transfer_to(O) playsound(O.loc, 'sound/voice/liveagain.ogg', 75, TRUE) - if(O.mind && O.mind.special_role) + if(O.is_antag()) to_chat(O, span_userdanger("You have been robotized!")) to_chat(O, span_danger("You must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.")) diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 7d4d834fea9f..bc950cd5f229 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -757,13 +757,7 @@ human_target.reagents.add_reagent(/datum/reagent/toxin, 2) return FALSE - /// If all the antag datums are 'fake' or none exist, disallow induction! No self-antagging. - var/faker - for(var/datum/antagonist/antag_datum as anything in human_target.mind.antag_datums) - if((antag_datum.antag_flags & FLAG_FAKE_ANTAG)) - faker = TRUE - - if(faker || isnull(human_target.mind.antag_datums)) // GTFO. Technically not foolproof but making a heartbreaker or a paradox clone a nuke op sounds hilarious + if(!human_target.is_antag()) // GTFO. Technically not foolproof but making a heartbreaker or a paradox clone a nuke op sounds hilarious to_chat(human_target, span_notice("Huh? Nothing happened? But you're starting to feel a little ill...")) human_target.reagents.add_reagent(/datum/reagent/toxin, 15) return FALSE diff --git a/code/game/say.dm b/code/game/say.dm index 8b0601c9c550..d157ed720dba 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -320,7 +320,7 @@ GLOBAL_LIST_INIT(freqtospan, list( //HACKY VIRTUALSPEAKER STUFF BEYOND THIS POINT //these exist mostly to deal with the AIs hrefs and job stuff. -/atom/movable/proc/GetJob() //Get a job, you lazy butte +/atom/movable/proc/get_job() //Get a job, you lazy butte /atom/movable/proc/GetSource() @@ -367,7 +367,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/virtualspeaker) else // Unidentifiable mob job = "Unknown" -/atom/movable/virtualspeaker/GetJob() +/atom/movable/virtualspeaker/get_job() return job /atom/movable/virtualspeaker/GetSource() diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 571e23d3a6b8..0ab6120f5526 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -20,19 +20,9 @@ return var/dat - if(SSticker.current_state <= GAME_STATE_PREGAME) - dat += "(Manage Dynamic Rulesets)
" - dat += "(Force Roundstart Rulesets)
" - if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) - for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) - dat += {"-> [rule.name] <-
"} - dat += "(Clear Rulesets)
" - dat += "(Dynamic mode options)
" - dat += "
" - if(SSticker.IsRoundInProgress()) - dat += "(Game Mode Panel)
" - dat += "(Manage Dynamic Rulesets)
" - dat += "
" + + dat += "Dynamic Panel
" + dat += "
" dat += {" Create Object
Quick Create Object
@@ -105,134 +95,6 @@ ADMIN_VERB(spawn_cargo, R_SPAWN, "Spawn Cargo", "Spawn a cargo crate.", ADMIN_CA log_admin("[key_name(user)] spawned cargo pack [chosen] at [AREACOORD(user.mob)]") BLACKBOX_LOG_ADMIN_VERB("Spawn Cargo") -/datum/admins/proc/dynamic_mode_options(mob/user) - var/dat = {"

Common options

- All these options can be changed midround.
-
- Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"]. -
This will force the round to be extended. No rulesets will be drafted.
-
- No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"]. -
Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.
-
- Forced threat level: Current value : [GLOB.dynamic_forced_threat_level]. -
The value threat is set to if it is higher than -1.
-
-
- Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit]. -
The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
- "} - - var/datum/browser/browser = new(user, "dyn_mode_options", "Dynamic Mode Options", 900, 650) - browser.set_content(dat) - browser.open() - -/datum/admins/proc/dynamic_ruleset_manager(mob/user) - var/datum/browser/browser = new(user, "dyn_mode_options", "Dynamic Ruleset Management", 900, 650) - var/dat = {" - Change these options to forcibly enable or disable dynamic rulesets.
- Disabled rulesets will never run, even if they would otherwise be valid.
- Enabled rulesets will run even if the qualifying minimum of threat or player count is not present, this does not guarantee that they will necessarily be chosen (for example their weight may be set to 0 in config).
- force enable all - force disable all - reset all - "} - - if (SSticker.current_state <= GAME_STATE_PREGAME) // Don't bother displaying after the round has started - var/static/list/rulesets_by_context = list() - if (!length(rulesets_by_context)) - for (var/datum/dynamic_ruleset/rule as anything in subtypesof(/datum/dynamic_ruleset)) - if (initial(rule.name) == "") - continue - LAZYADD(rulesets_by_context[initial(rule.ruletype)], rule) - - dat += dynamic_ruleset_category_pre_start_display("Roundstart", rulesets_by_context[ROUNDSTART_RULESET]) - dat += dynamic_ruleset_category_pre_start_display("Latejoin", rulesets_by_context[LATEJOIN_RULESET]) - dat += dynamic_ruleset_category_pre_start_display("Midround", rulesets_by_context[MIDROUND_RULESET]) - browser.set_content(dat) - browser.open() - return - - var/pop_count = length(GLOB.alive_player_list) - var/threat_level = SSdynamic.threat_level - dat += dynamic_ruleset_category_during_round_display("Latejoin", SSdynamic.latejoin_rules, pop_count, threat_level) - dat += dynamic_ruleset_category_during_round_display("Midround", SSdynamic.midround_rules, pop_count, threat_level) - browser.set_content(dat) - browser.open() - -/datum/admins/proc/dynamic_ruleset_category_pre_start_display(title, list/rules) - var/dat = "

[title]

" - for (var/datum/dynamic_ruleset/rule as anything in rules) - var/forced = GLOB.dynamic_forced_rulesets[rule] || RULESET_NOT_FORCED - var/color = COLOR_SILVER - switch (forced) - if (RULESET_FORCE_ENABLED) - color = COLOR_GREEN - if (RULESET_FORCE_DISABLED) - color = COLOR_RED - dat += "" - dat += "
[initial(rule.name)]\[ [forced] \] \ - force enabled \ - force disabled \ - reset
" - return dat - -/datum/admins/proc/dynamic_ruleset_category_during_round_display(title, list/rules, pop_count, threat_level) - var/dat = "

[title]

" - for (var/datum/dynamic_ruleset/rule as anything in rules) - var/active = rule.acceptable(population = pop_count, threat_level = threat_level) && rule.weight > 0 - var/forced = GLOB.dynamic_forced_rulesets[rule.type] || RULESET_NOT_FORCED - var/color = (active) ? COLOR_GREEN : COLOR_RED - var/explanation = "" - if (!active) - if (rule.weight <= 0) - explanation = " - Weight is zero" - else if (forced == RULESET_FORCE_DISABLED) - explanation = " - Forcibly disabled" - else if (forced == RULESET_FORCE_ENABLED) - explanation = " - Failed spawn conditions" - else if (!rule.is_valid_population(pop_count)) - explanation = " - Invalid player count" - else if (!rule.is_valid_threat(pop_count, threat_level)) - explanation = " - Insufficient threat" - else - explanation = " - Failed spawn conditions" - else if (forced == RULESET_FORCE_ENABLED) - explanation = " - Forcibly enabled" - active = active ? "Active" : "Inactive" - - dat += {" - - - "} - dat += "
[rule.name]\[ Weight: [rule.weight] \] - \[ [active][explanation] \] - force enabled - force disabled - resetVV
" - return dat - - -/datum/admins/proc/force_all_rulesets(mob/user, force_value) - if (force_value == RULESET_NOT_FORCED) - GLOB.dynamic_forced_rulesets = list() - else - for (var/datum/dynamic_ruleset/rule as anything in subtypesof(/datum/dynamic_ruleset)) - GLOB.dynamic_forced_rulesets[rule] = force_value - var/logged_message = "[key_name(user)] set all dynamic rulesets to [force_value]." - log_admin(logged_message) - message_admins(logged_message) - dynamic_ruleset_manager(user) - -/datum/admins/proc/set_dynamic_ruleset_forced(mob/user, datum/dynamic_ruleset/type, force_value) - if (isnull(type)) - return - GLOB.dynamic_forced_rulesets[type] = force_value - dynamic_ruleset_manager(user) - var/logged_message = "[key_name(user)] set '[initial(type.name)] ([initial(type.ruletype)])' to [GLOB.dynamic_forced_rulesets[type]]." - log_admin(logged_message) - message_admins(logged_message) - ADMIN_VERB(create_or_modify_area, R_DEBUG, "Create Or Modify Area", "Create of modify an area. wow.", ADMIN_CATEGORY_DEBUG) create_area(user.mob) @@ -292,14 +154,16 @@ ADMIN_VERB(create_or_modify_area, R_DEBUG, "Create Or Modify Area", "Create of m return TRUE -/client/proc/adminGreet(logout) - if(SSticker.HasRoundStarted()) - var/string - if(logout && CONFIG_GET(flag/announce_admin_logout)) - string = pick( - "Admin logout: [key_name(src)]") - else if(!logout && CONFIG_GET(flag/announce_admin_login) && (prefs.toggles & ANNOUNCE_LOGIN)) - string = pick( - "Admin login: [key_name(src)]") - if(string) - message_admins("[string]") +/// Sends a message to adminchat when anyone with a holder logs in or logs out. +/// Is dependent on admin preferences and configuration settings, which means that this proc can fire without sending a message. +/client/proc/adminGreet(logout = FALSE) + if(!SSticker.HasRoundStarted()) + return + + if(logout && CONFIG_GET(flag/announce_admin_logout)) + message_admins("Admin logout: [key_name(src)]") + return + + if(!logout && CONFIG_GET(flag/announce_admin_login) && (prefs.toggles & ANNOUNCE_LOGIN)) + message_admins("Admin login: [key_name(src)]") + return diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index f772e562da22..6f1f4f63c7a7 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -521,7 +521,7 @@ ADMIN_VERB(spawn_debug_full_crew, R_DEBUG, "Spawn Debug Full Crew", "Creates a f // Then, spawn a human and slap a person into it. var/number_made = 0 for(var/rank in SSjob.name_occupations) - var/datum/job/job = SSjob.GetJob(rank) + var/datum/job/job = SSjob.get_job(rank) // JOB_CREW_MEMBER is all jobs that pretty much aren't silicon if(!(job.job_flags & JOB_CREW_MEMBER)) @@ -533,7 +533,7 @@ ADMIN_VERB(spawn_debug_full_crew, R_DEBUG, "Spawn Debug Full Crew", "Creates a f new_guy.mind.name = "[rank] Dummy" // Assign the rank to the new player dummy. - if(!SSjob.AssignRole(new_guy, job, do_eligibility_checks = FALSE)) + if(!SSjob.assign_role(new_guy, job, do_eligibility_checks = FALSE)) qdel(new_guy) to_chat(user, "[rank] wasn't able to be spawned.") continue diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm index edb3b0831f8a..2473d406e8eb 100644 --- a/code/modules/admin/antag_panel.dm +++ b/code/modules/admin/antag_panel.dm @@ -74,18 +74,29 @@ GLOBAL_VAR(antag_prototypes) break return common_commands +/** + * Returns a list of "statuses" this mind has - like "Infected", "Mindshielded", etc + */ /datum/mind/proc/get_special_statuses() var/list/result = LAZYCOPY(special_statuses) if(!current) result += "No body!" if(current && HAS_TRAIT(current, TRAIT_MINDSHIELD)) - result += "Mindshielded" - //Move these to mob + result += span_good("Mindshielded") + if(current && HAS_MIND_TRAIT(current, TRAIT_UNCONVERTABLE)) + result += span_good("Unconvertable") + return result + +/** + * Returns a list of "roles" this mind has - like "Traitor", "Ex Head Rev", "Emagged", etc + */ +/datum/mind/proc/get_special_roles() + var/list/roles = LAZYCOPY(special_roles) if(iscyborg(current)) var/mob/living/silicon/robot/robot = current if (robot.emagged) - result += "Emagged" - return result.Join(" | ") + roles += "Emagged" + return roles /datum/mind/proc/traitor_panel() if(!SSticker.HasRoundStarted()) @@ -98,12 +109,11 @@ GLOBAL_VAR(antag_prototypes) var/out = "[name][(current && (current.real_name != name))?" (as [current.real_name])":""]
" out += "Mind currently owned by key: [key] [active?"(synced)":"(not synced)"]
" out += "Assigned role: [assigned_role.title]. Edit
" - out += "Faction and special role: [special_role]
" out += "Show Teams

" - var/special_statuses = get_special_statuses() + var/special_statuses = get_special_roles() | get_special_statuses() if(length(special_statuses)) - out += get_special_statuses() + "
" + out += "Roles: [jointext(special_statuses, " | ")]
" if(!GLOB.antag_prototypes) GLOB.antag_prototypes = list() @@ -165,7 +175,7 @@ GLOBAL_VAR(antag_prototypes) continue pref_source = prototype break - if(pref_source.job_rank) + if(pref_source.pref_flag) antag_header_parts += pref_source.enabled_in_preferences(src) ? "Enabled in Prefs" : "Disabled in Prefs" //Traitor : None | Traitor | IAA diff --git a/code/modules/admin/check_antagonists.dm b/code/modules/admin/check_antagonists.dm index 30071504c919..2931bdfad682 100644 --- a/code/modules/admin/check_antagonists.dm +++ b/code/modules/admin/check_antagonists.dm @@ -95,7 +95,7 @@ tgui_alert(usr, "The game hasn't started yet!") return var/list/dat = list("Round Status

Round Status

") - dat += "Game Mode Panel
" + dat += "Dynamic Panel
" dat += "Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
" dat += "Emergency shuttle
" if(EMERGENCY_IDLE_OR_RECALLED) @@ -152,7 +152,7 @@ if (checked_mob.client) observers_connected++ - if(checked_mob.mind.special_role) + if(checked_mob.is_antag()) antagonists++ if(checked_mob.stat == DEAD) antagonists_dead++ diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index a56fe9daf8da..00f93ac84bc5 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -228,7 +228,7 @@ var/color = "#e6e6e6" if(i%2 == 0) color = "#f2f2f2" - var/is_antagonist = is_special_character(M, allow_fake_antags = TRUE) + var/is_antagonist = M.is_antag(NONE) var/M_job = "" diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index d282ea47307e..f7380b1fc266 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -67,9 +67,7 @@ edit_rights_topic(href_list) else if(href_list["gamemode_panel"]) - if(!check_rights(R_ADMIN)) - return - SSdynamic.admin_panel() + dynamic_panel(usr) else if(href_list["call_shuttle"]) if(!check_rights(R_ADMIN)) @@ -388,122 +386,6 @@ return cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) - else if(href_list["f_dynamic_roundstart"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker.HasRoundStarted()) - return tgui_alert(usr, "The game has already started.") - var/roundstart_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) - var/datum/dynamic_ruleset/roundstart/newrule = new rule() - roundstart_rules[newrule.name] = newrule - var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in sort_list(roundstart_rules) - if (added_rule) - GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule] - log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.") - message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1) - Game() - - else if(href_list["f_dynamic_roundstart_clear"]) - if(!check_rights(R_ADMIN)) - return - GLOB.dynamic_forced_roundstart_ruleset = list() - Game() - log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.") - message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1) - - else if(href_list["f_dynamic_roundstart_remove"]) - if(!check_rights(R_ADMIN)) - return - var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"]) - GLOB.dynamic_forced_roundstart_ruleset -= rule - Game() - log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.") - message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1) - - else if (href_list["f_dynamic_ruleset_manage"]) - if(!check_rights(R_ADMIN)) - return - dynamic_ruleset_manager(usr) - else if (href_list["f_dynamic_ruleset_force_all_on"]) - if(!check_rights(R_ADMIN)) - return - force_all_rulesets(usr, RULESET_FORCE_ENABLED) - else if (href_list["f_dynamic_ruleset_force_all_off"]) - if(!check_rights(R_ADMIN)) - return - force_all_rulesets(usr, RULESET_FORCE_DISABLED) - else if (href_list["f_dynamic_ruleset_force_all_reset"]) - if(!check_rights(R_ADMIN)) - return - force_all_rulesets(usr, RULESET_NOT_FORCED) - else if (href_list["f_dynamic_ruleset_force_on"]) - if(!check_rights(R_ADMIN)) - return - set_dynamic_ruleset_forced(usr, locate(href_list["f_dynamic_ruleset_force_on"]), RULESET_FORCE_ENABLED) - else if (href_list["f_dynamic_ruleset_force_off"]) - if(!check_rights(R_ADMIN)) - return - set_dynamic_ruleset_forced(usr, locate(href_list["f_dynamic_ruleset_force_off"]), RULESET_FORCE_DISABLED) - else if (href_list["f_dynamic_ruleset_force_reset"]) - if(!check_rights(R_ADMIN)) - return - set_dynamic_ruleset_forced(usr, locate(href_list["f_dynamic_ruleset_force_reset"]), RULESET_NOT_FORCED) - else if (href_list["f_inspect_ruleset"]) - if(!check_rights(R_ADMIN)) - return - usr.client.debug_variables(locate(href_list["f_inspect_ruleset"])) - - else if (href_list["f_dynamic_options"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return tgui_alert(usr, "The game has already started.") - - dynamic_mode_options(usr) - else if(href_list["f_dynamic_force_extended"]) - if(!check_rights(R_ADMIN)) - return - - GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended - log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_no_stacking"]) - if(!check_rights(R_ADMIN)) - return - - GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking - log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - dynamic_mode_options(usr) - else if(href_list["f_dynamic_stacking_limit"]) - if(!check_rights(R_ADMIN)) - return - - GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num - log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_forced_threat"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return tgui_alert(usr, "The game has already started.") - - var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num - if (new_value > 100) - return tgui_alert(usr, "The value must be be under 100.") - GLOB.dynamic_forced_threat_level = new_value - - log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - dynamic_mode_options(usr) - else if(href_list["forcespeech"]) if(!check_rights(R_FUN)) return diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm index f947f3e118b1..f96d67a198b3 100644 --- a/code/modules/admin/verbs/adminevents.dm +++ b/code/modules/admin/verbs/adminevents.dm @@ -263,11 +263,11 @@ ADMIN_VERB(run_weather, R_FUN, "Run Weather", "Triggers specific weather on the ADMIN_VERB(command_report_footnote, R_ADMIN, "Command Report Footnote", "Adds a footnote to the roundstart command report.", ADMIN_CATEGORY_EVENTS) var/datum/command_footnote/command_report_footnote = new /datum/command_footnote() - SScommunications.block_command_report += 1 //Add a blocking condition to the counter until the inputs are done. + GLOB.communications_controller.block_command_report += 1 //Add a blocking condition to the counter until the inputs are done. command_report_footnote.message = tgui_input_text(user, "This message will be attached to the bottom of the roundstart threat report. Be sure to delay the roundstart report if you need extra time.", "P.S.") if(!command_report_footnote.message) - SScommunications.block_command_report -= 1 + GLOB.communications_controller.block_command_report -= 1 qdel(command_report_footnote) return @@ -276,8 +276,8 @@ ADMIN_VERB(command_report_footnote, R_ADMIN, "Command Report Footnote", "Adds a if(!command_report_footnote.signature) command_report_footnote.signature = "Classified" - SScommunications.command_report_footnotes += command_report_footnote - SScommunications.block_command_report-- + GLOB.communications_controller.command_report_footnotes += command_report_footnote + GLOB.communications_controller.block_command_report-- message_admins("[user] has added a footnote to the command report: [command_report_footnote.message], signed [command_report_footnote.signature]") @@ -286,5 +286,5 @@ ADMIN_VERB(command_report_footnote, R_ADMIN, "Command Report Footnote", "Adds a var/signature ADMIN_VERB(delay_command_report, R_FUN, "Delay Command Report", "Prevents the roundstart command report from being sent; or forces it to send it delayed.", ADMIN_CATEGORY_EVENTS) - SScommunications.block_command_report = !SScommunications.block_command_report - message_admins("[key_name_admin(user)] has [(SScommunications.block_command_report ? "delayed" : "sent")] the roundstart command report.") + GLOB.communications_controller.block_command_report = !GLOB.communications_controller.block_command_report + message_admins("[key_name_admin(user)] has [(GLOB.communications_controller.block_command_report ? "delayed" : "sent")] the roundstart command report.") diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm index 104b6fc1874d..d28622de0b65 100644 --- a/code/modules/admin/verbs/admingame.dm +++ b/code/modules/admin/verbs/admingame.dm @@ -225,7 +225,7 @@ ADMIN_VERB(respawn_character, R_ADMIN, "Respawn Character", "Respawn a player th else new_character.mind_initialize() if(is_unassigned_job(new_character.mind.assigned_role)) - new_character.mind.set_assigned_role(SSjob.GetJobType(SSjob.overflow_role)) + new_character.mind.set_assigned_role(SSjob.get_job_type(SSjob.overflow_role)) new_character.key = G_found.key @@ -245,33 +245,14 @@ ADMIN_VERB(respawn_character, R_ADMIN, "Respawn Character", "Respawn a player th SSjob.EquipRank(new_character, new_character.mind.assigned_role, new_character.client) new_character.mind.give_uplink(silent = TRUE, antag_datum = traitordatum) - switch(new_character.mind.special_role) - if(ROLE_WIZARD) - new_character.forceMove(pick(GLOB.wizardstart)) - var/datum/antagonist/wizard/A = new_character.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) - A.equip_wizard() - if(ROLE_SYNDICATE) - new_character.forceMove(pick(GLOB.nukeop_start)) - var/datum/antagonist/nukeop/N = new_character.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) - N.equip_op() - if(ROLE_NINJA) - var/list/ninja_spawn = list() - for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) - ninja_spawn += L - var/datum/antagonist/ninja/ninjadatum = new_character.mind.has_antag_datum(/datum/antagonist/ninja) - ninjadatum.equip_space_ninja() - if(ninja_spawn.len) - new_character.forceMove(pick(ninja_spawn)) - - else//They may also be a cyborg or AI. - switch(new_character.mind.assigned_role.type) - if(/datum/job/cyborg)//More rigging to make em' work and check if they're traitor. - new_character = new_character.Robotize(TRUE) - if(/datum/job/ai) - new_character = new_character.AIize() - else - if(!traitordatum) // Already equipped there. - SSjob.EquipRank(new_character, new_character.mind.assigned_role, new_character.client)//Or we simply equip them. + var/skip_job_respawn = FALSE + for(var/datum/antagonist/antag as anything in new_character.mind.antag_datums) + skip_job_respawn ||= antag.on_respawn(new_character) + if(skip_job_respawn) + break + + if(!skip_job_respawn) + new_character.mind.assigned_role.on_respawn(new_character) //Announces the character on all the systems, based on the record. if(!record_found && (new_character.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 26e46a89c70f..4ec275fc48f5 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -1017,9 +1017,7 @@ GLOBAL_DATUM_INIT(admin_help_ui_handler, /datum/admin_help_ui_handler, new) mobs_found += found if(!ai_found && isAI(found)) ai_found = 1 - var/is_antag = 0 - if(is_special_character(found)) - is_antag = 1 + var/is_antag = found.is_antag() founds += "Name: [found.name]([found.real_name]) Key: [found.key] Ckey: [found.ckey] [is_antag ? "(Antag)" : null] " msg += "[original_word](?|F) " continue diff --git a/code/modules/admin/verbs/ert.dm b/code/modules/admin/verbs/ert.dm index e067c96889bf..a5232ca53336 100644 --- a/code/modules/admin/verbs/ert.dm +++ b/code/modules/admin/verbs/ert.dm @@ -242,7 +242,7 @@ ert_antag.random_names = ertemplate.random_names ert_operative.mind.add_antag_datum(ert_antag,ert_team) - ert_operative.mind.set_assigned_role(SSjob.GetJobType(ert_antag.ert_job_path)) + ert_operative.mind.set_assigned_role(SSjob.get_job_type(ert_antag.ert_job_path)) //Logging and cleanup ert_operative.log_message("has been selected as \a [ert_antag.name].", LOG_GAME) diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index 89b89829b0f3..d0d82243f35c 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -88,7 +88,7 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w if("infinite_sec") if(!is_debugger) return - var/datum/job/sec_job = SSjob.GetJobType(/datum/job/security_officer) + var/datum/job/sec_job = SSjob.get_job_type(/datum/job/security_officer) sec_job.total_positions = -1 sec_job.spawn_positions = -1 message_admins("[key_name_admin(holder)] has removed the cap on security officers.") diff --git a/code/modules/admin/view_variables/admin_delete.dm b/code/modules/admin/view_variables/admin_delete.dm index 5ef04b351cce..1d5b621b70d5 100644 --- a/code/modules/admin/view_variables/admin_delete.dm +++ b/code/modules/admin/view_variables/admin_delete.dm @@ -20,6 +20,9 @@ var/turf/T = D T.ScrapeAway() else + if(ismob(D)) + var/mob/M = D + M.ghostize(FALSE) vv_update_display(D, "deleted", VV_MSG_DELETED) qdel(D) if(!QDELETED(D)) diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index c679fd45dfa1..0d551a2ba157 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -10,18 +10,14 @@ GLOBAL_LIST_EMPTY(antagonists) var/roundend_category = "other antagonists" ///Set to false to hide the antagonists from roundend report var/show_in_roundend = TRUE - ///If false, the roundtype will still convert with this antag active - var/prevent_roundtype_conversion = TRUE ///Mind that owns this datum var/datum/mind/owner ///Silent will prevent the gain/lose texts to show var/silent = FALSE - ///Whether or not the person will be able to have more than one datum - var/can_coexist_with_others = TRUE - ///List of datums this type can't coexist with - var/list/typecache_datum_blacklist = list() - ///The define string we use to identify the role for bans/player polls to spawn a random new one in. - var/job_rank + /// What flag is checked for jobbans and polling? Optional, if unset, will use pref_flag + var/jobban_flag + /// What flag to check for prefs? Required for antags with preferences associated + var/pref_flag ///Should replace jobbanned player with ghosts if granted. var/replace_banned = TRUE ///List of the objective datums that this role currently has, completing all objectives at round-end will cause this antagonist to greentext. @@ -38,8 +34,6 @@ GLOBAL_LIST_EMPTY(antagonists) var/hud_icon = 'icons/mob/huds/antag_hud.dmi' ///Name of the antag hud we provide to this mob. var/antag_hud_name - /// If set to true, the antag will not be added to the living antag list. - var/count_against_dynamic_roll_chance = TRUE /// The battlecry this antagonist shouts when suiciding with C4/X4. var/suicide_cry = "" //Antag panel properties @@ -61,6 +55,8 @@ GLOBAL_LIST_EMPTY(antagonists) var/default_custom_objective = "Cause chaos on the space station." /// Whether we give a hardcore random bonus for greentexting as this antagonist while playing hardcore random var/hardcore_random_bonus = FALSE + /// A path to the audio stinger that plays upon gaining this datum. + // var/stinger_sound //ANTAG UI @@ -74,7 +70,6 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/New() GLOB.antagonists += src - typecache_datum_blacklist = typecacheof(typecache_datum_blacklist) /datum/antagonist/Destroy() GLOB.antagonists -= src @@ -175,12 +170,7 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/can_be_owned(datum/mind/new_owner) var/datum/mind/tested = new_owner || owner - if(tested.has_antag_datum(type)) - return FALSE - for(var/datum/antagonist/badguy as anything in tested.antag_datums) - if(is_type_in_typecache(src, badguy.typecache_datum_blacklist)) - return FALSE - return TRUE + return !tested.has_antag_datum(type) //This will be called in add_antag_datum before owner assignment. //Should return antag datum without owner. @@ -198,7 +188,7 @@ GLOBAL_LIST_EMPTY(antagonists) info_button.Remove(old_body) info_button.Grant(new_body) apply_innate_effects(new_body) - if(count_against_dynamic_roll_chance && new_body.stat != DEAD) + if(new_body.stat != DEAD) new_body.add_to_current_living_antags() //This handles the application of antag huds/special abilities @@ -266,9 +256,14 @@ GLOBAL_LIST_EMPTY(antagonists) replace_banned_player() else if(owner.current.client?.holder && (CONFIG_GET(flag/auto_deadmin_antagonists) || owner.current.client.prefs?.toggles & DEADMIN_ANTAGONIST)) owner.current.client.holder.auto_deadmin() - if(count_against_dynamic_roll_chance && owner.current.stat != DEAD && owner.current.client) + if(owner.current.stat != DEAD && owner.current.client) owner.current.add_to_current_living_antags() + // for (var/datum/atom_hud/alternate_appearance/basic/antag_hud as anything in GLOB.active_alternate_appearances) + // antag_hud.apply_to_new_mob(owner.current) + + LAZYADD(owner.special_roles, (jobban_flag || pref_flag)) + SEND_SIGNAL(owner, COMSIG_ANTAGONIST_GAINED, src) /** @@ -285,7 +280,7 @@ GLOBAL_LIST_EMPTY(antagonists) if(!player.ckey) return FALSE - return (is_banned_from(player.ckey, list(ROLE_SYNDICATE, job_rank)) || QDELETED(player)) + return (is_banned_from(player.ckey, list(ROLE_SYNDICATE, jobban_flag || pref_flag)) || QDELETED(player)) /** * Proc that replaces a player who cannot play a specific antagonist due to being banned via a poll, and alerts the player of their being on the banlist. @@ -293,9 +288,8 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/replace_banned_player() set waitfor = FALSE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [name]?", check_jobban = "[name]", role = job_rank, poll_time = 5 SECONDS, target_mob = owner.current, pic_source = owner.current, role_name_text = name) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = jobban_flag || pref_flag, role = pref_flag, poll_time = 5 SECONDS, checked_target = owner.current, alert_pic = owner.current, role_name_text = name) + if(chosen_one) to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner)]) to replace a jobbanned player.") owner.current.ghostize(FALSE) @@ -323,6 +317,7 @@ GLOBAL_LIST_EMPTY(antagonists) var/datum/team/team = get_team() if(team) team.remove_member(owner) + LAZYREMOVE(owner.special_roles, (jobban_flag || pref_flag)) SEND_SIGNAL(owner, COMSIG_ANTAGONIST_REMOVED, src) // Remove HUDs that they should no longer see @@ -442,8 +437,8 @@ GLOBAL_LIST_EMPTY(antagonists) return "" /datum/antagonist/proc/enabled_in_preferences(datum/mind/noggin) - if(job_rank) - if(noggin.current && noggin.current.client && (job_rank in noggin.current.client.prefs.be_special)) + if(pref_flag) + if(noggin.current && noggin.current.client && (pref_flag in noggin.current.client.prefs.be_special)) return TRUE else return FALSE @@ -597,3 +592,7 @@ GLOBAL_LIST_EMPTY(antagonists) owner.announce_objectives() #undef CUSTOM_OBJECTIVE_MAX_LENGTH + +/// Return TRUE to prevent the antag's job from handling the respawn +/datum/antagonist/proc/on_respawn(mob/new_character) + return FALSE diff --git a/code/modules/antagonists/_common/antag_helpers.dm b/code/modules/antagonists/_common/antag_helpers.dm index 6ccaa80e55fd..de3ad9865e75 100644 --- a/code/modules/antagonists/_common/antag_helpers.dm +++ b/code/modules/antagonists/_common/antag_helpers.dm @@ -10,9 +10,10 @@ . |= A.owner /// From a list of players (minds, mobs or clients), finds the one with the highest playtime (either from a specific role or overall living) and returns it. +/// If playtime tracking is disabled, just returns the first player in the list. /proc/get_most_experienced(list/players, specific_role) if(!CONFIG_GET(flag/use_exp_tracking)) //woops - return + return players[1] var/most_experienced for(var/player in players) if(!most_experienced) diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 4998f7e198b2..0b5cb174c82e 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -84,8 +84,7 @@ app.wiz_team = master_wizard.wiz_team master_wizard.wiz_team.add_member(app_mind) app_mind.add_antag_datum(app) - app_mind.set_assigned_role(SSjob.GetJobType(/datum/job/wizard_apprentice)) - app_mind.special_role = ROLE_WIZARD_APPRENTICE + app_mind.set_assigned_role(SSjob.get_job_type(/datum/job/wizard_apprentice)) SEND_SOUND(M, sound('sound/effects/magic.ogg')) ///////////BORGS AND OPERATIVES @@ -101,7 +100,7 @@ icon_state = "nukietalkie" var/borg_to_spawn /// The name of the special role given to the recruit - var/special_role_name = ROLE_NUCLEAR_OPERATIVE + var/special_role_name = ROLE_OPERATIVE /// The applied outfit var/datum/outfit/syndicate/outfit = /datum/outfit/syndicate/reinforcement /// The outfit given to plasmaman operatives @@ -163,8 +162,14 @@ antag_datum.nukeop_outfit = use_subtypes ? pick(subtypesof(outfit)) : outfit var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop, TRUE) - op_mind.add_antag_datum(antag_datum, creator_op ? creator_op.get_team() : null) - op_mind.special_role = special_role_name + op_mind.add_antag_datum(new_datum, creator_op?.get_team()) + LAZYADD(op_mind.special_roles, special_role_name) + + if(outfit) + var/datum/antagonist/nukeop/nukie_datum = op_mind.has_antag_datum(antag_datum) + nukie_datum.nukeop_outfit = use_subtypes ? pick(subtypesof(outfit)) : outfit + + var/obj/structure/closet/supplypod/pod = setup_pod() nukie.forceMove(pod) new /obj/effect/pod_landingzone(get_turf(src), pod) @@ -227,10 +232,8 @@ borg.key = C.key - var/datum/antagonist/nukeop/new_borg = new() - new_borg.send_to_spawnpoint = FALSE - borg.mind.add_antag_datum(new_borg,creator_op.nuke_team) - borg.mind.special_role = "Syndicate Cyborg" + borg.mind.add_antag_datum(antag_datum, creator_op?.get_team()) + LAZYADD(borg.mind.special_roles, special_role_name) borg.forceMove(pod) new /obj/effect/pod_landingzone(get_turf(src), pod) @@ -366,7 +369,7 @@ human_mob.set_species(species_type) human_mob.equipOutfit(outfit) - op_mind.special_role = role_to_play + LAZYADD(op_mind.special_roles, role_to_play) do_special_things(spawned_mob, user) @@ -417,4 +420,3 @@ internals_slot = NONE belt = /obj/item/lighter/skull r_hand = /obj/item/food/grown/banana - diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index df0a93b4f07c..8d62cf16d7a9 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -2,7 +2,7 @@ name = "\improper Abductor" roundend_category = "abductors" antagpanel_category = ANTAG_GROUP_ABDUCTORS - job_rank = ROLE_ABDUCTOR + pref_flag = ROLE_ABDUCTOR antag_hud_name = "abductor" show_in_antagpanel = FALSE //should only show subtypes show_to_ghosts = TRUE @@ -69,16 +69,15 @@ return team /datum/antagonist/abductor/on_gain() - owner.set_assigned_role(SSjob.GetJobType(role_job)) - owner.special_role = ROLE_ABDUCTOR + owner.set_assigned_role(SSjob.get_job_type(role_job)) objectives += team.objectives finalize_abductor() - ADD_TRAIT(owner, TRAIT_ABDUCTOR_TRAINING, ABDUCTOR_ANTAGONIST) + // We don't want abductors to be converted by other antagonists + owner.add_traits(list(TRAIT_ABDUCTOR_TRAINING, TRAIT_UNCONVERTABLE), ABDUCTOR_ANTAGONIST) return ..() /datum/antagonist/abductor/on_removal() - owner.special_role = null - REMOVE_TRAIT(owner, TRAIT_ABDUCTOR_TRAINING, ABDUCTOR_ANTAGONIST) + owner.remove_traits(list(TRAIT_ABDUCTOR_TRAINING, TRAIT_UNCONVERTABLE), ABDUCTOR_ANTAGONIST) return ..() /datum/antagonist/abductor/greet() diff --git a/code/modules/antagonists/ashwalker/ashwalker.dm b/code/modules/antagonists/ashwalker/ashwalker.dm index 827d929b0fbb..588b1606b84c 100644 --- a/code/modules/antagonists/ashwalker/ashwalker.dm +++ b/code/modules/antagonists/ashwalker/ashwalker.dm @@ -1,12 +1,11 @@ /datum/antagonist/ashwalker name = "\improper Ash Walker" - job_rank = ROLE_LAVALAND + pref_flag = ROLE_LAVALAND show_in_antagpanel = FALSE show_to_ghosts = TRUE - prevent_roundtype_conversion = FALSE antagpanel_category = ANTAG_GROUP_ASHWALKERS suicide_cry = "I HAVE NO IDEA WHAT THIS THING DOES!!" - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST var/datum/team/ashwalkers/ashie_team /datum/antagonist/ashwalker/create_team(datum/team/ashwalkers/ashwalker_team) diff --git a/code/modules/antagonists/battlecruiser/battlecruiser.dm b/code/modules/antagonists/battlecruiser/battlecruiser.dm index 8560300698f8..ab1a87b9c625 100644 --- a/code/modules/antagonists/battlecruiser/battlecruiser.dm +++ b/code/modules/antagonists/battlecruiser/battlecruiser.dm @@ -19,7 +19,8 @@ suicide_cry = "FOR THE SYNDICATE!!!" antag_hud_name = "battlecruiser_crew" antagpanel_category = ANTAG_GROUP_SYNDICATE - job_rank = ROLE_BATTLECRUISER_CREW + pref_flag = ROLE_BATTLECRUISER_CREW + stinger_sound = 'sound/music/antag/ops.ogg' /// Team to place the crewmember on. var/datum/team/battlecruiser/battlecruiser_team @@ -38,7 +39,7 @@ /datum/antagonist/battlecruiser/captain name = "Battlecruiser Captain" antag_hud_name = "battlecruiser_lead" - job_rank = ROLE_BATTLECRUISER_CAPTAIN + pref_flag = ROLE_BATTLECRUISER_CAPTAIN /datum/antagonist/battlecruiser/create_team(datum/team/battlecruiser/team) if(!team) diff --git a/code/modules/antagonists/blob/blob_antag.dm b/code/modules/antagonists/blob/blob_antag.dm index 07ca14586e1f..3066fe74590d 100644 --- a/code/modules/antagonists/blob/blob_antag.dm +++ b/code/modules/antagonists/blob/blob_antag.dm @@ -4,7 +4,7 @@ antagpanel_category = ANTAG_GROUP_BIOHAZARDS show_to_ghosts = TRUE show_in_antagpanel = FALSE - job_rank = ROLE_BLOB + pref_flag = ROLE_BLOB ui_name = "AntagInfoBlob" /// Action to release a blob infection var/datum/action/innate/blobpop/pop_action @@ -156,7 +156,7 @@ /datum/antagonist/blob/infection name = "\improper Blob Infection" show_in_antagpanel = TRUE - job_rank = ROLE_BLOB_INFECTION + pref_flag = ROLE_BLOB_INFECTION /datum/antagonist/blob/infection/get_preview_icon() var/icon/blob_icon = ..() diff --git a/code/modules/antagonists/brainwashing/brainwashing.dm b/code/modules/antagonists/brainwashing/brainwashing.dm index a689bebd9dd1..af5bfd2b53e0 100644 --- a/code/modules/antagonists/brainwashing/brainwashing.dm +++ b/code/modules/antagonists/brainwashing/brainwashing.dm @@ -29,13 +29,14 @@ /datum/antagonist/brainwashed name = "\improper Brainwashed Victim" - job_rank = ROLE_BRAINWASHED + pref_flag = ROLE_BRAINWASHED + stinger_sound = 'sound/music/antag/brainwashed.ogg' roundend_category = "brainwashed victims" show_in_antagpanel = TRUE antag_hud_name = "brainwashed" antagpanel_category = ANTAG_GROUP_CREW show_name_in_check_antagonists = TRUE - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST ui_name = "AntagInfoBrainwashed" suicide_cry = "FOR... SOMEONE!!" diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 2b60dcdca89f..8a80081e286d 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -1,7 +1,7 @@ /datum/antagonist/brother name = "\improper Brother" antagpanel_category = "Brother" - job_rank = ROLE_BROTHER + pref_flag = ROLE_BROTHER var/special_role = ROLE_BROTHER antag_hud_name = "brother" hijack_speed = 0.5 @@ -14,6 +14,7 @@ /datum/antagonist/brother/create_team(datum/team/brother_team/new_team) if(!new_team) + team = new() return if(!istype(new_team)) stack_trace("Wrong team type passed to [type] initialization.") @@ -24,31 +25,44 @@ /datum/antagonist/brother/on_gain() objectives += team.objectives - owner.special_role = special_role finalize_brother() - var/is_first_brother = team.members.len == 1 - team.brothers_left -= 1 + if (team.brothers_left <= 0) + return ..() + + var/mob/living/carbon/carbon_owner = owner.current + if (!istype(carbon_owner)) + return ..() - if (is_first_brother || team.brothers_left > 0) - var/mob/living/carbon/carbon_owner = owner.current - if (istype(carbon_owner)) - carbon_owner.equip_conspicuous_item(new /obj/item/assembly/flash) - carbon_owner.AddComponentFrom(REF(src), /datum/component/can_flash_from_behind) - RegisterSignal(carbon_owner, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON, PROC_REF(on_mob_successful_flashed_carbon)) + grant_conversion_skills() + carbon_owner.equip_conspicuous_item(new /obj/item/assembly/flash) - if (!is_first_brother) - to_chat(carbon_owner, span_boldwarning("The Syndicate have higher expectations from you than others. They have granted you an extra flash to convert one other person.")) + var/is_first_brother = team.members.len == 1 + if (!is_first_brother) + to_chat(carbon_owner, span_boldwarning("The Syndicate have higher expectations from you than others. They have granted you an extra flash to convert one other person.")) return ..() /datum/antagonist/brother/on_removal() - owner.special_role = null - owner.RemoveComponentSource(REF(src), /datum/component/can_flash_from_behind) - UnregisterSignal(owner, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON) - + remove_conversion_skills() return ..() +/// Give us the ability to add another brother +/datum/antagonist/brother/proc/grant_conversion_skills() + var/mob/living/carbon/carbon_owner = owner.current + if (!istype(carbon_owner)) + return + carbon_owner.AddComponentFrom(REF(src), /datum/component/can_flash_from_behind) + RegisterSignal(carbon_owner, COMSIG_MOB_SUCCESSFUL_FLASHED_MOB, PROC_REF(on_mob_successful_flashed_mob)) + +/// Take away the ability to add more brothers +/datum/antagonist/brother/proc/remove_conversion_skills() + if (isnull(owner.current)) + return + var/mob/living/carbon/carbon_owner = owner.current + carbon_owner.RemoveComponentSource(REF(src), /datum/component/can_flash_from_behind) + UnregisterSignal(carbon_owner, COMSIG_MOB_SUCCESSFUL_FLASHED_MOB) + /datum/antagonist/brother/proc/on_mob_successful_flashed_carbon(mob/living/source, mob/living/carbon/flashed, obj/item/assembly/flash/flash) SIGNAL_HANDLER @@ -141,7 +155,7 @@ return brother_text /datum/antagonist/brother/greet() - to_chat(owner.current, span_alertsyndie("You are the [owner.special_role].")) + to_chat(owner.current, span_alertsyndie("You are a Blood Brother.")) owner.announce_objectives() /datum/antagonist/brother/proc/finalize_brother() diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index 5257e7d4242b..43c933afe4ad 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -5,7 +5,7 @@ name = "\improper Changeling" roundend_category = "changelings" antagpanel_category = "Changeling" - job_rank = ROLE_CHANGELING + pref_flag = ROLE_CHANGELING antag_moodlet = /datum/mood_event/ling antag_hud_name = "changeling" hijack_speed = 0.5 @@ -1029,7 +1029,7 @@ name = "\improper Headslug Changeling" show_in_antagpanel = FALSE give_objectives = FALSE - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_SKIP_GLOBAL_LIST genetic_points = 5 total_genetic_points = 5 diff --git a/code/modules/antagonists/changeling/fallen_changeling.dm b/code/modules/antagonists/changeling/fallen_changeling.dm index c44c1b66cd3d..9114886df295 100644 --- a/code/modules/antagonists/changeling/fallen_changeling.dm +++ b/code/modules/antagonists/changeling/fallen_changeling.dm @@ -3,11 +3,10 @@ name = "\improper Fallen Changeling" roundend_category = "changelings" antagpanel_category = "Changeling" - job_rank = ROLE_CHANGELING + pref_flag = ROLE_CHANGELING antag_moodlet = /datum/mood_event/fallen_changeling antag_hud_name = "changeling" /datum/mood_event/fallen_changeling description = "My powers! Where are my powers?!" mood_change = -4 - diff --git a/code/modules/antagonists/clown_ops/clownop.dm b/code/modules/antagonists/clown_ops/clownop.dm index 167708ff826c..c9dac60c06b8 100644 --- a/code/modules/antagonists/clown_ops/clownop.dm +++ b/code/modules/antagonists/clown_ops/clownop.dm @@ -4,6 +4,7 @@ roundend_category = "clown operatives" antagpanel_category = ANTAG_GROUP_CLOWNOPS nukeop_outfit = /datum/outfit/syndicate/clownop + job_type = /datum/job/nuclear_operative/clown_operative suicide_cry = "HAPPY BIRTHDAY!!" preview_outfit = /datum/outfit/clown_operative_elite @@ -11,7 +12,6 @@ nuke_icon_state = "bananiumbomb_base" /datum/antagonist/nukeop/clownop/admin_add(datum/mind/new_owner,mob/admin) - new_owner.set_assigned_role(SSjob.GetJobType(/datum/job/clown_operative)) new_owner.add_antag_datum(src) message_admins("[key_name_admin(admin)] has clown op'ed [key_name_admin(new_owner)].") log_admin("[key_name(admin)] has clown op'ed [key_name(new_owner)].") @@ -34,11 +34,12 @@ ADD_TRAIT(liver, TRAIT_COMEDY_METABOLISM, CLOWNOP_TRAIT) /datum/antagonist/nukeop/leader/clownop/give_alias() - title = pick("Head Honker", "Slipmaster", "Clown King", "Honkbearer") - if(nuke_team?.syndicate_name) - owner.current.real_name = "[nuke_team.syndicate_name] [title]" + title ||= pick("Head Honker", "Slipmaster", "Clown King", "Honkbearer") + . = ..() + if(ishuman(owner.current)) + owner.current.fully_replace_character_name(owner.current.real_name, "[title] [owner.current.real_name]") else - owner.current.real_name = "Syndicate [title]" + owner.current.fully_replace_character_name(owner.current.real_name, "[nuke_team.syndicate_name] [title]") /datum/antagonist/nukeop/leader/clownop name = "Clown Operative Leader" diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 4f1000c62c54..5da9203e7756 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -5,7 +5,7 @@ antag_moodlet = /datum/mood_event/cult suicide_cry = "FOR NAR'SIE!!" preview_outfit = /datum/outfit/cultist - job_rank = ROLE_CULTIST + pref_flag = ROLE_CULTIST antag_hud_name = "cult" ///The vote ability Cultists have to elect someone to be the leader. diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 265be82b7614..919d2b8d9703 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -303,7 +303,6 @@ structure_check() searches for nearby cultist structures required for the invoca convertee.Unconscious(10 SECONDS) new /obj/item/melee/cultblade/dagger(get_turf(src)) - convertee.mind.special_role = ROLE_CULTIST convertee.mind.add_antag_datum(/datum/antagonist/cult, cult_team) to_chat(convertee, span_cultitalic("Your blood pulses. Your head throbs. The world goes red. \ @@ -1174,7 +1173,7 @@ GLOBAL_VAR_INIT(narsie_summon_count, 0) force_event_async(/datum/round_event_control/meteor_wave, "an apocalypse rune") if(51 to 60) - force_event_async(/datum/round_event_control/spider_infestation, "an apocalypse rune") + SSdynamic.force_run_midround(/datum/dynamic_ruleset/midround/spiders) if(61 to 70) force_event_async(/datum/round_event_control/anomaly/anomaly_flux, "an apocalypse rune") diff --git a/code/modules/antagonists/disease/disease_datum.dm b/code/modules/antagonists/disease/disease_datum.dm index 17364feec559..cd166b5782b2 100644 --- a/code/modules/antagonists/disease/disease_datum.dm +++ b/code/modules/antagonists/disease/disease_datum.dm @@ -6,7 +6,7 @@ var/disease_name = "" /datum/antagonist/disease/on_gain() - owner.set_assigned_role(SSjob.GetJobType(/datum/job/sentient_disease)) + owner.set_assigned_role(SSjob.get_job_type(/datum/job/sentient_disease)) owner.special_role = ROLE_SENTIENT_DISEASE var/datum/objective/O = new /datum/objective/disease_infect() O.owner = owner diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm index 92ff1b1b5e54..073bafd61fb0 100644 --- a/code/modules/antagonists/ert/ert.dm +++ b/code/modules/antagonists/ert/ert.dm @@ -11,9 +11,8 @@ antag_moodlet = /datum/mood_event/focused antagpanel_category = ANTAG_GROUP_ERT suicide_cry = "FOR NANOTRASEN!!" - count_against_dynamic_roll_chance = FALSE // Not 'true' antags, this disables certain interactions that assume the owner is a baddie - antag_flags = FLAG_FAKE_ANTAG + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST var/datum/team/ert/ert_team var/leader = FALSE var/datum/outfit/outfit = /datum/outfit/centcom/ert/security diff --git a/code/modules/antagonists/evil_clone/evil_clone.dm b/code/modules/antagonists/evil_clone/evil_clone.dm new file mode 100644 index 000000000000..ef8455c3574d --- /dev/null +++ b/code/modules/antagonists/evil_clone/evil_clone.dm @@ -0,0 +1,67 @@ +/// Antag datum associated with the experimental cloner +/datum/antagonist/evil_clone + name = "\improper Evil Clone" + stinger_sound = 'sound/music/antag/hypnotized.ogg' + pref_flag = ROLE_EVIL_CLONE + roundend_category = "evil clones" + show_in_antagpanel = TRUE + antagpanel_category = ANTAG_GROUP_CREW + show_name_in_check_antagonists = TRUE + antag_flags = ANTAG_SKIP_GLOBAL_LIST + +/datum/antagonist/evil_clone/on_gain() + if (owner.current) + name = "[owner.current.real_name] Prime" + forge_objectives() + return ..() + +/datum/antagonist/evil_clone/greet() + if(silent) + return + play_stinger() + var/mob/living/current_mob = owner.current + if (current_mob) + to_chat(current_mob, span_big("You are [current_mob.real_name].")) + to_chat(current_mob, span_hypnophrase("You are the only [current_mob.real_name].")) + to_chat(current_mob, span_boldwarning("Anyone else pretending to be [current_mob.real_name] must be punished.")) + owner.announce_objectives() + +/datum/antagonist/evil_clone/forge_objectives() + var/datum/objective/accept_no_substitutes/objective = new + objective.owner = owner + objective.set_target_name(owner.current?.real_name) + objectives += objective + +/// Kill everyone with the same name as you +/datum/objective/accept_no_substitutes + name = "kill all clones" + explanation_text = "Ensure that nobody with a particular name that you don't remember remains alive." + admin_grantable = TRUE + /// What name do we want to expunge? + var/target_name + +/// Set the name to check for +/datum/objective/accept_no_substitutes/proc/set_target_name(new_name) + target_name = new_name + explanation_text = "Ensure that nobody with the name [target_name] remains alive." + +/datum/objective/accept_no_substitutes/check_completion() + if (!target_name) + return FALSE // Well we forgot to check for a name + + for (var/mob/living/someone as anything in GLOB.player_list) // We will generously not include people who logged out or ghosted + if (!istype(someone)) + continue + if (someone.stat == DEAD) + continue + if (someone == owner.current) + continue + if (someone.real_name == target_name) + return FALSE + + return TRUE + +/datum/objective/accept_no_substitutes/admin_edit(mob/admin) + admin_simple_target_pick(admin) + if (target.current) + set_target_name(target.current.real_name) diff --git a/code/modules/antagonists/fugitive/fugitive.dm b/code/modules/antagonists/fugitive/fugitive.dm index 7f2cc4a9f3d5..e42782bbf74f 100644 --- a/code/modules/antagonists/fugitive/fugitive.dm +++ b/code/modules/antagonists/fugitive/fugitive.dm @@ -2,16 +2,14 @@ /datum/antagonist/fugitive name = "\improper Fugitive" roundend_category = "Fugitive" - job_rank = ROLE_FUGITIVE - silent = TRUE //greet called by the event + pref_flag = ROLE_FUGITIVE show_in_antagpanel = FALSE show_to_ghosts = TRUE antagpanel_category = ANTAG_GROUP_FUGITIVES - prevent_roundtype_conversion = FALSE antag_hud_name = "fugitive" suicide_cry = "FOR FREEDOM!!" preview_outfit = /datum/outfit/prisoner - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_SKIP_GLOBAL_LIST var/datum/team/fugitive/fugitive_team var/is_captured = FALSE var/backstory = "error" @@ -38,10 +36,14 @@ return fugitive_icon - /datum/antagonist/fugitive/on_gain() forge_objectives() . = ..() + owner.set_assigned_role(SSjob.get_job_type(/datum/job/fugitive)) + +/datum/antagonist/fugitive/on_removal() + . = ..() + owner?.set_assigned_role(SSjob.get_job_type(/datum/job/unassigned)) /datum/antagonist/fugitive/forge_objectives() //this isn't the actual survive objective because it's about who in the team survives var/datum/objective/survive = new /datum/objective @@ -49,9 +51,8 @@ survive.explanation_text = "Avoid capture from the fugitive hunters." objectives += survive -/datum/antagonist/fugitive/greet(back_story) +/datum/antagonist/fugitive/greet() . = ..() - backstory = back_story var/message = "" switch(backstory) if(FUGITIVE_BACKSTORY_PRISONER) diff --git a/code/modules/antagonists/fugitive/hunters/hunter.dm b/code/modules/antagonists/fugitive/hunters/hunter.dm index b75cba69e528..510cb281ecab 100644 --- a/code/modules/antagonists/fugitive/hunters/hunter.dm +++ b/code/modules/antagonists/fugitive/hunters/hunter.dm @@ -6,10 +6,9 @@ show_in_antagpanel = FALSE show_to_ghosts = TRUE antagpanel_category = ANTAG_GROUP_HUNTERS - prevent_roundtype_conversion = FALSE antag_hud_name = "fugitive_hunter" suicide_cry = "FOR GLORY!!" - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_SKIP_GLOBAL_LIST var/datum/team/fugitive_hunters/hunter_team var/backstory = "error" diff --git a/code/modules/antagonists/greentext/greentext.dm b/code/modules/antagonists/greentext/greentext.dm index 7133066f8569..32f624c65054 100644 --- a/code/modules/antagonists/greentext/greentext.dm +++ b/code/modules/antagonists/greentext/greentext.dm @@ -3,7 +3,7 @@ show_in_antagpanel = FALSE show_name_in_check_antagonists = TRUE //Not that it will be there for long suicide_cry = "FOR THE GREENTEXT!!" // This can never actually show up, but not including it is a missed opportunity - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST hardcore_random_bonus = TRUE /datum/antagonist/greentext/forge_objectives() diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm index 9cd05062583f..820728dd2829 100644 --- a/code/modules/antagonists/heretic/heretic_antag.dm +++ b/code/modules/antagonists/heretic/heretic_antag.dm @@ -18,7 +18,7 @@ antagpanel_category = "Heretic" ui_name = "AntagInfoHeretic" antag_moodlet = /datum/mood_event/heretics - job_rank = ROLE_HERETIC + pref_flag = ROLE_HERETIC antag_hud_name = "heretic" hijack_speed = 0.5 suicide_cry = "THE MANSUS SMILES UPON ME!!" diff --git a/code/modules/antagonists/heretic/heretic_monsters.dm b/code/modules/antagonists/heretic/heretic_monsters.dm index 3f3dd3203572..5ce974014f9a 100644 --- a/code/modules/antagonists/heretic/heretic_monsters.dm +++ b/code/modules/antagonists/heretic/heretic_monsters.dm @@ -4,7 +4,7 @@ roundend_category = "Heretics" antagpanel_category = ANTAG_GROUP_HORRORS antag_moodlet = /datum/mood_event/heretics - job_rank = ROLE_HERETIC + pref_flag = ROLE_HERETIC antag_hud_name = "heretic_beast" suicide_cry = "MY MASTER SMILES UPON ME!!" show_in_antagpanel = FALSE diff --git a/code/modules/antagonists/highlander/highlander.dm b/code/modules/antagonists/highlander/highlander.dm index 491d55d8f2d0..2ba710b2ffd4 100644 --- a/code/modules/antagonists/highlander/highlander.dm +++ b/code/modules/antagonists/highlander/highlander.dm @@ -5,7 +5,7 @@ show_name_in_check_antagonists = TRUE can_elimination_hijack = ELIMINATION_ENABLED suicide_cry = "FOR SCOTLAND!!" // If they manage to lose their no-drop stuff somehow - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST /// Traits we apply/remove to our target on-demand. var/static/list/applicable_traits = list( TRAIT_DESENSITIZED, @@ -39,7 +39,6 @@ /datum/antagonist/highlander/on_gain() forge_objectives() - owner.special_role = "highlander" give_equipment() . = ..() diff --git a/code/modules/antagonists/hypnotized/hypnotized.dm b/code/modules/antagonists/hypnotized/hypnotized.dm index 4f1f49aa3be7..6ee82f01b69f 100644 --- a/code/modules/antagonists/hypnotized/hypnotized.dm +++ b/code/modules/antagonists/hypnotized/hypnotized.dm @@ -1,14 +1,15 @@ /// Antag datum associated with the hypnosis brain trauma, used for displaying objectives and antag hud /datum/antagonist/hypnotized name = "\improper Hypnotized Victim" - job_rank = ROLE_HYPNOTIZED + stinger_sound = 'sound/music/antag/hypnotized.ogg' + pref_flag = ROLE_HYPNOTIZED roundend_category = "hypnotized victims" antag_hud_name = "brainwashed" ui_name = "AntagInfoBrainwashed" show_in_antagpanel = TRUE antagpanel_category = ANTAG_GROUP_CREW show_name_in_check_antagonists = TRUE - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST silent = TRUE //not actually silent, because greet will be called by the trauma anyway. /// Brain trauma associated with this antag datum diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm index fc25fd11a72d..55b770ca7416 100644 --- a/code/modules/antagonists/malf_ai/malf_ai.dm +++ b/code/modules/antagonists/malf_ai/malf_ai.dm @@ -5,7 +5,7 @@ name = "\improper Malfunctioning AI" roundend_category = "traitors" antagpanel_category = "Malf AI" - job_rank = ROLE_MALF + pref_flag = ROLE_MALF antag_hud_name = "traitor" ui_name = "AntagInfoMalf" can_assign_self_objectives = TRUE @@ -30,7 +30,6 @@ stack_trace("Attempted to give malf AI antag datum to \[[owner]\], who did not meet the requirements.") return ..() - owner.special_role = job_rank if(give_objectives) forge_ai_objectives() @@ -46,8 +45,6 @@ malf_ai.remove_malf_abilities() QDEL_NULL(malf_ai.malf_picker) - owner.special_role = null - return ..() /// Generates a complete set of malf AI objectives up to the traitor objective limit. diff --git a/code/modules/antagonists/nightmare/nightmare.dm b/code/modules/antagonists/nightmare/nightmare.dm index 58417b2a9664..d8a1f18a3010 100644 --- a/code/modules/antagonists/nightmare/nightmare.dm +++ b/code/modules/antagonists/nightmare/nightmare.dm @@ -1,7 +1,7 @@ /datum/antagonist/nightmare name = "\improper Nightmare" antagpanel_category = ANTAG_GROUP_ABOMINATIONS - job_rank = ROLE_NIGHTMARE + pref_flag = ROLE_NIGHTMARE show_in_antagpanel = FALSE show_name_in_check_antagonists = TRUE show_to_ghosts = TRUE diff --git a/code/modules/antagonists/nukeop/datums/operative.dm b/code/modules/antagonists/nukeop/datums/operative.dm new file mode 100644 index 000000000000..0279a12939dd --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative.dm @@ -0,0 +1,196 @@ +/datum/antagonist/nukeop + name = ROLE_OPERATIVE + roundend_category = "syndicate operatives" //just in case + antagpanel_category = ANTAG_GROUP_SYNDICATE + pref_flag = ROLE_OPERATIVE + antag_hud_name = "synd" + antag_moodlet = /datum/mood_event/focused + show_to_ghosts = TRUE + hijack_speed = 2 //If you can't take out the station, take the shuttle instead. + suicide_cry = "FOR THE SYNDICATE!!" + stinger_sound = 'sound/music/antag/ops.ogg' + + /// Which nukie team are we on? + var/datum/team/nuclear/nuke_team + /// Should the user be moved to default spawnpoint after being granted this datum. + var/send_to_spawnpoint = TRUE + + var/job_type = /datum/job/nuclear_operative + /// The DEFAULT outfit we will give to players granted this datum + var/nukeop_outfit = /datum/outfit/syndicate + + preview_outfit = /datum/outfit/nuclear_operative_elite + + /// In the preview icon, the nukies who are behind the leader + var/preview_outfit_behind = /datum/outfit/nuclear_operative + + /// In the preview icon, a nuclear fission explosive device, only appearing if there's an icon state for it. + var/nuke_icon_state = "nuclearbomb_base" + + /// The amount of discounts that the team get + var/discount_team_amount = 5 + /// The amount of limited discounts that the team get + var/discount_limited_amount = 10 + +/datum/antagonist/nukeop/greet() + play_stinger() + to_chat(owner, span_big("You are a [nuke_team ? nuke_team.syndicate_name : "syndicate"] agent!")) + owner.announce_objectives() + +/datum/antagonist/nukeop/on_gain() + give_alias() + forge_objectives() + . = ..() + owner.set_assigned_role(SSjob.get_job_type(job_type)) + equip_op() + if(send_to_spawnpoint) + move_to_spawnpoint() + // grant extra TC for the people who start in the nukie base ie. not the lone op + var/extra_tc = CEILING(GLOB.joined_player_list.len/5, 5) + var/datum/component/uplink/uplink = owner.find_syndicate_uplink() + if (uplink) + uplink.uplink_handler.add_telecrystals(extra_tc) + var/datum/component/uplink/uplink = owner.find_syndicate_uplink() + if(uplink) + var/datum/team/nuclear/nuke_team = get_team() + if(!nuke_team.team_discounts) + var/list/uplink_items = list() + for(var/datum/uplink_item/item as anything in SStraitor.uplink_items) + if(item.item && !item.cant_discount && (item.purchasable_from & uplink.uplink_handler.uplink_flag) && item.cost > 1) + uplink_items += item + nuke_team.team_discounts = list() + nuke_team.team_discounts += create_uplink_sales(discount_team_amount, /datum/uplink_category/discount_team_gear, -1, uplink_items) + nuke_team.team_discounts += create_uplink_sales(discount_limited_amount, /datum/uplink_category/limited_discount_team_gear, 1, uplink_items) + uplink.uplink_handler.extra_purchasable += nuke_team.team_discounts + + if(nuke_team?.tracked_nuke && nuke_team?.memorized_code) + memorize_code() + +/datum/antagonist/nukeop/get_team() + return nuke_team + +/datum/antagonist/nukeop/apply_innate_effects(mob/living/mob_override) + add_team_hud(mob_override || owner.current, /datum/antagonist/nukeop) + +/datum/antagonist/nukeop/forge_objectives() + if(nuke_team) + objectives |= nuke_team.objectives + +/datum/antagonist/nukeop/leader/get_spawnpoint() + return pick(GLOB.nukeop_leader_start) + +/datum/antagonist/nukeop/create_team(datum/team/nuclear/new_team) + if(!new_team) + // Find the first leader to join up + for(var/datum/antagonist/nukeop/leader/leader in GLOB.antagonists) + if(leader.nuke_team) + nuke_team = leader.nuke_team + return + // Otherwise make a new team entirely + nuke_team = new /datum/team/nuclear() + return + if(!istype(new_team)) + stack_trace("Wrong team type passed to [type] initialization.") + nuke_team = new_team + +/datum/antagonist/nukeop/admin_add(datum/mind/new_owner,mob/admin) + new_owner.add_antag_datum(src) + message_admins("[key_name_admin(admin)] has nuke op'ed [key_name_admin(new_owner)].") + log_admin("[key_name(admin)] has nuke op'ed [key_name(new_owner)].") + +/datum/antagonist/nukeop/get_admin_commands() + . = ..() + .["Send to base"] = CALLBACK(src, PROC_REF(admin_send_to_base)) + .["Tell code"] = CALLBACK(src, PROC_REF(admin_tell_code)) + +/datum/antagonist/nukeop/get_preview_icon() + if (!preview_outfit) + return null + + var/icon/final_icon = render_preview_outfit(preview_outfit) + + if (!isnull(preview_outfit_behind)) + var/icon/teammate = render_preview_outfit(preview_outfit_behind) + teammate.Blend(rgb(128, 128, 128, 128), ICON_MULTIPLY) + + final_icon.Blend(teammate, ICON_UNDERLAY, -ICON_SIZE_X / 4, 0) + final_icon.Blend(teammate, ICON_UNDERLAY, ICON_SIZE_X / 4, 0) + + if (!isnull(nuke_icon_state)) + var/icon/nuke = icon('icons/obj/machines/nuke.dmi', nuke_icon_state) + nuke.Shift(SOUTH, 6) + final_icon.Blend(nuke, ICON_OVERLAY) + + return finish_preview_icon(final_icon) + +/datum/antagonist/nukeop/proc/equip_op() + if(!ishuman(owner.current)) + return + + var/mob/living/carbon/human/operative = owner.current + ADD_TRAIT(operative, TRAIT_NOFEAR_HOLDUPS, INNATE_TRAIT) + + if(!nukeop_outfit) // this variable is null in instances where an antagonist datum is granted via enslaving the mind (/datum/mind/proc/enslave_mind_to_creator), like in golems. + return + + // If our nuke_ops_species pref is set to TRUE, (or we have no client) make us a human + if(isnull(operative.client) || operative.client.prefs.read_preference(/datum/preference/toggle/nuke_ops_species)) + operative.set_species(/datum/species/human) + + operative.equip_species_outfit(nukeop_outfit) + + return TRUE + +/datum/antagonist/nukeop/proc/admin_send_to_base(mob/admin) + owner.current.forceMove(pick(GLOB.nukeop_start)) + +/datum/antagonist/nukeop/proc/admin_tell_code(mob/admin) + var/code + for (var/obj/machinery/nuclearbomb/bombue as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb)) + if (length(bombue.r_code) <= 5 && bombue.r_code != initial(bombue.r_code)) + code = bombue.r_code + break + if (code) + antag_memory += "Syndicate Nuclear Bomb Code: [code]
" + to_chat(owner.current, "The nuclear authorization code is: [code]") + else + to_chat(admin, span_danger("No valid nuke found!")) + +/datum/antagonist/nukeop/proc/give_alias() + if(nuke_team?.syndicate_name) + var/mob/living/carbon/human/human_to_rename = owner.current + if(istype(human_to_rename)) // Reinforcements get a real name + var/first_name = owner.current.client?.prefs?.read_preference(/datum/preference/name/operative_alias) || pick(GLOB.operative_aliases) + var/chosen_name = "[first_name] [nuke_team.syndicate_name]" + human_to_rename.fully_replace_character_name(null, chosen_name) + else + var/number = nuke_team?.members.Find(owner) || 1 + owner.current.fully_replace_character_name(null, "[nuke_team.syndicate_name] Operative #[number]") + +/datum/antagonist/nukeop/proc/memorize_code() + antag_memory += "[nuke_team.tracked_nuke] Code: [nuke_team.memorized_code]
" + owner.add_memory(/datum/memory/key/nuke_code, nuclear_code = nuke_team.memorized_code) + to_chat(owner, "The nuclear authorization code is: [nuke_team.memorized_code]") + +/// Actually moves our nukie to where they should be +/datum/antagonist/nukeop/proc/move_to_spawnpoint() + // Ensure that the nukiebase is loaded, and wait for it if required + SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE) + var/turf/destination = get_spawnpoint() + owner.current.forceMove(destination) + if(!owner.current.onSyndieBase()) + message_admins("[ADMIN_LOOKUPFLW(owner.current)] is a NUKE OP and move_to_spawnpoint put them somewhere that isn't the syndie base, help please.") + stack_trace("Nuke op move_to_spawnpoint resulted in a location not on the syndicate base. (Was moved to: [destination])") + +/// Gets the position we spawn at +/datum/antagonist/nukeop/proc/get_spawnpoint() + var/team_number = 1 + if(nuke_team) + team_number = nuke_team.members.Find(owner) + + return GLOB.nukeop_start[((team_number - 1) % GLOB.nukeop_start.len) + 1] + +/datum/antagonist/nukeop/on_respawn(mob/new_character) + new_character.forceMove(pick(GLOB.nukeop_start)) + equip_op() + return TRUE diff --git a/code/modules/antagonists/nukeop/datums/operative_leader.dm b/code/modules/antagonists/nukeop/datums/operative_leader.dm new file mode 100644 index 000000000000..a15a0dec31a2 --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative_leader.dm @@ -0,0 +1,69 @@ +/datum/antagonist/nukeop/leader + name = "Nuclear Operative Leader" + nukeop_outfit = /datum/outfit/syndicate/leader + /// Randomly chosen honorific, for distinction + var/title + /// The nuclear challenge remote we will spawn this player with. + var/challengeitem = /obj/item/nuclear_challenge + +/datum/antagonist/nukeop/leader/memorize_code() + . = ..() + var/obj/item/paper/nuke_code_paper = new(get_turf(owner.current)) + nuke_code_paper.add_raw_text("The nuclear authorization code is: [nuke_team.memorized_code]") + nuke_code_paper.name = "nuclear bomb code" + nuke_code_paper.update_appearance() + owner.current.put_in_hands(nuke_code_paper) + +/datum/antagonist/nukeop/leader/give_alias() + title ||= pick("Czar", "Boss", "Commander", "Chief", "Kingpin", "Director", "Overlord") + . = ..() + if(ishuman(owner.current)) + owner.current.fully_replace_character_name(owner.current.real_name, "[title] [owner.current.real_name]") + else + owner.current.fully_replace_character_name(owner.current.real_name, "[nuke_team.syndicate_name] [title]") + +/datum/antagonist/nukeop/leader/greet() + play_stinger() + to_chat(owner, "You are the Syndicate [title] for this mission. You are responsible for guiding your team.") + to_chat(owner, "If you feel you are not up to this task, trade your headset with another operative.") + if(!CONFIG_GET(flag/disable_warops)) + to_chat(owner, "In your hand you will find a special item capable of triggering a greater challenge for your team. Examine it carefully and consult with your fellow operatives before activating it.") + owner.announce_objectives() + +/datum/antagonist/nukeop/leader/on_gain() + . = ..() + if(!CONFIG_GET(flag/disable_warops)) + var/mob/living/carbon/human/leader = owner.current + var/obj/item/war_declaration = new challengeitem(leader.drop_location()) + leader.put_in_hands(war_declaration) + nuke_team.war_button_ref = WEAKREF(war_declaration) + addtimer(CALLBACK(src, PROC_REF(nuketeam_name_assign)), 0.1 SECONDS) + +/datum/antagonist/nukeop/leader/proc/nuketeam_name_assign() + if(!nuke_team) + return + nuke_team.rename_team(ask_name()) + +/datum/antagonist/nukeop/leader/proc/ask_name() + var/randomname = pick(GLOB.last_names) + var/newname = tgui_input_text( + owner.current, + "You are the nuclear operative [title]. Please choose a last name for your family.", + "Name change", + randomname, + max_length = MAX_NAME_LEN, + ) + if (!newname) + newname = randomname + else + newname = reject_bad_name(newname) + if(!newname) + newname = randomname + + return capitalize(newname) + +/datum/antagonist/nukeop/leader/create_team(datum/team/nuclear/new_team) + if(new_team) + return ..() + // Leaders always make new teams + nuke_team = new /datum/team/nuclear() diff --git a/code/modules/antagonists/nukeop/datums/operative_lone.dm b/code/modules/antagonists/nukeop/datums/operative_lone.dm new file mode 100644 index 000000000000..b074016890aa --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative_lone.dm @@ -0,0 +1,13 @@ +/datum/antagonist/nukeop/lone + name = "Lone Operative" + send_to_spawnpoint = FALSE //Handled by event + nukeop_outfit = /datum/outfit/syndicate/full/loneop + preview_outfit = /datum/outfit/nuclear_operative + preview_outfit_behind = null + nuke_icon_state = null + +/datum/antagonist/nukeop/lone/create_team(datum/team/nuclear/new_team) + if(new_team) + return ..() + // Lone ops always get a solo team solely because a lot of nukie code is on the team + nuke_team = new /datum/team/nuclear/loneop() diff --git a/code/modules/antagonists/nukeop/datums/operative_team.dm b/code/modules/antagonists/nukeop/datums/operative_team.dm new file mode 100644 index 000000000000..4eab9341d48c --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative_team.dm @@ -0,0 +1,354 @@ +#define SPAWN_AT_BASE "Nuke base" +#define SPAWN_AT_INFILTRATOR "Infiltrator" + +/datum/team/nuclear + var/syndicate_name + var/obj/machinery/nuclearbomb/tracked_nuke + var/core_objective = /datum/objective/nuclear + var/memorized_code + var/list/team_discounts + var/datum/weakref/war_button_ref + +/datum/team/nuclear/New() + ..() + syndicate_name = syndicate_name() + + var/datum/objective/maingoal = new core_objective() + maingoal.team = src + objectives += maingoal + + // when a nuke team is created, the infiltrator has not loaded in yet - it takes some time. so no nuke, we have to wait + addtimer(CALLBACK(src, PROC_REF(assign_nuke_delayed)), 4 SECONDS) + +/datum/team/nuclear/roundend_report() + var/list/parts = list() + parts += span_header("[syndicate_name] Operatives:") + + switch(get_result()) + if(NUKE_RESULT_FLUKE) + parts += "Humiliating Syndicate Defeat" + parts += "The crew of [station_name()] gave [syndicate_name] operatives back their bomb! The syndicate base was destroyed! Next time, don't lose the nuke!" + if(NUKE_RESULT_NUKE_WIN) + parts += "Syndicate Major Victory!" + parts += "[syndicate_name] operatives have destroyed [station_name()]!" + if(NUKE_RESULT_NOSURVIVORS) + parts += "Total Annihilation!" + parts += "[syndicate_name] operatives destroyed [station_name()] but did not leave the area in time and got caught in the explosion. Next time, don't lose the disk!" + if(NUKE_RESULT_WRONG_STATION) + parts += "Crew Minor Victory!" + parts += "[syndicate_name] operatives secured the authentication disk but blew up something that wasn't [station_name()]. Next time, don't do that!" + if(NUKE_RESULT_WRONG_STATION_DEAD) + parts += "[syndicate_name] operatives have earned Darwin Award!" + parts += "[syndicate_name] operatives blew up something that wasn't [station_name()] and got caught in the explosion. Next time, don't do that!" + if(NUKE_RESULT_HIJACK_DISK) + parts += "Syndicate Miniscule Victory!" + parts += "[syndicate_name] operatives failed to destroy [station_name()], but they managed to secure the disk and hijack the emergency shuttle, causing it to land on the syndicate base. Good job?" + if(NUKE_RESULT_HIJACK_NO_DISK) + parts += "Syndicate Insignificant Victory!" + parts += "[syndicate_name] operatives failed to destroy [station_name()] or secure the disk, but they managed to hijack the emergency shuttle, causing it to land on the syndicate base. Good job?" + if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD) + parts += "Crew Major Victory!" + parts += "The Research Staff has saved the disk and killed the [syndicate_name] Operatives" + if(NUKE_RESULT_CREW_WIN) + parts += "Crew Major Victory!" + parts += "The Research Staff has saved the disk and stopped the [syndicate_name] Operatives!" + if(NUKE_RESULT_DISK_LOST) + parts += "Neutral Victory!" + parts += "The Research Staff failed to secure the authentication disk but did manage to kill most of the [syndicate_name] Operatives!" + if(NUKE_RESULT_DISK_STOLEN) + parts += "Syndicate Minor Victory!" + parts += "[syndicate_name] operatives survived the assault but did not achieve the destruction of [station_name()]. Next time, don't lose the disk!" + else + parts += "Neutral Victory" + parts += "Mission aborted!" + + var/text = span_header("
The syndicate operatives were:") + var/purchases = "" + var/TC_uses = 0 + LAZYINITLIST(GLOB.uplink_purchase_logs_by_key) + for(var/I in members) + var/datum/mind/syndicate = I + var/datum/uplink_purchase_log/H = GLOB.uplink_purchase_logs_by_key[syndicate.key] + if(H) + TC_uses += H.total_spent + purchases += H.generate_render(show_key = FALSE) + text += printplayerlist(members) + text += "
" + text += "(Syndicates used [TC_uses] TC) [purchases]" + if(TC_uses == 0 && GLOB.station_was_nuked && !are_all_operatives_dead()) + text += "[icon2html('icons/ui/antags/badass.dmi', world, "badass")]" + + parts += text + + return "
[parts.Join("
")]
" + +/datum/team/nuclear/antag_listing_name() + if(syndicate_name) + return "[syndicate_name] Syndicates" + else + return "Syndicates" + +/datum/team/nuclear/antag_listing_entry() + var/disk_report = "Nuclear Disk(s)
" + disk_report += "" + for(var/obj/item/disk/nuclear/N in SSpoints_of_interest.real_nuclear_disks) + disk_report += "" + disk_report += "
[N.name], " + var/atom/disk_loc = N.loc + while(!isturf(disk_loc)) + if(ismob(disk_loc)) + var/mob/M = disk_loc + disk_report += "carried by [M.real_name] " + if(isobj(disk_loc)) + var/obj/O = disk_loc + disk_report += "in \a [O.name] " + disk_loc = disk_loc.loc + disk_report += "in [disk_loc.loc] at ([disk_loc.x], [disk_loc.y], [disk_loc.z])FLW
" + + var/post_report + + var/war_declared = FALSE + for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards) + if(board.challenge_start_time) + war_declared = TRUE + + var/force_war_button = "" + + if(war_declared) + post_report += "War declared." + force_war_button = "\[Force war\]" + else + post_report += "War not declared." + var/obj/item/nuclear_challenge/war_button = war_button_ref?.resolve() + if(war_button) + force_war_button = "\[Force war\]" + else + force_war_button = "\[Cannot declare war, challenge button missing!\]" + + post_report += "\n[force_war_button]" + post_report += "\n\[Send Reinforcement\]" + + var/final_report = ..() + final_report += disk_report + final_report += post_report + return final_report + +/datum/team/nuclear/proc/rename_team(new_name) + syndicate_name = new_name + name = "[syndicate_name] Team" + for(var/datum/mind/synd_mind in members) + var/datum/antagonist/nukeop/synd_datum = synd_mind.has_antag_datum(/datum/antagonist/nukeop) + synd_datum?.give_alias() + +/datum/team/nuclear/proc/admin_spawn_reinforcement(mob/admin) + if(!check_rights_for(admin.client, R_ADMIN)) + return + + var/infil_or_nukebase = tgui_alert( + admin, + "Spawn them at the nuke base, or in the Infiltrator?", + "Where to reinforce?", + list(SPAWN_AT_BASE, SPAWN_AT_INFILTRATOR, "Cancel"), + ) + + if(!infil_or_nukebase || infil_or_nukebase == "Cancel") + return + + var/tc_to_spawn = tgui_input_number(admin, "How much TC to spawn with?", "TC", 0, 100) + + var/mob/chosen_one = SSpolling.poll_ghost_candidates( + check_jobban = ROLE_OPERATIVE, + role = ROLE_OPERATIVE, + poll_time = 30 SECONDS, + ignore_category = POLL_IGNORE_SYNDICATE, + alert_pic = /obj/structure/sign/poster/contraband/gorlex_recruitment, + role_name_text = "emergency syndicate reinforcement", + amount_to_pick = 1, + ) + + if(isnull(chosen_one)) + tgui_alert(admin, "No candidates found.", "Recruitment Shortage", list("OK")) + return + + + var/turf/spawn_loc + if(infil_or_nukebase == SPAWN_AT_INFILTRATOR) + var/area/spawn_in + // Prioritize EVA then hallway, if neither can be found default to the first area we can find + for(var/area_type in list(/area/shuttle/syndicate/eva, /area/shuttle/syndicate/hallway, /area/shuttle/syndicate)) + spawn_in = locate(area_type) in GLOB.areas // I'd love to use areas_by_type but the Infiltrator is a unique area + if(spawn_in) + break + + var/list/turf/options = list() + for(var/turf/open/open_turf in spawn_in?.get_turfs_from_all_zlevels()) + if(open_turf.is_blocked_turf()) + continue + options += open_turf + + if(length(options)) + spawn_loc = pick(options) + else + infil_or_nukebase = SPAWN_AT_BASE + + if(infil_or_nukebase == SPAWN_AT_BASE) + spawn_loc = pick(GLOB.nukeop_start) + + var/mob/living/carbon/human/nukie = new(spawn_loc) + chosen_one.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE) + nukie.PossessByPlayer(chosen_one.key) + + var/datum/antagonist/nukeop/antag_datum = new() + antag_datum.send_to_spawnpoint = FALSE + antag_datum.nukeop_outfit = /datum/outfit/syndicate/reinforcement + + nukie.mind.add_antag_datum(antag_datum, src) + + var/datum/component/uplink/uplink = nukie.mind.find_syndicate_uplink() + uplink?.uplink_handler.set_telecrystals(tc_to_spawn) + + // add some pizzazz + do_sparks(4, FALSE, spawn_loc) + new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(spawn_loc) + playsound(spawn_loc, SFX_SPARKS, 50, TRUE) + playsound(spawn_loc, 'sound/effects/phasein.ogg', 50, TRUE) + + tgui_alert(admin, "Reinforcement spawned at [infil_or_nukebase] with [tc_to_spawn].", "Reinforcements have arrived", list("God speed")) + +/datum/team/nuclear/proc/is_disk_rescued() + for(var/obj/item/disk/nuclear/nuke_disk in SSpoints_of_interest.real_nuclear_disks) + //If emergency shuttle is in transit disk is only safe on it + if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE) + if(!SSshuttle.emergency.is_in_shuttle_bounds(nuke_disk)) + return FALSE + //If shuttle escaped check if it's on centcom side + else if(SSshuttle.emergency.mode == SHUTTLE_ENDGAME) + if(!nuke_disk.onCentCom()) + return FALSE + else //Otherwise disk is safe when on station + var/turf/disk_turf = get_turf(nuke_disk) + if(!disk_turf || !is_station_level(disk_turf.z)) + return FALSE + return TRUE + +/datum/team/nuclear/proc/are_all_operatives_dead() + for(var/datum/mind/operative_mind as anything in members) + if(ishuman(operative_mind.current) && (operative_mind.current.stat != DEAD)) + return FALSE + return TRUE + +/datum/team/nuclear/proc/get_result() + var/shuttle_evacuated = EMERGENCY_ESCAPED_OR_ENDGAMED + var/shuttle_landed_base = SSshuttle.emergency.is_hijacked() + var/disk_rescued = is_disk_rescued() + var/syndies_didnt_escape = !is_infiltrator_docked_at_syndiebase() + var/team_is_dead = are_all_operatives_dead() + var/station_was_nuked = GLOB.station_was_nuked + var/station_nuke_source = GLOB.station_nuke_source + + // The nuke detonated on the syndicate base + if(station_nuke_source == DETONATION_HIT_SYNDIE_BASE) + return NUKE_RESULT_FLUKE + + // The station was nuked + if(station_was_nuked) + // The station was nuked and the infiltrator failed to escape + if(syndies_didnt_escape) + return NUKE_RESULT_NOSURVIVORS + // The station was nuked and the infiltrator escaped, and the nuke ops won + else + return NUKE_RESULT_NUKE_WIN + + // The station was not nuked, but something was + else if(station_nuke_source && !disk_rescued) + // The station was not nuked, but something was, and the syndicates didn't escape it + if(syndies_didnt_escape) + return NUKE_RESULT_WRONG_STATION_DEAD + // The station was not nuked, but something was, and the syndicates returned to their base + else + return NUKE_RESULT_WRONG_STATION + + // Nuke didn't blow, but nukies somehow hijacked the emergency shuttle to land at the base anyways. + else if(shuttle_landed_base) + if(disk_rescued) + return NUKE_RESULT_HIJACK_DISK + else + return NUKE_RESULT_HIJACK_NO_DISK + + // No nuke went off, the station rescued the disk + else if(disk_rescued) + // No nuke went off, the shuttle left, and the team is dead + if(shuttle_evacuated && team_is_dead) + return NUKE_RESULT_CREW_WIN_SYNDIES_DEAD + // No nuke went off, but the nuke ops survived + else + return NUKE_RESULT_CREW_WIN + + // No nuke went off, but the disk was left behind + else + // No nuke went off, the disk was left, but all the ops are dead + if(team_is_dead) + return NUKE_RESULT_DISK_LOST + // No nuke went off, the disk was left, there are living ops, but the shuttle left successfully + else if(shuttle_evacuated) + return NUKE_RESULT_DISK_STOLEN + + CRASH("[type] - got an undefined / unexpected result.") + +/// Returns whether or not syndicate operatives escaped. +/proc/is_infiltrator_docked_at_syndiebase() + var/obj/docking_port/mobile/infiltrator/infiltrator_port = SSshuttle.getShuttle("syndicate") + + var/datum/lazy_template/nukie_base/nukie_template = GLOB.lazy_templates[LAZY_TEMPLATE_KEY_NUKIEBASE] + if(!nukie_template) + return FALSE // if its not even loaded, cant be docked + + for(var/datum/turf_reservation/loaded_area as anything in nukie_template.reservations) + var/infiltrator_turf = get_turf(infiltrator_port) + if(infiltrator_turf in loaded_area.reserved_turfs) + return TRUE + return FALSE + +/datum/team/nuclear/add_member(datum/mind/new_member) + ..() + SEND_SIGNAL(src, COMSIG_NUKE_TEAM_ADDITION, new_member.current) + +/datum/team/nuclear/proc/assign_nuke_delayed() + assign_nuke() + if(tracked_nuke && memorized_code) + for(var/datum/mind/synd_mind in members) + var/datum/antagonist/nukeop/synd_datum = synd_mind.has_antag_datum(/datum/antagonist/nukeop) + synd_datum?.memorize_code() + +/datum/team/nuclear/proc/assign_nuke() + memorized_code = random_nukecode() + var/obj/machinery/nuclearbomb/syndicate/nuke = locate() in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/syndicate) + if(!nuke) + stack_trace("Syndicate nuke not found during nuke team creation.") + memorized_code = null + return + tracked_nuke = nuke + if(nuke.r_code == NUKE_CODE_UNSET) + nuke.r_code = memorized_code + else //Already set by admins/something else? + memorized_code = nuke.r_code + for(var/obj/machinery/nuclearbomb/beer/beernuke as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/beer)) + beernuke.r_code = memorized_code + +#undef SPAWN_AT_BASE +#undef SPAWN_AT_INFILTRATOR + +/datum/team/nuclear/loneop + +/datum/team/nuclear/loneop/assign_nuke() + memorized_code = random_nukecode() + var/obj/machinery/nuclearbomb/selfdestruct/nuke = locate() in SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/selfdestruct) + if(nuke) + tracked_nuke = nuke + if(nuke.r_code == NUKE_CODE_UNSET) + nuke.r_code = memorized_code + else //Already set by admins/something else? + memorized_code = nuke.r_code + else + stack_trace("Station self-destruct not found during lone op team creation.") + memorized_code = null diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm deleted file mode 100644 index dc0a813bb504..000000000000 --- a/code/modules/antagonists/nukeop/nukeop.dm +++ /dev/null @@ -1,638 +0,0 @@ -/datum/antagonist/nukeop - name = ROLE_NUCLEAR_OPERATIVE - roundend_category = "syndicate operatives" //just in case - antagpanel_category = ANTAG_GROUP_SYNDICATE - job_rank = ROLE_OPERATIVE - antag_hud_name = "synd" - antag_moodlet = /datum/mood_event/focused - show_to_ghosts = TRUE - hijack_speed = 2 //If you can't take out the station, take the shuttle instead. - suicide_cry = "FOR THE SYNDICATE!!" - /// Which nukie team are we on? - var/datum/team/nuclear/nuke_team - /// If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team. - var/always_new_team = FALSE - /// Should the user be moved to default spawnpoint after being granted this datum. - var/send_to_spawnpoint = TRUE - /// The DEFAULT outfit we will give to players granted this datum - var/nukeop_outfit = /datum/outfit/syndicate - - preview_outfit = /datum/outfit/nuclear_operative_elite - - /// In the preview icon, the nukies who are behind the leader - var/preview_outfit_behind = /datum/outfit/nuclear_operative - /// In the preview icon, a nuclear fission explosive device, only appearing if there's an icon state for it. - var/nuke_icon_state = "nuclearbomb_base" - - /// The amount of discounts that the team get - var/discount_team_amount = 5 - /// The amount of limited discounts that the team get - var/discount_limited_amount = 10 - -/datum/antagonist/nukeop/proc/equip_op() - if(!ishuman(owner.current)) - return - - var/mob/living/carbon/human/operative = owner.current - ADD_TRAIT(operative, TRAIT_NOFEAR_HOLDUPS, INNATE_TRAIT) - ADD_TRAIT(operative, TRAIT_DESENSITIZED, INNATE_TRAIT) - - if(!nukeop_outfit) // this variable is null in instances where an antagonist datum is granted via enslaving the mind (/datum/mind/proc/enslave_mind_to_creator), like in golems. - return - - // If our nuke_ops_species pref is set to TRUE, (or we have no client) make us a human - if(isnull(operative.client) || operative.client.prefs.read_preference(/datum/preference/toggle/nuke_ops_species)) - operative.set_species(/datum/species/human) - - operative.equip_species_outfit(nukeop_outfit) - - return TRUE - -/datum/antagonist/nukeop/greet() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0, use_reverb = FALSE) - to_chat(owner, span_big("You are a [nuke_team ? nuke_team.syndicate_name : "syndicate"] agent!")) - owner.announce_objectives() - -/datum/antagonist/nukeop/on_gain() - give_alias() - forge_objectives() - owner.current.add_personality(/datum/personality/callous) - . = ..() - equip_op() - if(send_to_spawnpoint) - move_to_spawnpoint() - // grant extra TC for the people who start in the nukie base ie. not the lone op - var/extra_tc = CEILING(GLOB.joined_player_list.len/5, 5) - var/datum/component/uplink/uplink = owner.find_syndicate_uplink() - if (uplink) - uplink.add_telecrystals(extra_tc) - - var/datum/component/uplink/uplink = owner.find_syndicate_uplink() - if(uplink) - var/datum/team/nuclear/nuke_team = get_team() - if(!nuke_team.team_discounts) - var/list/uplink_items = list() - for(var/datum/uplink_item/item as anything in SStraitor.uplink_items) - if(item.item && !item.cant_discount && (item.purchasable_from & uplink.uplink_handler.uplink_flag) && item.cost > 1) - uplink_items += item - nuke_team.team_discounts = list() - nuke_team.team_discounts += create_uplink_sales(discount_team_amount, /datum/uplink_category/discount_team_gear, -1, uplink_items) - nuke_team.team_discounts += create_uplink_sales(discount_limited_amount, /datum/uplink_category/limited_discount_team_gear, 1, uplink_items) - uplink.uplink_handler.extra_purchasable += nuke_team.team_discounts - - memorize_code() - -/datum/antagonist/nukeop/get_team() - return nuke_team - -/datum/antagonist/nukeop/apply_innate_effects(mob/living/mob_override) - add_team_hud(mob_override || owner.current, /datum/antagonist/nukeop) - -/datum/antagonist/nukeop/proc/assign_nuke() - if(nuke_team && !nuke_team.tracked_nuke) - nuke_team.memorized_code = random_nukecode() - var/obj/machinery/nuclearbomb/syndicate/nuke = locate() in SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/syndicate) - if(nuke) - nuke_team.tracked_nuke = nuke - if(nuke.r_code == NUKE_CODE_UNSET) - nuke.r_code = nuke_team.memorized_code - else //Already set by admins/something else? - nuke_team.memorized_code = nuke.r_code - for(var/obj/machinery/nuclearbomb/beer/beernuke as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/beer)) - beernuke.r_code = nuke_team.memorized_code - else - stack_trace("Syndicate nuke not found during nuke team creation.") - nuke_team.memorized_code = null - -/datum/antagonist/nukeop/proc/give_alias() - if(nuke_team?.syndicate_name) - var/mob/living/carbon/human/human_to_rename = owner.current - if(istype(human_to_rename)) // Reinforcements get a real name - var/first_name = owner.current.client?.prefs?.read_preference(/datum/preference/name/operative_alias) || pick(GLOB.operative_aliases) - var/chosen_name = "[first_name] [nuke_team.syndicate_name]" - human_to_rename.fully_replace_character_name(human_to_rename.real_name, chosen_name) - else - var/number = 1 - number = nuke_team.members.Find(owner) - owner.current.real_name = "[nuke_team.syndicate_name] Operative #[number]" - -/datum/antagonist/nukeop/proc/memorize_code() - if(nuke_team && nuke_team.tracked_nuke && nuke_team.memorized_code) - antag_memory += "[nuke_team.tracked_nuke] Code: [nuke_team.memorized_code]
" - owner.add_memory(/datum/memory/key/nuke_code, nuclear_code = nuke_team.memorized_code) - to_chat(owner, "The nuclear authorization code is: [nuke_team.memorized_code]") - else - to_chat(owner, "Unfortunately the syndicate was unable to provide you with nuclear authorization code.") - -/datum/antagonist/nukeop/forge_objectives() - if(nuke_team) - objectives |= nuke_team.objectives - -/// Actually moves our nukie to where they should be -/datum/antagonist/nukeop/proc/move_to_spawnpoint() - // Ensure that the nukiebase is loaded, and wait for it if required - SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE) - var/turf/destination = get_spawnpoint() - owner.current.forceMove(destination) - if(!owner.current.onSyndieBase()) - message_admins("[ADMIN_LOOKUPFLW(owner.current)] is a NUKE OP and move_to_spawnpoint put them somewhere that isn't the syndie base, help please.") - stack_trace("Nuke op move_to_spawnpoint resulted in a location not on the syndicate base. (Was moved to: [destination])") - -/// Gets the position we spawn at -/datum/antagonist/nukeop/proc/get_spawnpoint() - var/team_number = 1 - if(nuke_team) - team_number = nuke_team.members.Find(owner) - - return GLOB.nukeop_start[((team_number - 1) % GLOB.nukeop_start.len) + 1] - -/datum/antagonist/nukeop/leader/get_spawnpoint() - return pick(GLOB.nukeop_leader_start) - -/datum/antagonist/nukeop/create_team(datum/team/nuclear/new_team) - if(!new_team) - if(!always_new_team) - for(var/datum/antagonist/nukeop/N in GLOB.antagonists) - if(!N.owner) - stack_trace("Antagonist datum without owner in GLOB.antagonists: [N]") - continue - if(N.nuke_team) - nuke_team = N.nuke_team - return - nuke_team = new /datum/team/nuclear - nuke_team.update_objectives() - assign_nuke() //This is bit ugly - return - if(!istype(new_team)) - stack_trace("Wrong team type passed to [type] initialization.") - nuke_team = new_team - -/datum/antagonist/nukeop/admin_add(datum/mind/new_owner,mob/admin) - new_owner.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative)) - new_owner.add_antag_datum(src) - message_admins("[key_name_admin(admin)] has nuke op'ed [key_name_admin(new_owner)].") - log_admin("[key_name(admin)] has nuke op'ed [key_name(new_owner)].") - -/datum/antagonist/nukeop/get_admin_commands() - . = ..() - .["Send to base"] = CALLBACK(src, PROC_REF(admin_send_to_base)) - .["Tell code"] = CALLBACK(src, PROC_REF(admin_tell_code)) - -/datum/antagonist/nukeop/proc/admin_send_to_base(mob/admin) - owner.current.forceMove(pick(GLOB.nukeop_start)) - -/datum/antagonist/nukeop/proc/admin_tell_code(mob/admin) - var/code - for (var/obj/machinery/nuclearbomb/bombue as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb)) - if (length(bombue.r_code) <= 5 && bombue.r_code != initial(bombue.r_code)) - code = bombue.r_code - break - if (code) - antag_memory += "Syndicate Nuclear Bomb Code: [code]
" - to_chat(owner.current, "The nuclear authorization code is: [code]") - else - to_chat(admin, span_danger("No valid nuke found!")) - -/datum/antagonist/nukeop/get_preview_icon() - if (!preview_outfit) - return null - - var/icon/final_icon = render_preview_outfit(preview_outfit) - - if (!isnull(preview_outfit_behind)) - var/icon/teammate = render_preview_outfit(preview_outfit_behind) - teammate.Blend(rgb(128, 128, 128, 128), ICON_MULTIPLY) - - final_icon.Blend(teammate, ICON_UNDERLAY, -world.icon_size / 4, 0) - final_icon.Blend(teammate, ICON_UNDERLAY, world.icon_size / 4, 0) - - if (!isnull(nuke_icon_state)) - var/icon/nuke = icon('icons/obj/machines/nuke.dmi', nuke_icon_state) - nuke.Shift(SOUTH, 6) - final_icon.Blend(nuke, ICON_OVERLAY) - - return finish_preview_icon(final_icon) - -/datum/outfit/nuclear_operative - name = "Nuclear Operative (Preview only)" - - back = /obj/item/mod/control/pre_equipped/empty/syndicate - uniform = /obj/item/clothing/under/syndicate - -/datum/outfit/nuclear_operative_elite - name = "Nuclear Operative (Elite, Preview only)" - - back = /obj/item/mod/control/pre_equipped/empty/elite - uniform = /obj/item/clothing/under/syndicate - l_hand = /obj/item/modular_computer/pda/nukeops - r_hand = /obj/item/shield/energy - -/datum/outfit/nuclear_operative_elite/post_equip(mob/living/carbon/human/H, visualsOnly) - var/obj/item/shield/energy/shield = locate() in H.held_items - shield.icon_state = "[shield.base_icon_state]1" - H.update_held_items() - -/datum/antagonist/nukeop/leader - name = "Nuclear Operative Leader" - nukeop_outfit = /datum/outfit/syndicate/leader - always_new_team = TRUE - /// Randomly chosen honorific, for distinction - var/title - /// The nuclear challenge remote we will spawn this player with. - var/challengeitem = /obj/item/nuclear_challenge - -/datum/antagonist/nukeop/leader/memorize_code() - ..() - if(nuke_team?.memorized_code) - var/obj/item/paper/nuke_code_paper = new - nuke_code_paper.add_raw_text("The nuclear authorization code is: [nuke_team.memorized_code]") - nuke_code_paper.name = "nuclear bomb code" - var/mob/living/carbon/human/H = owner.current - if(!istype(H)) - nuke_code_paper.forceMove(get_turf(H)) - else - H.put_in_hands(nuke_code_paper, TRUE) - H.update_icons() - -/datum/antagonist/nukeop/leader/greet() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0, use_reverb = FALSE) - to_chat(owner, "You are the Syndicate [title] for this mission. You are responsible for guiding the team and your ID is the only one who can open the launch bay doors.") - to_chat(owner, "If you feel you are not up to this task, give your ID and radio to another operative.") - if(!CONFIG_GET(flag/disable_warops)) - to_chat(owner, "In your hand you will find a special item capable of triggering a greater challenge for your team. Examine it carefully and consult with your fellow operatives before activating it.") - owner.announce_objectives() - -/datum/antagonist/nukeop/leader/on_gain() - . = ..() - if(!CONFIG_GET(flag/disable_warops)) - var/mob/living/carbon/human/leader = owner.current - var/obj/item/war_declaration = new challengeitem(leader.drop_location()) - leader.put_in_hands(war_declaration) - nuke_team.war_button_ref = WEAKREF(war_declaration) - addtimer(CALLBACK(src, PROC_REF(nuketeam_name_assign)), 1) - -/datum/antagonist/nukeop/leader/proc/nuketeam_name_assign() - if(!nuke_team) - return - nuke_team.rename_team(ask_name()) - -/datum/team/nuclear/proc/rename_team(new_name) - syndicate_name = new_name - name = "[syndicate_name] Team" - for(var/I in members) - var/datum/mind/synd_mind = I - var/mob/living/carbon/human/human_to_rename = synd_mind.current - if(!istype(human_to_rename)) - continue - var/first_name = human_to_rename.client?.prefs?.read_preference(/datum/preference/name/operative_alias) || pick(GLOB.operative_aliases) - var/chosen_name = "[first_name] [syndicate_name]" - human_to_rename.fully_replace_character_name(human_to_rename.real_name, chosen_name) - -/datum/antagonist/nukeop/leader/proc/ask_name() - var/randomname = pick(GLOB.last_names) - var/newname = tgui_input_text(owner.current, "You are the nuclear operative [title]. Please choose a last name for your family.", "Name change", randomname, MAX_NAME_LEN) - if (!newname) - newname = randomname - else - newname = reject_bad_name(newname) - if(!newname) - newname = randomname - - return capitalize(newname) - -/datum/antagonist/nukeop/lone - name = "Lone Operative" - always_new_team = TRUE - send_to_spawnpoint = FALSE //Handled by event - nukeop_outfit = /datum/outfit/syndicate/full - preview_outfit = /datum/outfit/nuclear_operative - preview_outfit_behind = null - nuke_icon_state = null - -/datum/antagonist/nukeop/lone/assign_nuke() - if(nuke_team && !nuke_team.tracked_nuke) - nuke_team.memorized_code = random_nukecode() - var/obj/machinery/nuclearbomb/selfdestruct/nuke = locate() in SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/selfdestruct) - if(nuke) - nuke_team.tracked_nuke = nuke - if(nuke.r_code == NUKE_CODE_UNSET) - nuke.r_code = nuke_team.memorized_code - else //Already set by admins/something else? - nuke_team.memorized_code = nuke.r_code - else - stack_trace("Station self-destruct not found during lone op team creation.") - nuke_team.memorized_code = null - -/datum/antagonist/nukeop/reinforcement - show_in_antagpanel = FALSE - send_to_spawnpoint = FALSE - nukeop_outfit = /datum/outfit/syndicate/reinforcement - -/datum/team/nuclear - var/syndicate_name - var/obj/machinery/nuclearbomb/tracked_nuke - var/core_objective = /datum/objective/nuclear - var/memorized_code - var/list/team_discounts - var/datum/weakref/war_button_ref - -/datum/team/nuclear/New() - ..() - syndicate_name = syndicate_name() - -/datum/team/nuclear/proc/update_objectives() - if(core_objective) - var/datum/objective/O = new core_objective - O.team = src - objectives += O - -/datum/team/nuclear/proc/is_disk_rescued() - for(var/obj/item/disk/nuclear/nuke_disk in SSpoints_of_interest.real_nuclear_disks) - //If emergency shuttle is in transit disk is only safe on it - if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE) - if(!SSshuttle.emergency.is_in_shuttle_bounds(nuke_disk)) - return FALSE - //If shuttle escaped check if it's on centcom side - else if(SSshuttle.emergency.mode == SHUTTLE_ENDGAME) - if(!nuke_disk.onCentCom()) - return FALSE - else //Otherwise disk is safe when on station - var/turf/disk_turf = get_turf(nuke_disk) - if(!disk_turf || !is_station_level(disk_turf.z)) - return FALSE - return TRUE - -/datum/team/nuclear/proc/are_all_operatives_dead() - for(var/datum/mind/operative_mind as anything in members) - if(ishuman(operative_mind.current) && (operative_mind.current.stat != DEAD)) - return FALSE - return TRUE - -/datum/team/nuclear/proc/get_result() - var/shuttle_evacuated = EMERGENCY_ESCAPED_OR_ENDGAMED - var/shuttle_landed_base = SSshuttle.emergency.is_hijacked() - var/disk_rescued = is_disk_rescued() - var/syndies_didnt_escape = !is_infiltrator_docked_at_syndiebase() - var/team_is_dead = are_all_operatives_dead() - var/station_was_nuked = GLOB.station_was_nuked - var/station_nuke_source = GLOB.station_nuke_source - - // The nuke detonated on the syndicate base - if(station_nuke_source == DETONATION_HIT_SYNDIE_BASE) - return NUKE_RESULT_FLUKE - - // The station was nuked - if(station_was_nuked) - // The station was nuked and the infiltrator failed to escape - if(syndies_didnt_escape) - return NUKE_RESULT_NOSURVIVORS - // The station was nuked and the infiltrator escaped, and the nuke ops won - else - return NUKE_RESULT_NUKE_WIN - - // The station was not nuked, but something was - else if(station_nuke_source && !disk_rescued) - // The station was not nuked, but something was, and the syndicates didn't escape it - if(syndies_didnt_escape) - return NUKE_RESULT_WRONG_STATION_DEAD - // The station was not nuked, but something was, and the syndicates returned to their base - else - return NUKE_RESULT_WRONG_STATION - - // Nuke didn't blow, but nukies somehow hijacked the emergency shuttle to land at the base anyways. - else if(shuttle_landed_base) - if(disk_rescued) - return NUKE_RESULT_HIJACK_DISK - else - return NUKE_RESULT_HIJACK_NO_DISK - - // No nuke went off, the station rescued the disk - else if(disk_rescued) - // No nuke went off, the shuttle left, and the team is dead - if(shuttle_evacuated && team_is_dead) - return NUKE_RESULT_CREW_WIN_SYNDIES_DEAD - // No nuke went off, but the nuke ops survived - else - return NUKE_RESULT_CREW_WIN - - // No nuke went off, but the disk was left behind - else - // No nuke went off, the disk was left, but all the ops are dead - if(team_is_dead) - return NUKE_RESULT_DISK_LOST - // No nuke went off, the disk was left, there are living ops, but the shuttle left successfully - else if(shuttle_evacuated) - return NUKE_RESULT_DISK_STOLEN - - CRASH("[type] - got an undefined / unexpected result.") - -/datum/team/nuclear/roundend_report() - var/list/parts = list() - parts += "[syndicate_name] Operatives:" - - switch(get_result()) - if(NUKE_RESULT_FLUKE) - parts += "Humiliating Syndicate Defeat" - parts += "The crew of [station_name()] gave [syndicate_name] operatives back their bomb! The syndicate base was destroyed! Next time, don't lose the nuke!" - if(NUKE_RESULT_NUKE_WIN) - parts += "Syndicate Major Victory!" - parts += "[syndicate_name] operatives have destroyed [station_name()]!" - if(NUKE_RESULT_NOSURVIVORS) - parts += "Total Annihilation!" - parts += "[syndicate_name] operatives destroyed [station_name()] but did not leave the area in time and got caught in the explosion. Next time, don't lose the disk!" - if(NUKE_RESULT_WRONG_STATION) - parts += "Crew Minor Victory!" - parts += "[syndicate_name] operatives secured the authentication disk but blew up something that wasn't [station_name()]. Next time, don't do that!" - if(NUKE_RESULT_WRONG_STATION_DEAD) - parts += "[syndicate_name] operatives have earned Darwin Award!" - parts += "[syndicate_name] operatives blew up something that wasn't [station_name()] and got caught in the explosion. Next time, don't do that!" - if(NUKE_RESULT_HIJACK_DISK) - parts += "Syndicate Miniscule Victory!" - parts += "[syndicate_name] operatives failed to destroy [station_name()], but they managed to secure the disk and hijack the emergency shuttle, causing it to land on the syndicate base. Good job?" - if(NUKE_RESULT_HIJACK_NO_DISK) - parts += "Syndicate Insignificant Victory!" - parts += "[syndicate_name] operatives failed to destroy [station_name()] or secure the disk, but they managed to hijack the emergency shuttle, causing it to land on the syndicate base. Good job?" - if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD) - parts += "Crew Major Victory!" - parts += "The Research Staff has saved the disk and killed the [syndicate_name] Operatives" - if(NUKE_RESULT_CREW_WIN) - parts += "Crew Major Victory!" - parts += "The Research Staff has saved the disk and stopped the [syndicate_name] Operatives!" - if(NUKE_RESULT_DISK_LOST) - parts += "Neutral Victory!" - parts += "The Research Staff failed to secure the authentication disk but did manage to kill most of the [syndicate_name] Operatives!" - if(NUKE_RESULT_DISK_STOLEN) - parts += "Syndicate Minor Victory!" - parts += "[syndicate_name] operatives survived the assault but did not achieve the destruction of [station_name()]. Next time, don't lose the disk!" - else - parts += "Neutral Victory" - parts += "Mission aborted!" - - var/text = "
The syndicate operatives were:" - var/purchases = "" - var/TC_uses = 0 - LAZYINITLIST(GLOB.uplink_purchase_logs_by_key) - for(var/I in members) - var/datum/mind/syndicate = I - var/datum/uplink_purchase_log/H = GLOB.uplink_purchase_logs_by_key[syndicate.key] - if(H) - TC_uses += H.total_spent - purchases += H.generate_render(show_key = FALSE) - text += printplayerlist(members) - text += "
" - text += "(Syndicates used [TC_uses] TC) [purchases]" - if(TC_uses == 0 && GLOB.station_was_nuked && !are_all_operatives_dead()) - text += "[icon2html('icons/ui_icons/antags/badass.dmi', world, "badass")]" - - parts += text - - return "
[parts.Join("
")]
" - -/datum/team/nuclear/antag_listing_name() - if(syndicate_name) - return "[syndicate_name] Syndicates" - else - return "Syndicates" - -/datum/team/nuclear/antag_listing_entry() - var/disk_report = "Nuclear Disk(s)
" - disk_report += "" - for(var/obj/item/disk/nuclear/N in SSpoints_of_interest.real_nuclear_disks) - disk_report += "" // Non-module change : 516 byond:// - disk_report += "
[N.name], " - var/atom/disk_loc = N.loc - while(!isturf(disk_loc)) - if(ismob(disk_loc)) - var/mob/M = disk_loc - disk_report += "carried by [M.real_name] " // Non-module change : 516 byond:// - if(isobj(disk_loc)) - var/obj/O = disk_loc - disk_report += "in \a [O.name] " - disk_loc = disk_loc.loc - disk_report += "in [disk_loc.loc] at ([disk_loc.x], [disk_loc.y], [disk_loc.z])FLW
" - - var/post_report - - var/war_declared = FALSE - for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards) - if(board.challenge) - war_declared = TRUE - - var/force_war_button = "" - - if(war_declared) - post_report += "War declared." - force_war_button = "\[Force war\]" - else - post_report += "War not declared." - var/obj/item/nuclear_challenge/war_button = war_button_ref?.resolve() - if(war_button) - force_war_button = "\[Force war\]" // Non-module change : 516 byond:// - else - force_war_button = "\[Cannot declare war, challenge button missing!\]" - - post_report += "\n[force_war_button]" - post_report += "\n\[Send Reinforcement\]" // Non-module change : 516 byond:// - - var/final_report = ..() - final_report += disk_report - final_report += post_report - return final_report - -#define SPAWN_AT_BASE "Nuke base" -#define SPAWN_AT_INFILTRATOR "Infiltrator" - -/datum/team/nuclear/proc/admin_spawn_reinforcement(mob/admin) - if(!check_rights_for(admin.client, R_ADMIN)) - return - - var/infil_or_nukebase = tgui_alert( - admin, - "Spawn them at the nuke base, or in the Infiltrator?", - "Where to reinforce?", - list(SPAWN_AT_BASE, SPAWN_AT_INFILTRATOR, "Cancel"), - ) - - if(!infil_or_nukebase || infil_or_nukebase == "Cancel") - return - - var/tc_to_spawn = tgui_input_number(admin, "How much TC to spawn with?", "TC", 0, 100) - - var/list/nuke_candidates = SSpolling.poll_ghost_candidates( - "Do you want to play as an emergency syndicate reinforcement?", - check_jobban = ROLE_OPERATIVE, - role = ROLE_OPERATIVE, - poll_time = 30 SECONDS, - ignore_category = POLL_IGNORE_SYNDICATE, - pic_source = /obj/structure/sign/poster/contraband/gorlex_recruitment, - role_name_text = "syndicate reinforcement", - ) - - nuke_candidates -= admin // may be easy to fat-finger say yes. so just don't - - if(!length(nuke_candidates)) - tgui_alert(admin, "No candidates found.", "Recruitment Shortage", list("OK")) - return - - - var/turf/spawn_loc - if(infil_or_nukebase == SPAWN_AT_INFILTRATOR) - var/area/spawn_in - // Prioritize EVA then hallway, if neither can be found default to the first area we can find - for(var/area_type in list(/area/shuttle/syndicate/eva, /area/shuttle/syndicate/hallway, /area/shuttle/syndicate)) - spawn_in = locate(area_type) in GLOB.areas // I'd love to use areas_by_type but the Infiltrator is a unique area - if(spawn_in) - break - - var/list/turf/options = list() - for(var/turf/open/open_turf in spawn_in?.get_turfs_from_all_zlevels()) - if(open_turf.is_blocked_turf()) - continue - options += open_turf - - if(length(options)) - spawn_loc = pick(options) - else - infil_or_nukebase = SPAWN_AT_BASE - - if(infil_or_nukebase == SPAWN_AT_BASE) - spawn_loc = pick(GLOB.nukeop_start) - - var/mob/dead/observer/picked = pick(nuke_candidates) - var/mob/living/carbon/human/nukie = new(spawn_loc) - picked.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE) - nukie.key = picked.key - - var/datum/antagonist/nukeop/antag_datum = new() - antag_datum.send_to_spawnpoint = FALSE - antag_datum.nukeop_outfit = /datum/outfit/syndicate/reinforcement - - nukie.mind.add_antag_datum(antag_datum, src) - - var/datum/component/uplink/uplink = nukie.mind.find_syndicate_uplink() - uplink?.set_telecrystals(tc_to_spawn) - - // add some pizzazz - do_sparks(4, FALSE, spawn_loc) - new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(spawn_loc) - playsound(spawn_loc, SFX_SPARKS, 50, TRUE) - playsound(spawn_loc, 'sound/effects/phasein.ogg', 50, TRUE) - - tgui_alert(admin, "Reinforcement spawned at [infil_or_nukebase] with [tc_to_spawn].", "Reinforcements have arrived", list("God speed")) - -#undef SPAWN_AT_BASE -#undef SPAWN_AT_INFILTRATOR - -/// Returns whether or not syndicate operatives escaped. -/proc/is_infiltrator_docked_at_syndiebase() - var/obj/docking_port/mobile/infiltrator/infiltrator_port = SSshuttle.getShuttle("syndicate") - - var/datum/lazy_template/nukie_base/nukie_template = GLOB.lazy_templates[LAZY_TEMPLATE_KEY_NUKIEBASE] - if(!nukie_template) - return FALSE // if its not even loaded, cant be docked - - for(var/datum/turf_reservation/loaded_area as anything in nukie_template.reservations) - var/infiltrator_turf = get_turf(infiltrator_port) - if(infiltrator_turf in loaded_area.reserved_turfs) - return TRUE - return FALSE diff --git a/code/modules/antagonists/obsessed/obsessed.dm b/code/modules/antagonists/obsessed/obsessed.dm index 6fb2267904a6..e83f96dd6065 100644 --- a/code/modules/antagonists/obsessed/obsessed.dm +++ b/code/modules/antagonists/obsessed/obsessed.dm @@ -2,18 +2,29 @@ name = "Obsessed" show_in_antagpanel = TRUE antagpanel_category = ANTAG_GROUP_CREW - job_rank = ROLE_OBSESSED + pref_flag = ROLE_OBSESSED show_to_ghosts = TRUE antag_hud_name = "obsessed" show_name_in_check_antagonists = TRUE roundend_category = "obsessed" - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_SKIP_GLOBAL_LIST silent = TRUE //not actually silent, because greet will be called by the trauma anyway. suicide_cry = "FOR MY LOVE!!" preview_outfit = /datum/outfit/obsessed hardcore_random_bonus = TRUE var/datum/brain_trauma/special/obsessed/trauma +/// Dummy antag datum that will show the cured obsessed to admins +/datum/antagonist/former_obsessed + name = "Former Obsessed" + show_in_antagpanel = FALSE + show_name_in_check_antagonists = TRUE + antagpanel_category = ANTAG_GROUP_CREW + show_in_roundend = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST + silent = TRUE + can_elimination_hijack = ELIMINATION_PREVENT + /datum/antagonist/obsessed/admin_add(datum/mind/new_owner,mob/admin) var/mob/living/carbon/C = new_owner.current if(!istype(C)) @@ -169,7 +180,7 @@ /datum/objective/assassinate/obsessed/update_explanation_text() ..() if(target?.current) - explanation_text = "Murder [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role]." + explanation_text = "Murder [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())]." else message_admins("WARNING! [ADMIN_LOOKUPFLW(owner)] obsessed objectives forged without an obsession!") explanation_text = "Free Objective" diff --git a/code/modules/antagonists/paradox_clone/paradox_clone.dm b/code/modules/antagonists/paradox_clone/paradox_clone.dm index e809e8cecbf0..e9c07c581e68 100644 --- a/code/modules/antagonists/paradox_clone/paradox_clone.dm +++ b/code/modules/antagonists/paradox_clone/paradox_clone.dm @@ -1,7 +1,7 @@ /datum/antagonist/paradox_clone name = "\improper Paradox Clone" roundend_category = "Paradox Clone" - job_rank = ROLE_PARADOX_CLONE + pref_flag = ROLE_PARADOX_CLONE antagpanel_category = ANTAG_GROUP_PARADOX antag_hud_name = "paradox_clone" show_to_ghosts = TRUE @@ -28,17 +28,6 @@ return clone_icon -/datum/antagonist/paradox_clone/on_gain() - owner.special_role = ROLE_PARADOX_CLONE - return ..() - -/datum/antagonist/paradox_clone/on_removal() - //don't null it if we got a different one added on top, somehow. - if(owner.special_role == ROLE_PARADOX_CLONE) - owner.special_role = null - original_ref = null - return ..() - /datum/antagonist/paradox_clone/Destroy() original_ref = null return ..() @@ -52,7 +41,7 @@ kill.update_explanation_text() objectives += kill - owner.set_assigned_role(SSjob.GetJobType(/datum/job/paradox_clone)) + owner.set_assigned_role(SSjob.get_job_type(/datum/job/paradox_clone)) //clone doesnt show up on message lists var/obj/item/modular_computer/pda/messenger = locate() in owner.current @@ -94,7 +83,7 @@ if(!target?.current) explanation_text = "Free Objective" CRASH("WARNING! [ADMIN_LOOKUPFLW(owner)] paradox clone objectives forged without an original!") - explanation_text = "Murder and replace [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role]. Remember, your mission is to blend in, do not kill anyone else unless you have to!" + explanation_text = "Murder and replace [target.name], the [!target_role_type ? target.assigned_role.title : english_list(target.get_special_roles())]. Remember, your mission is to blend in, do not kill anyone else unless you have to!" ///Static bluespace stream used in its ghost poll icon. /obj/effect/bluespace_stream diff --git a/code/modules/antagonists/pirate/pirate.dm b/code/modules/antagonists/pirate/pirate.dm index 5f21449163e3..4ee393f396c1 100644 --- a/code/modules/antagonists/pirate/pirate.dm +++ b/code/modules/antagonists/pirate/pirate.dm @@ -1,6 +1,6 @@ /datum/antagonist/pirate name = "\improper Space Pirate" - job_rank = ROLE_TRAITOR + pref_flag = ROLE_TRAITOR roundend_category = "space pirates" antagpanel_category = ANTAG_GROUP_PIRATES show_in_antagpanel = FALSE diff --git a/code/modules/antagonists/pirate/pirate_event.dm b/code/modules/antagonists/pirate/pirate_event.dm deleted file mode 100644 index f3c6655a2757..000000000000 --- a/code/modules/antagonists/pirate/pirate_event.dm +++ /dev/null @@ -1,119 +0,0 @@ -#define NO_ANSWER 0 -#define POSITIVE_ANSWER 1 -#define NEGATIVE_ANSWER 2 - -/datum/round_event_control/pirates - name = "Space Pirates" - typepath = /datum/round_event/pirates - weight = 10 - max_occurrences = 1 - min_players = 20 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_INVASION - description = "The crew will either pay up, or face a pirate assault." - admin_setup = list(/datum/event_admin_setup/listed_options/pirates) - map_flags = EVENT_SPACE_ONLY - -/datum/round_event_control/pirates/preRunEvent() - if (SSmapping.is_planetary()) - return EVENT_CANT_RUN - return ..() - -/datum/round_event/pirates - ///admin chosen pirate team - var/list/datum/pirate_gang/gang_list - -/datum/round_event/pirates/start() - send_pirate_threat(gang_list) - -/proc/send_pirate_threat(list/pirate_selection) - var/datum/pirate_gang/chosen_gang = pick_n_take(pirate_selection) - ///If there was nothing to pull from our requested list, stop here. - if(!chosen_gang) - message_admins("Error attempting to run the space pirate event, as the given pirate gangs list was empty.") - return - //set payoff - var/payoff = 0 - var/datum/bank_account/account = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(account) - payoff = max(PAYOFF_MIN, FLOOR(account.account_balance * 0.80, 1000)) - var/datum/comm_message/threat = chosen_gang.generate_message(payoff) - //send message - priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", SSstation.announcer.get_rand_report_sound()) - threat.answer_callback = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(pirates_answered), threat, chosen_gang, payoff, world.time) - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(spawn_pirates), threat, chosen_gang), RESPONSE_MAX_TIME) - SScommunications.send_message(threat, unique = TRUE) - -/proc/pirates_answered(datum/comm_message/threat, datum/pirate_gang/chosen_gang, payoff, initial_send_time) - if(world.time > initial_send_time + RESPONSE_MAX_TIME) - priority_announce(chosen_gang.response_too_late, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color) - return - if(!threat?.answered) - return - if(threat.answered == NEGATIVE_ANSWER) - priority_announce(chosen_gang.response_rejected, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color) - return - - var/datum/bank_account/plundered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(plundered_account) - if(plundered_account.adjust_money(-payoff)) - chosen_gang.paid_off = TRUE - priority_announce(chosen_gang.response_received, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color) - else - priority_announce(chosen_gang.response_not_enough, sender_override = chosen_gang.ship_name, color_override = chosen_gang.announcement_color) - -/proc/spawn_pirates(datum/comm_message/threat, datum/pirate_gang/chosen_gang) - if(chosen_gang.paid_off) - return - - var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a pirate crew of [chosen_gang.name]?", check_jobban = ROLE_TRAITOR, pic_source = /obj/item/claymore/cutlass, role_name_text = "pirate crew") - shuffle_inplace(candidates) - - var/template_key = "pirate_[chosen_gang.ship_template_id]" - var/datum/map_template/shuttle/pirate/ship = SSmapping.shuttle_templates[template_key] - var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width) - var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height) - var/z = SSmapping.empty_space.z_value - var/turf/T = locate(x,y,z) - if(!T) - CRASH("Pirate event found no turf to load in") - - if(!ship.load(T)) - CRASH("Loading pirate ship failed!") - - for(var/turf/area_turf as anything in ship.get_affected_turfs(T)) - for(var/obj/effect/mob_spawn/ghost_role/human/pirate/spawner in area_turf) - if(candidates.len > 0) - var/mob/our_candidate = candidates[1] - var/mob/spawned_mob = spawner.create_from_ghost(our_candidate) - candidates -= our_candidate - notify_ghosts( - "The [chosen_gang.ship_name] has an object of interest: [spawned_mob]!", - source = spawned_mob, - header = "Pirates!", - ) - else - notify_ghosts( - "The [chosen_gang.ship_name] has an object of interest: [spawner]!", - source = spawner, - header = "Pirate Spawn Here!", - ) - - priority_announce(chosen_gang.arrival_announcement, sender_override = chosen_gang.ship_name) - -/datum/event_admin_setup/listed_options/pirates - input_text = "Select Pirate Gang" - normal_run_option = "Random Pirate Gang" - -/datum/event_admin_setup/listed_options/pirates/get_list() - return subtypesof(/datum/pirate_gang) - -/datum/event_admin_setup/listed_options/pirates/apply_to_event(datum/round_event/pirates/event) - if(isnull(chosen)) - event.gang_list = GLOB.light_pirate_gangs + GLOB.heavy_pirate_gangs - else - event.gang_list = list(new chosen) - -#undef NO_ANSWER -#undef POSITIVE_ANSWER -#undef NEGATIVE_ANSWER diff --git a/code/modules/antagonists/pyro_slime/pyro_slime.dm b/code/modules/antagonists/pyro_slime/pyro_slime.dm index aed278d261d9..d5a70ecb402c 100644 --- a/code/modules/antagonists/pyro_slime/pyro_slime.dm +++ b/code/modules/antagonists/pyro_slime/pyro_slime.dm @@ -5,6 +5,7 @@ show_in_antagpanel = FALSE show_name_in_check_antagonists = TRUE show_to_ghosts = TRUE + pref_flag = ROLE_PYROCLASTIC_SLIME /datum/antagonist/pyro_slime/on_gain() forge_objectives() diff --git a/code/modules/antagonists/revolution/enemy_of_the_state.dm b/code/modules/antagonists/revolution/enemy_of_the_state.dm index 90a6431d428b..417ee0d543ea 100644 --- a/code/modules/antagonists/revolution/enemy_of_the_state.dm +++ b/code/modules/antagonists/revolution/enemy_of_the_state.dm @@ -23,7 +23,7 @@ /datum/antagonist/enemy_of_the_state/on_gain() owner.add_memory(/datum/memory/revolution_rev_defeat) - owner.special_role = "exiled headrev" + // LAZYADD(owner.special_statuses, "Exiled Head Revolutionary") forge_objectives() . = ..() diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm index b22a4ec0a7e5..6e494334552c 100644 --- a/code/modules/antagonists/revolution/revolution.dm +++ b/code/modules/antagonists/revolution/revolution.dm @@ -2,7 +2,7 @@ name = "\improper Revolutionary" roundend_category = "revolutionaries" // if by some miracle revolutionaries without revolution happen antagpanel_category = "Revolution" - job_rank = ROLE_REV + pref_flag = ROLE_REV antag_moodlet = /datum/mood_event/revolution antag_hud_name = "rev" suicide_cry = "VIVA LA REVOLUTION!!" @@ -56,14 +56,9 @@ /datum/antagonist/rev/on_gain() . = ..() - create_objectives() equip_rev() owner.current.log_message("has been converted to the revolution!", LOG_ATTACK, color="red") -/datum/antagonist/rev/on_removal() - remove_objectives() - . = ..() - /datum/antagonist/rev/greet() . = ..() to_chat(owner, span_userdanger("Help your cause. Do not harm your fellow freedom fighters. You can identify your comrades by the red \"R\" icons, and your leaders by the blue \"R\" icons. Help them kill the heads to win the revolution!")) @@ -72,16 +67,9 @@ /datum/antagonist/rev/create_team(datum/team/revolution/new_team) if(!new_team) - //For now only one revolution at a time - for(var/datum/antagonist/rev/head/H in GLOB.antagonists) - if(!H.owner) - continue - if(H.rev_team) - rev_team = H.rev_team - return - rev_team = new /datum/team/revolution - rev_team.update_objectives() - rev_team.update_rev_heads() + GLOB.revolution_handler ||= new() + rev_team = GLOB.revolution_handler.revs + GLOB.revolution_handler.start_revolution() return if(!istype(new_team)) stack_trace("Wrong team type passed to [type] initialization.") @@ -90,12 +78,6 @@ /datum/antagonist/rev/get_team() return rev_team -/datum/antagonist/rev/proc/create_objectives() - objectives |= rev_team.objectives - -/datum/antagonist/rev/proc/remove_objectives() - objectives -= rev_team.objectives - //Bump up to head_rev /datum/antagonist/rev/proc/promote() var/old_team = rev_team @@ -173,7 +155,7 @@ /datum/antagonist/rev/head name = "\improper Head Revolutionary" antag_hud_name = "rev_head" - job_rank = ROLE_REV_HEAD + pref_flag = ROLE_REV_HEAD preview_outfit = /datum/outfit/revolutionary hardcore_random_bonus = TRUE @@ -297,7 +279,6 @@ rev_mind.add_memory(/datum/memory/recruited_by_headrev, protagonist = rev_mind.current, antagonist = owner.current) rev_mind.add_antag_datum(/datum/antagonist/rev,rev_team) - rev_mind.special_role = ROLE_REV return TRUE /datum/antagonist/rev/head/proc/demote() @@ -337,7 +318,9 @@ owner.current.log_message("has been deconverted from the revolution by [ismob(deconverter) ? key_name(deconverter) : deconverter]!", LOG_ATTACK, color=COLOR_CULT_RED) if(deconverter == DECONVERTER_BORGED) message_admins("[ADMIN_LOOKUPFLW(owner.current)] has been borged while being a [name]") - owner.special_role = null + if(iscarbon(owner.current) && deconverter) + var/mob/living/carbon/formerrev = owner.current + formerrev.Unconscious(10 SECONDS) deconversion_source = deconverter owner.remove_antag_datum(type) @@ -374,40 +357,19 @@ /datum/team/revolution name = "\improper Revolution" - /// Maximum number of headrevs var/max_headrevs = 3 /// List of all ex-headrevs. Useful because dynamic removes antag status when it ends, so this can be kept for the roundend report. - var/list/ex_headrevs = list() + var/list/datum/mind/ex_headrevs = list() /// List of all ex-revs. Useful because dynamic removes antag status when it ends, so this can be kept for the roundend report. - var/list/ex_revs = list() - - /// The objective of the heads of staff, aka to kill the headrevs. - var/list/datum/objective/mutiny/heads_objective = list() - -/// Proc called on periodic timer. -/// Updates the rev team's objectives to make sure all heads are targets, useful when new heads latejoin. -/// Propagates all objectives to all revs. -/datum/team/revolution/proc/update_objectives(initial = FALSE) - var/untracked_heads = SSjob.get_all_heads() + var/list/datum/mind/ex_revs = list() - for(var/datum/objective/mutiny/mutiny_objective in objectives) - untracked_heads -= mutiny_objective.target - - for(var/datum/mind/extra_mutiny_target in untracked_heads) - var/datum/objective/mutiny/new_target = new() - new_target.team = src - new_target.target = extra_mutiny_target - new_target.update_explanation_text() - objectives += new_target - - for(var/datum/mind/rev_member in members) - var/datum/antagonist/rev/rev_antag = rev_member.has_antag_datum(/datum/antagonist/rev) - rev_antag.objectives |= objectives - - addtimer(CALLBACK(src, PROC_REF(update_objectives)), HEAD_UPDATE_PERIOD, TIMER_UNIQUE) +/// Saves all current headrevs and revs +/datum/team/revolution/proc/save_members() + ex_headrevs = get_head_revolutionaries() + ex_revs = members - ex_headrevs /// Returns a list of all headrevs. /datum/team/revolution/proc/get_head_revolutionaries() @@ -419,129 +381,39 @@ return headrev_list -/// Proc called on periodic timer. +/datum/team/revolution/proc/headrev_cap() + var/list/datum/mind/heads = SSjob.get_all_heads() + var/list/sec = SSjob.get_all_sec() + + return clamp(round(length(heads) - ((8 - length(sec)) / 3)), 1, max_headrevs) + /// Tries to make sure an appropriate number of headrevs are part of the revolution. /// Will promote up revs to headrevs as necessary based on the hard max_headrevs cap and the soft cap based on the number of heads of staff and sec. /datum/team/revolution/proc/update_rev_heads() - if(SSticker.HasRoundStarted()) - var/list/datum/mind/head_revolutionaries = get_head_revolutionaries() - var/list/datum/mind/heads = SSjob.get_all_heads() - var/list/sec = SSjob.get_all_sec() - - if(head_revolutionaries.len < max_headrevs && head_revolutionaries.len < round(heads.len - ((8 - sec.len) / 3))) - var/list/datum/mind/non_heads = members - head_revolutionaries - var/list/datum/mind/promotable = list() - var/list/datum/mind/monkey_promotable = list() - for(var/datum/mind/khrushchev in non_heads) - if(khrushchev.current && !khrushchev.current.incapacitated() && !HAS_TRAIT(khrushchev.current, TRAIT_RESTRAINED) && khrushchev.current.client) - if((ROLE_REV_HEAD in khrushchev.current.client.prefs.be_special) || (ROLE_PROVOCATEUR in khrushchev.current.client.prefs.be_special)) - if(!ismonkey(khrushchev.current)) - promotable += khrushchev - else - monkey_promotable += khrushchev - if(!promotable.len && monkey_promotable.len) //if only monkey revolutionaries remain, promote one of them to the leadership. - promotable = monkey_promotable - if(promotable.len) - var/datum/mind/new_leader = pick(promotable) - var/datum/antagonist/rev/rev = new_leader.has_antag_datum(/datum/antagonist/rev) - rev.promote() - - addtimer(CALLBACK(src, PROC_REF(update_rev_heads)),HEAD_UPDATE_PERIOD,TIMER_UNIQUE) - -/// Saves a list of all ex-headrevs and a list of all revs. -/datum/team/revolution/proc/save_members() - ex_headrevs = get_antag_minds(/datum/antagonist/rev/head, TRUE) - ex_revs = get_antag_minds(/datum/antagonist/rev, TRUE) - -/// Checks if revs have won -/datum/team/revolution/proc/check_rev_victory() - for(var/datum/objective/mutiny/objective in objectives) - if(!(objective.check_completion())) - return FALSE - return TRUE + var/list/datum/mind/head_revolutionaries = get_head_revolutionaries() -/// Checks if heads have won -/datum/team/revolution/proc/check_heads_victory() - // List of headrevs we're currently tracking - var/list/included_headrevs = list() - // List of current headrevs - var/list/current_headrevs = get_head_revolutionaries() - // A copy of the head of staff objective list, since we're going to be modifying the original list. - var/list/heads_objective_copy = heads_objective.Copy() - - var/objective_complete = TRUE - // Here, we check current head of staff objectives and remove them if the target doesn't exist as a headrev anymore - for(var/datum/objective/mutiny/objective in heads_objective_copy) - if(!(objective.target in current_headrevs)) - heads_objective -= objective - continue - if(!objective.check_completion()) - objective_complete = FALSE - included_headrevs += objective.target - - // Here, we check current headrevs and add them as objectives if they didn't exist as a head of staff objective before. - // Additionally, we make sure the objective is not completed by running the check_completion check on them. - for(var/datum/mind/rev_mind as anything in current_headrevs) - if(!(rev_mind in included_headrevs)) - var/datum/objective/mutiny/objective = new() - objective.target = rev_mind - if(!objective.check_completion()) - objective_complete = FALSE - heads_objective += objective - - return objective_complete - -/// Updates the state of the world depending on if revs won or loss. -/// Returns who won, at which case this method should no longer be called. -/datum/team/revolution/proc/process_victory() - if (check_rev_victory()) - victory_effects() - return REVOLUTION_VICTORY - - if (!check_heads_victory()) + if(length(head_revolutionaries) >= headrev_cap()) return - . = STATION_VICTORY - - SSshuttle.clearHostileEnvironment(src) - - // Save rev lists before we remove the antag datums. - save_members() - - // Remove everyone as a revolutionary - for (var/datum/mind/rev_mind as anything in members) - var/datum/antagonist/rev/rev_antag = rev_mind.has_antag_datum(/datum/antagonist/rev) - if (!isnull(rev_antag)) - rev_antag.remove_revolutionary(DECONVERTER_STATION_WIN) - if(rev_mind in ex_headrevs) - LAZYADD(rev_mind.special_statuses, "Former head revolutionary") - else - LAZYADD(rev_mind.special_statuses, "Former revolutionary") - - defeat_effects() - -/// Handles any pre-round-ending effects on rev victory. An example use case is recording memories. -/datum/team/revolution/proc/victory_effects() - for(var/datum/mind/headrev_mind as anything in ex_headrevs) - var/mob/living/real_headrev = headrev_mind.current - if(isnull(real_headrev)) + var/list/datum/mind/promotable = list() + var/list/datum/mind/monkey_promotable = list() + for(var/datum/mind/khrushchev as anything in members - head_revolutionaries) + if(!can_be_headrev(khrushchev)) continue - add_memory_in_range(real_headrev, 5, /datum/memory/revolution_rev_victory, protagonist = real_headrev) - -/// Handles effects of revs losing, such as making ex-headrevs unrevivable and setting up head of staff memories. -/datum/team/revolution/proc/defeat_effects() - // If the revolution was quelled, make rev heads unable to be revived through pods - for (var/datum/mind/rev_head as anything in ex_headrevs) - if(!isnull(rev_head.current)) - ADD_TRAIT(rev_head.current, TRAIT_DEFIB_BLACKLISTED, REF(src)) - - for(var/datum/objective/mutiny/head_tracker in objectives) - var/mob/living/head_of_staff = head_tracker.target?.current - if(!isnull(head_of_staff)) - add_memory_in_range(head_of_staff, 5, /datum/memory/revolution_heads_victory, protagonist = head_of_staff) - - priority_announce("It appears the mutiny has been quelled. Please return yourself and your incapacitated colleagues to work. \ - We have remotely blacklisted the head revolutionaries in your medical records to prevent accidental revival.", null, null, null, "[command_name()] Loyalty Monitoring Division") + var/client/khruschevs_client = GET_CLIENT(khrushchev.current) + if(!(ROLE_REV_HEAD in khruschevs_client.prefs.be_special) && !(ROLE_PROVOCATEUR in khruschevs_client.prefs.be_special)) + continue + if(ismonkey(khrushchev.current)) + monkey_promotable += khrushchev + else + promotable += khrushchev + if(!length(promotable) && length(monkey_promotable)) + promotable = monkey_promotable + if(!length(promotable)) + return + var/datum/mind/new_leader = pick(promotable) + var/datum/antagonist/rev/rev = new_leader.has_antag_datum(/datum/antagonist/rev) + rev.promote() /// Mutates the ticker to report that the revs have won /datum/team/revolution/proc/round_result(finished) @@ -586,14 +458,14 @@ if(headrevs.len) var/list/headrev_part = list() - headrev_part += "The head revolutionaries were:" - headrev_part += printplayerlist(headrevs, !check_rev_victory()) + headrev_part += span_header("The head revolutionaries were:") + headrev_part += printplayerlist(headrevs, GLOB.revolution_handler.result != REVOLUTION_VICTORY) result += headrev_part.Join("
") if(revs.len) var/list/rev_part = list() - rev_part += "The revolutionaries were:" - rev_part += printplayerlist(revs, !check_rev_victory()) + rev_part += span_header("The revolutionaries were:") + rev_part += printplayerlist(revs, GLOB.revolution_handler.result != REVOLUTION_VICTORY) result += rev_part.Join("
") var/list/heads = SSjob.get_all_heads() diff --git a/code/modules/antagonists/revolution/revolution_handler.dm b/code/modules/antagonists/revolution/revolution_handler.dm new file mode 100644 index 000000000000..d3bdbf972693 --- /dev/null +++ b/code/modules/antagonists/revolution/revolution_handler.dm @@ -0,0 +1,159 @@ +GLOBAL_DATUM(revolution_handler, /datum/revolution_handler) + +/datum/revolution_handler + /// The revolution team + var/datum/team/revolution/revs + + /// The objective of the heads of staff, aka to kill the headrevs. + var/list/datum/objective/mutiny/heads_objective = list() + + /// Cooldown between head revs being promoted + COOLDOWN_DECLARE(rev_head_promote_cd) + + var/result + +/datum/revolution_handler/New() + revs = new() + +/datum/revolution_handler/proc/start_revolution() + if((datum_flags & DF_ISPROCESSING) || result) + return + START_PROCESSING(SSprocessing, src) + SSshuttle.registerHostileEnvironment(src) + + for(var/datum/mind/mutiny_target as anything in SSjob.get_all_heads()) + var/datum/objective/mutiny/new_target = new() + new_target.team = revs + new_target.target = mutiny_target + new_target.update_explanation_text() + revs.objectives += new_target + + RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN, PROC_REF(update_objectives)) + COOLDOWN_START(src, rev_head_promote_cd, 5 MINUTES) + +/datum/revolution_handler/proc/cleanup() + STOP_PROCESSING(SSprocessing, src) + SSshuttle.clearHostileEnvironment(src) + UnregisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN) + +/datum/revolution_handler/process(seconds_per_tick) + if(check_rev_victory()) + declare_revs_win() + . = PROCESS_KILL + + else if(check_heads_victory()) + declare_heads_win() + . = PROCESS_KILL + + if(. == PROCESS_KILL) + cleanup() + return . + + if(COOLDOWN_FINISHED(src, rev_head_promote_cd)) + revs.update_rev_heads() + COOLDOWN_START(src, rev_head_promote_cd, 5 MINUTES) + + return . + +/datum/revolution_handler/proc/update_objectives(datum/source, datum/job/job, mob/living/spawned) + SIGNAL_HANDLER + + if(!(job.job_flags & JOB_HEAD_OF_STAFF)) + return + + var/datum/objective/mutiny/new_target = new() + new_target.team = revs + new_target.target = spawned.mind + new_target.update_explanation_text() + revs.objectives += new_target + +/datum/revolution_handler/proc/declare_revs_win() + for(var/datum/mind/headrev_mind as anything in revs.ex_headrevs) + var/mob/living/real_headrev = headrev_mind.current + if(isnull(real_headrev)) + continue + add_memory_in_range(real_headrev, 5, /datum/memory/revolution_rev_victory, protagonist = real_headrev) + + result = REVOLUTION_VICTORY + +/datum/revolution_handler/proc/declare_heads_win() + // Save rev lists before we remove the antag datums. + revs.save_members() + + // Remove everyone as a revolutionary + for(var/datum/mind/rev_mind as anything in revs.members) + var/datum/antagonist/rev/rev_antag = rev_mind.has_antag_datum(/datum/antagonist/rev) + if (!isnull(rev_antag)) + rev_antag.remove_revolutionary(DECONVERTER_STATION_WIN) + if(rev_mind in revs.ex_headrevs) + LAZYADD(rev_mind.special_roles, "Former Head Revolutionary") + else + LAZYADD(rev_mind.special_roles, "Former Revolutionary") + + // If the revolution was quelled, make rev heads unable to be revived through pods + for(var/datum/mind/rev_head as anything in revs.ex_headrevs) + if(!isnull(rev_head.current)) + ADD_TRAIT(rev_head.current, TRAIT_DEFIB_BLACKLISTED, REF(src)) + + for(var/datum/objective/mutiny/head_tracker in revs.objectives) + var/mob/living/head_of_staff = head_tracker.target?.current + if(!isnull(head_of_staff)) + add_memory_in_range(head_of_staff, 5, /datum/memory/revolution_heads_victory, protagonist = head_of_staff) + + priority_announce("It appears the mutiny has been quelled. Please return yourself and your incapacitated colleagues to work. \ + We have remotely blacklisted the head revolutionaries in your medical records to prevent accidental revival.", null, null, null, "[command_name()] Loyalty Monitoring Division") + + result = STATION_VICTORY + +/datum/revolution_handler/proc/check_rev_victory() + for(var/datum/objective/mutiny/objective in revs.objectives) + if(!(objective.check_completion())) + return FALSE + return TRUE + +/datum/revolution_handler/proc/check_heads_victory() + // List of headrevs we're currently tracking + var/list/included_headrevs = list() + // List of current headrevs + var/list/current_headrevs = revs.get_head_revolutionaries() + // A copy of the head of staff objective list, since we're going to be modifying the original list. + var/list/heads_objective_copy = heads_objective.Copy() + + var/objective_complete = TRUE + // Here, we check current head of staff objectives and remove them if the target doesn't exist as a headrev anymore + for(var/datum/objective/mutiny/objective in heads_objective_copy) + if(!(objective.target in current_headrevs)) + heads_objective -= objective + continue + if(!objective.check_completion()) + objective_complete = FALSE + included_headrevs += objective.target + + // Here, we check current headrevs and add them as objectives if they didn't exist as a head of staff objective before. + // Additionally, we make sure the objective is not completed by running the check_completion check on them. + for(var/datum/mind/rev_mind as anything in current_headrevs) + if(!(rev_mind in included_headrevs)) + var/datum/objective/mutiny/objective = new() + objective.target = rev_mind + if(!objective.check_completion()) + objective_complete = FALSE + heads_objective += objective + + return objective_complete + +/// Checks if someone is valid to be a headrev +/proc/can_be_headrev(datum/mind/candidate) + var/turf/head_turf = get_turf(candidate.current) + if(considered_afk(candidate)) + return FALSE + if(!considered_alive(candidate)) + return FALSE + if(!is_station_level(head_turf.z)) + return FALSE + if(candidate.current.is_antag()) + return FALSE + if(candidate.assigned_role.job_flags & JOB_HEAD_OF_STAFF) + return FALSE + if(HAS_MIND_TRAIT(candidate.current, TRAIT_UNCONVERTABLE)) + return FALSE + return TRUE diff --git a/code/modules/antagonists/sentient_creature/sentient_creature.dm b/code/modules/antagonists/sentient_creature/sentient_creature.dm index d1197265ced5..79008f082398 100644 --- a/code/modules/antagonists/sentient_creature/sentient_creature.dm +++ b/code/modules/antagonists/sentient_creature/sentient_creature.dm @@ -2,7 +2,7 @@ name = "\improper Sentient Creature" show_in_antagpanel = FALSE show_in_roundend = FALSE - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST ui_name = "AntagInfoSentient" /datum/antagonist/sentient_creature/get_preview_icon() diff --git a/code/modules/antagonists/shade/shade_minion.dm b/code/modules/antagonists/shade/shade_minion.dm index 4b06452c4b79..4d7790e320a6 100644 --- a/code/modules/antagonists/shade/shade_minion.dm +++ b/code/modules/antagonists/shade/shade_minion.dm @@ -10,7 +10,7 @@ show_in_roundend = FALSE silent = TRUE ui_name = "AntagInfoShade" - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_SKIP_GLOBAL_LIST /// Name of this shade's master. var/master_name = "nobody?" diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm index 9c4d418426be..2725e7cba6d8 100644 --- a/code/modules/antagonists/space_dragon/space_dragon.dm +++ b/code/modules/antagonists/space_dragon/space_dragon.dm @@ -2,7 +2,7 @@ name = "\improper Space Dragon" roundend_category = "space dragons" antagpanel_category = ANTAG_GROUP_LEVIATHANS - job_rank = ROLE_SPACE_DRAGON + pref_flag = ROLE_SPACE_DRAGON show_in_antagpanel = FALSE show_name_in_check_antagonists = TRUE show_to_ghosts = TRUE @@ -66,13 +66,11 @@ /datum/antagonist/space_dragon/on_gain() forge_objectives() rift_ability = new() - owner.special_role = ROLE_SPACE_DRAGON - owner.set_assigned_role(SSjob.GetJobType(/datum/job/space_dragon)) + owner.set_assigned_role(SSjob.get_job_type(/datum/job/space_dragon)) return ..() /datum/antagonist/space_dragon/on_removal() - owner.special_role = null - owner.set_assigned_role(SSjob.GetJobType(/datum/job/unassigned)) + owner.set_assigned_role(SSjob.get_job_type(/datum/job/unassigned)) return ..() /datum/antagonist/space_dragon/apply_innate_effects(mob/living/mob_override) diff --git a/code/modules/antagonists/space_ninja/space_ninja.dm b/code/modules/antagonists/space_ninja/space_ninja.dm index 2d49138fc450..c0f3db8b1186 100644 --- a/code/modules/antagonists/space_ninja/space_ninja.dm +++ b/code/modules/antagonists/space_ninja/space_ninja.dm @@ -1,7 +1,7 @@ /datum/antagonist/ninja name = "\improper Space Ninja" antagpanel_category = ANTAG_GROUP_NINJAS - job_rank = ROLE_NINJA + pref_flag = ROLE_NINJA antag_hud_name = "ninja" hijack_speed = 1 show_name_in_check_antagonists = TRUE @@ -122,13 +122,18 @@ equip_space_ninja(owner.current) owner.current.add_quirk(/datum/quirk/freerunning, announce = FALSE) owner.current.add_quirk(/datum/quirk/light_step, announce = FALSE) - owner.current.mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_ninja)) - owner.current.mind.special_role = ROLE_NINJA + owner.current.mind.set_assigned_role(SSjob.get_job_type(/datum/job/space_ninja)) return ..() /datum/antagonist/ninja/admin_add(datum/mind/new_owner,mob/admin) - new_owner.set_assigned_role(SSjob.GetJobType(/datum/job/space_ninja)) - new_owner.special_role = ROLE_NINJA + new_owner.set_assigned_role(SSjob.get_job_type(/datum/job/space_ninja)) new_owner.add_antag_datum(src) message_admins("[key_name_admin(admin)] has ninja'ed [key_name_admin(new_owner)].") log_admin("[key_name(admin)] has ninja'ed [key_name(new_owner)].") + +/datum/antagonist/ninja/on_respawn(mob/new_character) + equip_space_ninja() + var/turf/spawnpoint = find_space_spawn() + if(spawnpoint) + new_character.forceMove(spawnpoint) + return TRUE diff --git a/code/modules/antagonists/survivalist/survivalist.dm b/code/modules/antagonists/survivalist/survivalist.dm index 2480b186600a..cef6a8f61f02 100644 --- a/code/modules/antagonists/survivalist/survivalist.dm +++ b/code/modules/antagonists/survivalist/survivalist.dm @@ -11,7 +11,6 @@ objectives += survive /datum/antagonist/survivalist/on_gain() - owner.special_role = "survivalist" forge_objectives() . = ..() diff --git a/code/modules/antagonists/syndicate_monkey/syndicate_monkey.dm b/code/modules/antagonists/syndicate_monkey/syndicate_monkey.dm index eae5d558a7cd..84ec707a8688 100644 --- a/code/modules/antagonists/syndicate_monkey/syndicate_monkey.dm +++ b/code/modules/antagonists/syndicate_monkey/syndicate_monkey.dm @@ -4,7 +4,7 @@ show_in_roundend = TRUE show_in_antagpanel = TRUE show_name_in_check_antagonists = TRUE - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_SKIP_GLOBAL_LIST show_to_ghosts = TRUE /// The antagonist's master, used for objective var/mob/living/monky_master diff --git a/code/modules/antagonists/traitor/components/demoraliser.dm b/code/modules/antagonists/traitor/components/demoraliser.dm index ee44527728c0..3d0ddf1fe3d5 100644 --- a/code/modules/antagonists/traitor/components/demoraliser.dm +++ b/code/modules/antagonists/traitor/components/demoraliser.dm @@ -52,7 +52,7 @@ return - if (is_special_character(viewer)) + if (viewer.is_antag()) to_chat(viewer, span_notice("[moods.antag_notification]")) viewer.add_mood_event(moods.mood_category, moods.antag_mood) else if (viewer.mind.assigned_role.departments_bitflags & (DEPARTMENT_BITFLAG_SECURITY|DEPARTMENT_BITFLAG_COMMAND)) diff --git a/code/modules/antagonists/traitor/contractor/contract_teammate.dm b/code/modules/antagonists/traitor/contractor/contract_teammate.dm index c97af9cfc812..d48d86269ecb 100644 --- a/code/modules/antagonists/traitor/contractor/contract_teammate.dm +++ b/code/modules/antagonists/traitor/contractor/contract_teammate.dm @@ -30,6 +30,8 @@ /// Support unit gets it's own very basic antag datum for admin logging. /datum/antagonist/traitor/contractor_support name = "Contractor Support Unit" + pref_flag = ROLE_CONTRACTOR_SUPPORT + employer = "Contractor Support Unit" show_in_roundend = FALSE give_objectives = TRUE give_uplink = FALSE diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index 119c54133042..df36231c572a 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -1,13 +1,13 @@ ///all the employers that are syndicate #define FLAVOR_FACTION_SYNDICATE "syndicate" -///all the employers that are nanotrasen +///all the employers that are Nanotrasen #define FLAVOR_FACTION_NANOTRASEN "nanotrasen" /datum/antagonist/traitor name = "\improper Traitor" roundend_category = "traitors" antagpanel_category = "Traitor" - job_rank = ROLE_TRAITOR + pref_flag = ROLE_TRAITOR antag_moodlet = /datum/mood_event/focused antag_hud_name = "traitor" hijack_speed = 0.5 //10 seconds per hijack stage by default @@ -17,6 +17,7 @@ can_assign_self_objectives = TRUE default_custom_objective = "Perform an overcomplicated heist on valuable Nanotrasen assets." hardcore_random_bonus = TRUE + stinger_sound = 'sound/music/antag/traitor/tatoralert.ogg' ///The flag of uplink that this traitor is supposed to have. var/uplink_flag_given = UPLINK_TRAITORS @@ -28,7 +29,7 @@ ///if TRUE, this traitor will always get hijacking as their final objective var/is_hijacker = FALSE - ///the name of the antag flavor this traitor has. + ///the name of the antag flavor this traitor has, set in Traitor's setup if not preset. var/employer ///assoc list of strings set up after employer is given @@ -40,7 +41,8 @@ /// The uplink handler that this traitor belongs to. var/datum/uplink_handler/uplink_handler - var/uplink_sale_count = 3 + var/uplink_sales_min = 4 + var/uplink_sales_max = 6 ///the final objective the traitor has to accomplish, be it escaping, hijacking, or just martyrdom. var/datum/objective/ending_objective @@ -50,20 +52,6 @@ src.give_objectives = give_objectives /datum/antagonist/traitor/on_gain() - owner.special_role = job_rank - - // NON-MODULE CHANGE: ADV TRAITORS - if(give_objectives) - forge_traitor_objectives() - - if(finalize_antag) - finalize_antag() - - return ..() - // NON-MODULE CHANGE END - -/* Moved to finalize_antags() - if(give_uplink) owner.give_uplink(silent = TRUE, antag_datum = src) @@ -87,14 +75,14 @@ var/list/uplink_items = list() for(var/datum/uplink_item/item as anything in SStraitor.uplink_items) - if(item.item && !item.cant_discount && (item.purchasable_from & uplink_handler.uplink_flag) && item.cost > 1) + if(item.item && !item.cant_discount && (item.purchasable_from & uplink_handler.uplink_flag) && item.cost >= TRAITOR_DISCOUNT_MIN_PRICE) if(!length(item.restricted_roles) && !length(item.restricted_species)) uplink_items += item continue if((uplink_handler.assigned_role in item.restricted_roles) || (uplink_handler.assigned_species in item.restricted_species)) uplink_items += item continue - uplink_handler.extra_purchasable += create_uplink_sales(uplink_sale_count, /datum/uplink_category/discounts, 1, uplink_items) + uplink_handler.extra_purchasable += create_uplink_sales(rand(uplink_sales_min, uplink_sales_max), /datum/uplink_category/discounts, -1, uplink_items) if(give_objectives) forge_traitor_objectives() @@ -102,15 +90,13 @@ pick_employer() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) return ..() -*/ + /datum/antagonist/traitor/on_removal() if(!isnull(uplink_handler)) uplink_handler.can_replace_objectives = null uplink_handler.replace_objectives = null owner.take_uplink() - owner.special_role = null return ..() /// Returns true if we're allowed to assign ourselves a new objective @@ -118,22 +104,23 @@ return can_assign_self_objectives /datum/antagonist/traitor/proc/pick_employer() - var/faction = prob(75) ? FLAVOR_FACTION_SYNDICATE : FLAVOR_FACTION_NANOTRASEN - var/list/possible_employers = list() - - possible_employers.Add(GLOB.syndicate_employers, GLOB.nanotrasen_employers) - - if(istype(ending_objective, /datum/objective/hijack)) - possible_employers -= GLOB.normal_employers - else //escape or martyrdom - possible_employers -= GLOB.hijack_employers - - switch(faction) - if(FLAVOR_FACTION_SYNDICATE) - possible_employers -= GLOB.nanotrasen_employers - if(FLAVOR_FACTION_NANOTRASEN) - possible_employers -= GLOB.syndicate_employers - employer = pick(possible_employers) + if(!employer) + var/faction = prob(75) ? FLAVOR_FACTION_SYNDICATE : FLAVOR_FACTION_NANOTRASEN + var/list/possible_employers = list() + + possible_employers.Add(GLOB.syndicate_employers, GLOB.nanotrasen_employers) + + if(istype(ending_objective, /datum/objective/hijack)) + possible_employers -= GLOB.normal_employers + else //escape or martyrdom + possible_employers -= GLOB.hijack_employers + + switch(faction) + if(FLAVOR_FACTION_SYNDICATE) + possible_employers -= GLOB.nanotrasen_employers + if(FLAVOR_FACTION_NANOTRASEN) + possible_employers -= GLOB.syndicate_employers + employer = pick(possible_employers) traitor_flavor = strings(TRAITOR_FLAVOR_FILE, employer) /// Generates a complete set of traitor objectives up to the traitor objective limit, including non-generic objectives such as martyr and hijack. @@ -145,11 +132,14 @@ objective_count++ var/objective_limit = CONFIG_GET(number/traitor_objectives_amount) - + var/datum/objective/job_objective = forge_job_objective() // for(in...to) loops iterate inclusively, so to reach objective_limit we need to loop to objective_limit - 1 // This does not give them 1 fewer objectives than intended. for(var/i in objective_count to objective_limit - 1) - objectives += forge_single_generic_objective() + var/generated = forge_single_generic_objective(job_objective) + if(generated == job_objective) + job_objective = null + objectives += generated /** * ## forge_ending_objective @@ -179,9 +169,12 @@ ending_objective.owner = owner objectives += ending_objective -/datum/antagonist/traitor/proc/forge_single_generic_objective() +/datum/antagonist/traitor/proc/forge_single_generic_objective(job_objective) + if(prob(JOB_PROB) && job_objective) + return job_objective + if(prob(KILL_PROB)) - var/list/active_ais = active_ais() + var/list/active_ais = active_ais(skip_syndicate = TRUE) if(active_ais.len && prob(DESTROY_AI_PROB(GLOB.joined_player_list.len))) var/datum/objective/destroy/destroy_objective = new() destroy_objective.owner = owner @@ -204,6 +197,11 @@ steal_objective.find_target() return steal_objective +/datum/antagonist/traitor/proc/forge_job_objective() + var/datum/objective/job_objective = owner.assigned_role.generate_traitor_objective() // can return null + job_objective?.owner = owner + return job_objective + /datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override) . = ..() var/mob/living/datum_owner = mob_override || owner.current @@ -214,13 +212,18 @@ datum_owner.AddComponent(/datum/component/codeword_hearing, GLOB.syndicate_code_response_regex, "red", src) /datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override) - . = ..() var/mob/living/datum_owner = mob_override || owner.current handle_clown_mutation(datum_owner, removing = FALSE) for(var/datum/component/codeword_hearing/component as anything in datum_owner.GetComponents(/datum/component/codeword_hearing)) component.delete_if_from_source(src) +/datum/antagonist/traitor/submit_player_objective(retain_existing, retain_escape, force) + . = ..() + if (!.) + return + owner.current.playsound_local(get_turf(owner.current), 'sound/music/antag/traitor/final_objective.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) + /datum/antagonist/traitor/ui_static_data(mob/user) var/datum/component/uplink/uplink = uplink_ref?.resolve() var/list/data = list() @@ -235,6 +238,7 @@ data["allies"] = traitor_flavor["allies"] data["goal"] = traitor_flavor["goal"] data["has_uplink"] = uplink ? TRUE : FALSE + data["given_uplink"] = give_uplink if(uplink) data["uplink_intro"] = traitor_flavor["uplink"] data["uplink_unlock_info"] = uplink.unlock_text @@ -274,16 +278,14 @@ if(uplink_owned) var/uplink_text = "(used [used_telecrystals] TC) [purchases]" if((used_telecrystals == 0) && traitor_won) - var/static/icon/badass = icon('icons/ui_icons/antags/badass.dmi', "badass") + var/static/icon/badass = icon('icons/ui/antags/badass.dmi', "badass") uplink_text += "[icon2html(badass, world)]" result += uplink_text result += objectives_text - if(uplink_handler) - if (uplink_handler.contractor_hub) - result += contractor_round_end() - result += "
The traitor had a total of [DISPLAY_PROGRESSION(uplink_handler.progression_points)] Reputation and [uplink_handler.telecrystals] Unused Telecrystals." + if(uplink_handler && uplink_handler.contractor_hub) + result += contractor_round_end() var/special_role_text = LOWER_TEXT(name) @@ -291,7 +293,7 @@ result += span_greentext("The [special_role_text] was successful!") else result += span_redtext("The [special_role_text] has failed!") - SEND_SOUND(owner.current, 'sound/ambience/ambifailure.ogg') + SEND_SOUND(owner.current, 'sound/ambience/misc/ambifailure.ogg') return result.Join("
") @@ -333,7 +335,7 @@ r_hand = /obj/item/gun/energy/recharge/ebow shoes = /obj/item/clothing/shoes/magboots/advance -/datum/outfit/traitor/post_equip(mob/living/carbon/human/H, visualsOnly) +/datum/outfit/traitor/post_equip(mob/living/carbon/human/H, visuals_only) var/obj/item/melee/energy/sword/sword = locate() in H.held_items if(sword.flags_1 & INITIALIZED_1) sword.attack_self() @@ -346,3 +348,8 @@ #undef FLAVOR_FACTION_SYNDICATE #undef FLAVOR_FACTION_NANOTRASEN + +/datum/antagonist/traitor/on_respawn(mob/new_character) + SSjob.equip_rank(new_character, new_character.mind.assigned_role, new_character.client) + new_character.mind.give_uplink(silent = TRUE, antag_datum = src) + return TRUE diff --git a/code/modules/antagonists/valentines/valentine.dm b/code/modules/antagonists/valentines/valentine.dm index 086c50827f5e..d528dae144b9 100644 --- a/code/modules/antagonists/valentines/valentine.dm +++ b/code/modules/antagonists/valentines/valentine.dm @@ -2,12 +2,10 @@ name = "\improper Valentine" roundend_category = "valentines" //there's going to be a ton of them so put them in separate category show_in_antagpanel = FALSE - prevent_roundtype_conversion = FALSE suicide_cry = "FOR MY LOVE!!" ui_name = null // Not 'true' antags, this disables certain interactions that assume the owner is a baddie - antag_flags = FLAG_FAKE_ANTAG - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST /// Reference to our date's mind VAR_FINAL/datum/mind/date diff --git a/code/modules/antagonists/wishgranter/wishgranter.dm b/code/modules/antagonists/wishgranter/wishgranter.dm index fd18ffe5c1ed..6ecb9500854e 100644 --- a/code/modules/antagonists/wishgranter/wishgranter.dm +++ b/code/modules/antagonists/wishgranter/wishgranter.dm @@ -11,7 +11,6 @@ objectives += hijack /datum/antagonist/wishgranter/on_gain() - owner.special_role = "Avatar of the Wish Granter" forge_objectives() . = ..() give_powers() diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm index fe5f69fd9fa5..4d1ceb2790f2 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm @@ -20,11 +20,8 @@ There is a good chance that they will shoot each other first." /datum/spellbook_entry/summon/guns/can_be_purchased() - // Summon Guns requires 98 threat. - if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE - // Also must be config enabled - return !CONFIG_GET(flag/no_summon_guns) + // Must be a high chaos round + Also must be config enabled + return SSdynamic.current_tier.tier == DYNAMIC_TIER_HIGH && !CONFIG_GET(flag/no_summon_guns) /datum/spellbook_entry/summon/guns/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book, log_buy = TRUE) summon_guns(user, 10) @@ -37,11 +34,8 @@ why they aren't to be trusted with it at the same time." /datum/spellbook_entry/summon/magic/can_be_purchased() - // Summon Magic requires 98 threat. - if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE - // Also must be config enabled - return !CONFIG_GET(flag/no_summon_magic) + // Must be a high chaos round + Also must be config enabled + return SSdynamic.current_tier.tier == DYNAMIC_TIER_HIGH && !CONFIG_GET(flag/no_summon_magic) /datum/spellbook_entry/summon/magic/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book, log_buy = TRUE) summon_magic(user, 10) @@ -57,11 +51,8 @@ limit = 5 // Each purchase can intensify it. /datum/spellbook_entry/summon/events/can_be_purchased() - // Summon Events requires 98 threat. - if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE - // Also, must be config enabled - return !CONFIG_GET(flag/no_summon_events) + // Must be a high chaos round + Also must be config enabled + return SSdynamic.current_tier.tier == DYNAMIC_TIER_HIGH && !CONFIG_GET(flag/no_summon_events) /datum/spellbook_entry/summon/events/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book, log_buy = TRUE) summon_events(user) @@ -135,7 +126,7 @@ return ..() /datum/spellbook_entry/summon/specific_spell/can_be_purchased() - if(SSdynamic.threat_level < MINIMUM_THREAT_FOR_RITUALS) + if(SSdynamic.current_tier.tier != DYNAMIC_TIER_HIGH) return FALSE if(GLOB.mass_teaching) return FALSE diff --git a/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm b/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm index f13b53b12edd..79fb2a1568f2 100644 --- a/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm +++ b/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm @@ -66,7 +66,7 @@ return if(user.mind != owner) - if(user.mind?.special_role == ROLE_WIZARD_APPRENTICE) + if(IS_WIZARD_APPRENTICE(user)) to_chat(user, span_warning("If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not.")) else to_chat(user, span_warning("[src] does not recognize you as its owner and refuses to open!")) diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm index a951a5daf422..1633bb50a876 100644 --- a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm +++ b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm @@ -49,9 +49,9 @@ var/obj/energy_ball/tesla = new (current_location) tesla.energy = 200 if (DOOM_METEORS) - var/datum/dynamic_ruleset/roundstart/meteor/meteors = new() - meteors.meteordelay = 0 - SSdynamic.execute_roundstart_rule(meteors) // Meteors will continue until morale is crushed. + GLOB.meteor_mode ||= new() + GLOB.meteor_mode.meteordelay = 0 + GLOB.meteor_mode.start_meteor() priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", ANNOUNCER_METEORS) #undef DOOM_SINGULARITY diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm b/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm index 1ceb21b72eb7..a1bf16ff790e 100644 --- a/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm +++ b/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm @@ -73,7 +73,7 @@ show_in_antagpanel = FALSE antagpanel_category = ANTAG_GROUP_CREW show_name_in_check_antagonists = TRUE - count_against_dynamic_roll_chance = FALSE + antag_flags = ANTAG_FAKE|ANTAG_SKIP_GLOBAL_LIST silent = TRUE /// Give everyone magic items, its so simple it feels pointless to give it its own file diff --git a/code/modules/antagonists/wizard/slaughter_antag.dm b/code/modules/antagonists/wizard/slaughter_antag.dm index c9aea4478c39..71d4c4669a01 100644 --- a/code/modules/antagonists/wizard/slaughter_antag.dm +++ b/code/modules/antagonists/wizard/slaughter_antag.dm @@ -2,7 +2,7 @@ name = "\improper Slaughter Demon" show_name_in_check_antagonists = TRUE ui_name = "AntagInfoDemon" - job_rank = ROLE_ALIEN + pref_flag = ROLE_ALIEN show_in_antagpanel = FALSE show_to_ghosts = TRUE antagpanel_category = ANTAG_GROUP_WIZARDS diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm index 2466f58947a8..cbbf82e9c8ae 100644 --- a/code/modules/antagonists/wizard/wizard.dm +++ b/code/modules/antagonists/wizard/wizard.dm @@ -5,7 +5,7 @@ GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key) name = "\improper Space Wizard" roundend_category = "wizards/witches" antagpanel_category = ANTAG_GROUP_WIZARDS - job_rank = ROLE_WIZARD + pref_flag = ROLE_WIZARD antag_hud_name = "wizard" antag_moodlet = /datum/mood_event/focused hijack_speed = 0.5 @@ -449,3 +449,8 @@ GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key) parts += printplayerlist(members - master_wizard.owner) return "
[parts.Join("
")]
" + +/datum/antagonist/wizard/on_respawn(mob/new_character) + new_character.forceMove(pick(GLOB.wizardstart)) + equip_wizard() + return TRUE diff --git a/code/modules/antagonists/xeno/xeno.dm b/code/modules/antagonists/xeno/xeno.dm index eaebc61cd477..3dcbe0918ff8 100644 --- a/code/modules/antagonists/xeno/xeno.dm +++ b/code/modules/antagonists/xeno/xeno.dm @@ -17,10 +17,9 @@ /datum/antagonist/xeno name = "\improper Xenomorph" - job_rank = ROLE_ALIEN + pref_flag = ROLE_ALIEN show_in_antagpanel = FALSE antagpanel_category = ANTAG_GROUP_XENOS - prevent_roundtype_conversion = FALSE show_to_ghosts = TRUE var/datum/team/xeno/xeno_team @@ -87,7 +86,7 @@ explanation_text = "Escape from captivity." /datum/objective/escape_captivity/check_completion() - if(!istype(get_area(owner), SScommunications.captivity_area)) + if(!istype(get_area(owner), GLOB.communications_controller.captivity_area)) return TRUE /datum/objective/advance_hive @@ -146,7 +145,7 @@ if(!captive_alien || captive_alien.stat == DEAD) return CAPTIVE_XENO_DEAD - if(istype(get_area(captive_alien), SScommunications.captivity_area)) + if(istype(get_area(captive_alien), GLOB.communications_controller.captivity_area)) return CAPTIVE_XENO_FAIL return CAPTIVE_XENO_PASS @@ -155,13 +154,12 @@ /mob/living/carbon/alien/mind_initialize() ..() if(!mind.has_antag_datum(/datum/antagonist/xeno)) - if(SScommunications.xenomorph_egg_delivered && istype(get_area(src), SScommunications.captivity_area)) + if(GLOB.communications_controller.xenomorph_egg_delivered && istype(get_area(src), GLOB.communications_controller.captivity_area)) mind.add_antag_datum(/datum/antagonist/xeno/captive) else mind.add_antag_datum(/datum/antagonist/xeno) - mind.set_assigned_role(SSjob.GetJobType(/datum/job/xenomorph)) - mind.special_role = ROLE_ALIEN + mind.set_assigned_role(SSjob.get_job_type(/datum/job/xenomorph)) /mob/living/carbon/alien/on_wabbajacked(mob/living/new_mob) . = ..() @@ -170,8 +168,7 @@ if(isalien(new_mob)) return mind.remove_antag_datum(/datum/antagonist/xeno) - mind.set_assigned_role(SSjob.GetJobType(/datum/job/unassigned)) - mind.special_role = null + mind.set_assigned_role(SSjob.get_job_type(/datum/job/unassigned)) #undef CAPTIVE_XENO_DEAD #undef CAPTIVE_XENO_FAIL diff --git a/code/modules/bitrunning/antagonists/_parent.dm b/code/modules/bitrunning/antagonists/_parent.dm index 8bd061d72a1d..5a00026f9965 100644 --- a/code/modules/bitrunning/antagonists/_parent.dm +++ b/code/modules/bitrunning/antagonists/_parent.dm @@ -4,7 +4,7 @@ /datum/antagonist/bitrunning_glitch name = "Generic Bitrunning Glitch" antagpanel_category = ANTAG_GROUP_GLITCH - job_rank = ROLE_GLITCH + pref_flag = ROLE_GLITCH preview_outfit = /datum/outfit/cyber_police show_in_roundend = FALSE show_in_antagpanel = FALSE diff --git a/code/modules/bitrunning/antagonists/ghost_role.dm b/code/modules/bitrunning/antagonists/ghost_role.dm index 3bf88e16dfb2..2114382d552a 100644 --- a/code/modules/bitrunning/antagonists/ghost_role.dm +++ b/code/modules/bitrunning/antagonists/ghost_role.dm @@ -1,7 +1,7 @@ /datum/antagonist/domain_ghost_actor name = "Virtual Domain Actor" antagpanel_category = ANTAG_GROUP_GLITCH - job_rank = ROLE_GLITCH + pref_flag = ROLE_GLITCH show_to_ghosts = TRUE suicide_cry = "FATAL ERROR" ui_name = "AntagInfoGlitch" diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm index 801f51f5b7c9..1891a4689974 100644 --- a/code/modules/bitrunning/components/avatar_connection.dm +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -69,7 +69,7 @@ avatar.fully_replace_character_name(newname = alias) update_avatar_id() - avatar.mind.set_assigned_role(SSjob.GetJobType(/datum/job/bit_avatar)) + avatar.mind.set_assigned_role(SSjob.get_job_type(/datum/job/bit_avatar)) for(var/skill_type in old_mind.known_skills) avatar.mind.set_experience(skill_type, old_mind.get_skill_exp(skill_type), silent = TRUE) diff --git a/code/modules/bitrunning/event.dm b/code/modules/bitrunning/event.dm index 370957e2ebb0..c50ef016a515 100644 --- a/code/modules/bitrunning/event.dm +++ b/code/modules/bitrunning/event.dm @@ -6,7 +6,6 @@ ) category = EVENT_CATEGORY_INVASION description = "Causes a short term antagonist to spawn in the virtual domain." - dynamic_should_hijack = FALSE min_players = 1 max_occurrences = 0 typepath = /datum/round_event/ghost_role/bitrunning_glitch diff --git a/code/modules/bitrunning/server/threats.dm b/code/modules/bitrunning/server/threats.dm index d912e972289a..4c44355412bf 100644 --- a/code/modules/bitrunning/server/threats.dm +++ b/code/modules/bitrunning/server/threats.dm @@ -111,8 +111,7 @@ var/datum/mind/antag_mind = new_mob.mind antag_mind.add_antag_datum(chosen_role) - antag_mind.special_role = ROLE_GLITCH - antag_mind.set_assigned_role(SSjob.GetJobType(/datum/job/bitrunning_glitch)) + antag_mind.set_assigned_role(SSjob.get_job_type(/datum/job/bitrunning_glitch)) playsound(new_mob, 'sound/magic/ethereal_exit.ogg', 50, vary = TRUE) message_admins("[ADMIN_LOOKUPFLW(new_mob)] has been made into virtual antagonist by an event.") diff --git a/code/modules/bitrunning/spawners.dm b/code/modules/bitrunning/spawners.dm index 81d039fad137..0ad7e454e6f2 100644 --- a/code/modules/bitrunning/spawners.dm +++ b/code/modules/bitrunning/spawners.dm @@ -18,7 +18,7 @@ ..() spawned_mob.mind.add_antag_datum(/datum/antagonist/domain_ghost_actor) - spawned_mob.mind.set_assigned_role(SSjob.GetJobType(/datum/job/bitrunning_glitch)) + spawned_mob.mind.set_assigned_role(SSjob.get_job_type(/datum/job/bitrunning_glitch)) /obj/effect/mob_spawn/ghost_role/human/virtual_domain/pirate name = "Virtual Pirate Remains" diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 2cbd9f892b06..6e9990b418a0 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -174,7 +174,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) data["character_profiles"] = create_character_profiles() data["character_preview_view"] = character_preview_view.assigned_map - data["overflow_role"] = SSjob.GetJobType(SSjob.overflow_role).title + data["overflow_role"] = SSjob.get_job_type(SSjob.overflow_role).title data["window"] = current_window data["content_unlocked"] = unlock_content diff --git a/code/modules/client/preferences/middleware/antags.dm b/code/modules/client/preferences/middleware/antags.dm index 5b107d2c0d0b..2e69288adf74 100644 --- a/code/modules/client/preferences/middleware/antags.dm +++ b/code/modules/client/preferences/middleware/antags.dm @@ -1,3 +1,11 @@ +/// Antagonists that don't have a dynamic ruleset, but do have a preference +GLOBAL_LIST_INIT(non_ruleset_antagonists, list( + ROLE_GLITCH = /datum/antagonist/bitrunning_glitch, + ROLE_FUGITIVE = /datum/antagonist/fugitive, + ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone, + ROLE_SENTIENCE = /datum/antagonist/sentient_creature, +)) + /datum/preference_middleware/antags action_delegations = list( "set_antags" = PROC_REF(set_antags), @@ -59,14 +67,9 @@ /datum/preference_middleware/antags/proc/get_antag_bans() var/list/antag_bans = list() - for (var/datum/dynamic_ruleset/dynamic_ruleset as anything in subtypesof(/datum/dynamic_ruleset)) - var/antag_flag = initial(dynamic_ruleset.antag_flag) - var/antag_flag_override = initial(dynamic_ruleset.antag_flag_override) - - if (isnull(antag_flag)) - continue - - if (is_banned_from(preferences.parent.ckey, list(antag_flag_override || antag_flag, ROLE_SYNDICATE))) + var/is_banned_from_all = is_banned_from(preferences.parent.ckey, ROLE_SYNDICATE) + for (var/antag_flag in get_all_antag_flags()) + if (is_banned_from_all || is_banned_from(preferences.parent.ckey, antag_flag)) antag_bans += serialize_antag_name(antag_flag) return antag_bans @@ -76,18 +79,8 @@ return var/list/antag_days_left = list() - - for (var/datum/dynamic_ruleset/dynamic_ruleset as anything in subtypesof(/datum/dynamic_ruleset)) - var/antag_flag = initial(dynamic_ruleset.antag_flag) - var/antag_flag_override = initial(dynamic_ruleset.antag_flag_override) - - if (isnull(antag_flag)) - continue - - var/days_needed = preferences.parent?.get_remaining_days( - GLOB.special_roles[antag_flag_override || antag_flag] - ) - + for (var/antag_flag in get_all_antag_flags()) + var/days_needed = preferences.parent?.get_days_to_play_antag(antag_flag) || 0 if (days_needed > 0) antag_days_left[serialize_antag_name(antag_flag)] = days_needed @@ -99,11 +92,50 @@ if (isnull(serialized_antags)) serialized_antags = list() - for (var/special_role in GLOB.special_roles) + for (var/special_role in get_all_antag_flags()) serialized_antags[serialize_antag_name(special_role)] = special_role return serialized_antags +/** + * Returns a list of all antag flags that are available to the player + * + * So this includes stuff like traitor, wizard, fugitive, but does not include wizard apprentice or hypnotized + */ +/proc/get_all_antag_flags() as /list + var/static/list/antag_flags + if(antag_flags) + return antag_flags + + var/list/ruleset_antags = list() + for(var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset)) + var/antag_flag = initial(ruleset.pref_flag) + var/jobban_flag = initial(ruleset.jobban_flag) + + if(antag_flag) + ruleset_antags |= antag_flag + if(jobban_flag) + ruleset_antags |= jobban_flag + + antag_flags = ruleset_antags | GLOB.non_ruleset_antagonists + return antag_flags + +/** + * Returns the number of days more the client's account must be to play the passed in antag + */ +/client/proc/get_days_to_play_antag(checked_antag_flag) + var/static/list/antag_time_limits + if(!antag_time_limits) + antag_time_limits = list() + for(var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset)) + var/antag_flag = initial(ruleset.pref_flag) + var/config_min_days = SSdynamic.dynamic_config[initial(ruleset.config_tag)]?[NAMEOF(ruleset, minimum_required_age)] + var/min_days = isnull(config_min_days) ? initial(ruleset.minimum_required_age) : config_min_days + + antag_time_limits[antag_flag] = min_days + + return get_remaining_days(antag_time_limits[checked_antag_flag] || 0) + /// Sprites generated for the antagonists panel /datum/asset/spritesheet/antagonists name = "antagonists" @@ -113,23 +145,16 @@ var/list/antag_icons = list() /datum/asset/spritesheet/antagonists/create_spritesheets() - // Antagonists that don't have a dynamic ruleset, but do have a preference - var/static/list/non_ruleset_antagonists = list( - ROLE_GLITCH = /datum/antagonist/bitrunning_glitch, - ROLE_FUGITIVE = /datum/antagonist/fugitive, - ROLE_LONE_OPERATIVE = /datum/antagonist/nukeop/lone, - ROLE_SENTIENCE = /datum/antagonist/sentient_creature, - ) - - var/list/antagonists = non_ruleset_antagonists.Copy() + var/list/antagonists = GLOB.non_ruleset_antagonists.Copy() for (var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset)) - var/datum/antagonist/antagonist_type = initial(ruleset.antag_datum) - if (isnull(antagonist_type)) + var/datum/antagonist/antagonist_type = initial(ruleset.preview_antag_datum) + var/antag_flag = initial(ruleset.pref_flag) + if (isnull(antagonist_type) || isnull(antag_flag)) continue // antag_flag is guaranteed to be unique by unit tests. - antagonists[initial(ruleset.antag_flag)] = antagonist_type + antagonists[initial(ruleset.pref_flag)] = antagonist_type var/list/generated_icons = list() diff --git a/code/modules/client/preferences/middleware/jobs.dm b/code/modules/client/preferences/middleware/jobs.dm index 0471367d2899..a0c08e9382d6 100644 --- a/code/modules/client/preferences/middleware/jobs.dm +++ b/code/modules/client/preferences/middleware/jobs.dm @@ -11,7 +11,7 @@ if (level != null && level != JP_LOW && level != JP_MEDIUM && level != JP_HIGH) return FALSE - var/datum/job/job = SSjob.GetJob(job_title) + var/datum/job/job = SSjob.get_job(job_title) if (isnull(job)) return FALSE @@ -36,7 +36,7 @@ if(new_title == base_title) new_title = null // clearing, essentially - var/datum/job/job = SSjob.GetJob(base_title) + var/datum/job/job = SSjob.get_job(base_title) if(isnull(job)) return FALSE if(new_title && !(new_title in job.get_titles(TRUE))) @@ -166,7 +166,7 @@ /datum/preference/job_titles/deserialize(input, datum/preferences/preferences) var/list/input_sanitized = list() for(var/job_title in input) - var/datum/job/job = SSjob.GetJob(job_title) + var/datum/job/job = SSjob.get_job(job_title) if(isnull(job)) continue if(!(input[job_title] in job.get_titles(TRUE))) diff --git a/code/modules/client/preferences/species_features/vampire.dm b/code/modules/client/preferences/species_features/vampire.dm index bd24acb3ab15..cc54fb3391f5 100644 --- a/code/modules/client/preferences/species_features/vampire.dm +++ b/code/modules/client/preferences/species_features/vampire.dm @@ -32,7 +32,7 @@ GLOBAL_LIST_EMPTY(vampire_houses) //find and setup the house (department) this vampire is joining var/datum/job_department/vampire_house - var/datum/job/vampire_job = SSjob.GetJob(target.job) + var/datum/job/vampire_job = SSjob.get_job(target.job) if(!vampire_job) //no job or no mind LOSERS return var/list/valid_departments = (SSjob.joinable_departments.Copy()) - list(/datum/job_department/silicon, /datum/job_department/undefined) diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 0ac47d29c92d..46d0a8644e31 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -460,7 +460,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car var/list/output = list() for (var/role in input_be_special) - if (role in GLOB.special_roles) + if (role in get_all_antag_flags()) output += role return output.len == input_be_special.len ? input_be_special : output diff --git a/code/modules/client/verbs/who.dm b/code/modules/client/verbs/who.dm index 5b31ae49849c..796475fddb49 100644 --- a/code/modules/client/verbs/who.dm +++ b/code/modules/client/verbs/who.dm @@ -36,7 +36,7 @@ entry += " - DEAD" else entry += " - DEAD" - if(is_special_character(client.mob)) + if(client.mob.is_antag()) entry += " - Antagonist" entry += " [ADMIN_QUE(client.mob)]" entry += " ([round(client.avgping, 1)]ms)" diff --git a/code/modules/clothing/chameleon/_chameleon_action.dm b/code/modules/clothing/chameleon/_chameleon_action.dm index b822f7feabb2..4befe80fbe82 100644 --- a/code/modules/clothing/chameleon/_chameleon_action.dm +++ b/code/modules/clothing/chameleon/_chameleon_action.dm @@ -207,7 +207,7 @@ if(istype(applying_from, /datum/outfit/job)) var/datum/outfit/job/job_outfit = applying_from - var/datum/job/job_datum = SSjob.GetJobType(job_outfit.jobtype) + var/datum/job/job_datum = SSjob.get_job_type(job_outfit.jobtype) apply_job_data(job_datum) update_look(using_item_type) diff --git a/code/modules/clothing/outfits/event.dm b/code/modules/clothing/outfits/event.dm index fa07b3297b28..43857f5f3cb3 100644 --- a/code/modules/clothing/outfits/event.dm +++ b/code/modules/clothing/outfits/event.dm @@ -18,8 +18,7 @@ if(visualsOnly) return user.fully_replace_character_name(user.real_name, "Santa Claus") - user.mind.set_assigned_role(SSjob.GetJobType(/datum/job/santa)) - user.mind.special_role = ROLE_SANTA + user.mind.set_assigned_role(SSjob.get_job_type(/datum/job/santa)) user.hairstyle = "Long Hair 3" user.facial_hairstyle = "Beard (Full)" diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index e1ff22c7a127..0b1f0d319e83 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -32,9 +32,6 @@ var/triggering //admin cancellation - /// Whether or not dynamic should hijack this event - var/dynamic_should_hijack = FALSE - /// Datum that will handle admin options for forcing the event. /// If there are no options, just leave it as an empty list. var/list/datum/event_admin_setup/admin_setup = list() @@ -84,9 +81,6 @@ if(ispath(typepath, /datum/round_event/ghost_role) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT)) return FALSE - if (dynamic_should_hijack && SSdynamic.random_event_hijacked != HIJACKED_NOTHING) - return FALSE - return TRUE /datum/round_event_control/proc/preRunEvent() diff --git a/code/modules/events/creep_awakening.dm b/code/modules/events/creep_awakening.dm deleted file mode 100644 index 0dfa87ddfa33..000000000000 --- a/code/modules/events/creep_awakening.dm +++ /dev/null @@ -1,26 +0,0 @@ -/datum/round_event_control/obsessed - name = "Obsession Awakening" - typepath = /datum/round_event/obsessed - max_occurrences = 1 - min_players = 20 - category = EVENT_CATEGORY_HEALTH - description = "A random crewmember becomes obsessed with another." - -/datum/round_event/obsessed - fakeable = FALSE - -/datum/round_event/obsessed/start() - for(var/mob/living/carbon/human/H in shuffle(GLOB.player_list)) - if(!H.client || !(ROLE_OBSESSED in H.client.prefs.be_special)) - continue - if(H.stat == DEAD) - continue - if(!(H.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) //only station jobs sans nonhuman roles, prevents ashwalkers trying to stalk with crewmembers they never met - continue - if(H.mind.has_antag_datum(/datum/antagonist/obsessed)) - continue - if(!H.get_organ_by_type(/obj/item/organ/brain)) - continue - H.gain_trauma(/datum/brain_trauma/special/obsessed) - announce_to_ghosts(H) - break diff --git a/code/modules/events/dynamic_tweak.dm b/code/modules/events/dynamic_tweak.dm new file mode 100644 index 000000000000..3c5899df0185 --- /dev/null +++ b/code/modules/events/dynamic_tweak.dm @@ -0,0 +1,35 @@ +// Simple hidden event that adds a few more latejoins and midrounds to the round +// Keeps Greenshifts on their toes and prevents metagaming +/datum/round_event_control/dynamic_tweak + name = "Dynamic Tweak" + typepath = /datum/round_event/dynamic_tweak + weight = 10 + max_occurrences = 1 + earliest_start = 20 MINUTES + alert_observers = FALSE + category = EVENT_CATEGORY_INVASION + description = "Allows Dynamic to spawn another midround or latejoin. Gives some spice to Greenshifts." + +/datum/round_event_control/dynamic_tweak/New() + . = ..() + if(max_occurrences > 0) + max_occurrences += rand(-1, 1) + +/datum/round_event_control/dynamic_tweak/can_spawn_event(players_amt, allow_magic) + return ..() && SSdynamic.antag_events_enabled && !EMERGENCY_PAST_POINT_OF_NO_RETURN + +/datum/round_event/dynamic_tweak + start_when = 1 + end_when = 2 + fakeable = FALSE + +/datum/round_event/dynamic_tweak/start() + var/new_lights = rand(0, 1) + var/new_heavies = rand(1 - new_lights, 1) // guarantee a heavy if no new light + var/new_latejoins = rand(1 - new_heavies, 1) // guarantee a latejoin if no new heavy + + SSdynamic.rulesets_to_spawn[LIGHT_MIDROUND] += new_lights + SSdynamic.rulesets_to_spawn[HEAVY_MIDROUND] += new_heavies + SSdynamic.rulesets_to_spawn[LATEJOIN] += new_latejoins + + message_admins("Event: Dynamic Tweak added [new_latejoins] latejoin\s, [new_lights] light midround\s and [new_heavies] heavy midround\s.") diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm index 6e5cfdc61a1f..f862c0be1022 100644 --- a/code/modules/events/false_alarm.dm +++ b/code/modules/events/false_alarm.dm @@ -12,7 +12,7 @@ if(!.) return . - if(!length(gather_false_events())) + if(!length(get_potential_false_alarm())) return FALSE return TRUE @@ -26,44 +26,65 @@ /datum/round_event/falsealarm/announce(fake) if(fake) //What are you doing return - var/players_amt = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1) - - var/events_list = gather_false_events(players_amt) - var/datum/round_event_control/event_control - if(forced_type) - event_control = forced_type - else - event_control = pick(events_list) - if(event_control) - var/datum/round_event/Event = new event_control.typepath() - message_admins("False Alarm: [Event]") - Event.kill() //do not process this event - no starts, no ticks, no ends - Event.announce(TRUE) //just announce it like it's happening - -/proc/gather_false_events(players_amt) + + var/players_amt = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE) + var/picked_trigger = forced_type + if(ispath(forced_type, /datum/dynamic_ruleset/midround)) + picked_trigger = new forced_type() + + var/list/event_pool = get_potential_false_alarm() + + while(length(event_pool) && isnull(picked_trigger)) + var/potential_trigger = pick_n_take(event_pool) + if(istype(potential_trigger, /datum/round_event_control)) + var/datum/round_event_control/event_control = potential_trigger + if(event_control.can_spawn_event(players_amt)) + picked_trigger = event_control + break + + else if(ispath(potential_trigger, /datum/dynamic_ruleset/midround)) + var/datum/dynamic_ruleset/midround/ruleset = new potential_trigger(SSdynamic.get_config()) + if(ruleset.can_be_selected(players_amt)) + picked_trigger = ruleset + break + qdel(ruleset) + + else + stack_trace("Unknown false alarm candidate type: [potential_trigger || "null"]") + + if(istype(picked_trigger, /datum/round_event_control)) + var/datum/round_event_control/event_control = picked_trigger + var/datum/round_event/fake_event = new event_control.typepath() + message_admins("False Alarm: [fake_event]") + fake_event.kill() //do not process this event - no starts, no ticks, no ends + fake_event.announce(TRUE) //just announce it like it's happening + + else if(istype(picked_trigger, /datum/dynamic_ruleset/midround)) + var/datum/dynamic_ruleset/midround/ruleset = picked_trigger + message_admins("False Alarm: [ruleset]") + ruleset.false_alarm() + qdel(ruleset) + +/proc/get_potential_false_alarm() . = list() - for(var/datum/round_event_control/E in SSevents.control) - if(istype(E, /datum/round_event_control/falsealarm)) + for(var/datum/round_event_control/controller as anything in SSevents.control) + if(istype(controller, /datum/round_event_control/falsealarm)) continue - if(!E.can_spawn_event(players_amt)) + var/datum/round_event/event = controller.typepath + if(!initial(event.fakeable)) continue + . += controller - var/datum/round_event/event = E.typepath - if(!initial(event.fakeable)) + for(var/datum/dynamic_ruleset/midround/midround as anything in subtypesof(/datum/dynamic_ruleset/midround)) + if(!initial(midround.false_alarm_able)) continue - . += E + . += midround /datum/event_admin_setup/listed_options/false_alarm normal_run_option = "Random Fake Event" /datum/event_admin_setup/listed_options/false_alarm/get_list() - var/list/possible_types = list() - for(var/datum/round_event_control/event_control in SSevents.control) - var/datum/round_event/event = event_control.typepath - if(!initial(event.fakeable)) - continue - possible_types += event_control - return possible_types + return get_potential_false_alarm() /datum/event_admin_setup/listed_options/false_alarm/apply_to_event(datum/round_event/falsealarm/event) event.forced_type = chosen diff --git a/code/modules/events/ghost_role/abductor.dm b/code/modules/events/ghost_role/abductor.dm deleted file mode 100644 index 65fe4a142f5a..000000000000 --- a/code/modules/events/ghost_role/abductor.dm +++ /dev/null @@ -1,38 +0,0 @@ -/datum/round_event_control/abductor - name = "Abductors" - typepath = /datum/round_event/ghost_role/abductor - weight = 10 - max_occurrences = 1 - min_players = 20 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_INVASION - description = "One or more abductor teams spawns, and they plan to experiment on the crew." - -/datum/round_event/ghost_role/abductor - minimum_required = 2 - role_name = "abductor team" - fakeable = FALSE //Nothing to fake here - -/datum/round_event/ghost_role/abductor/spawn_role() - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ABDUCTOR, role = ROLE_ABDUCTOR, pic_source = /obj/item/melee/baton/abductor, role_name_text = role_name) - - if(candidates.len < 2) - return NOT_ENOUGH_PLAYERS - - SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS) - var/mob/living/carbon/human/agent = make_body(pick_n_take(candidates)) - var/mob/living/carbon/human/scientist = make_body(pick_n_take(candidates)) - - var/datum/team/abductor_team/T = new - if(T.team_number > ABDUCTOR_MAX_TEAMS) - return MAP_ERROR - - scientist.log_message("has been selected as [T.name] abductor scientist.", LOG_GAME) - agent.log_message("has been selected as [T.name] abductor agent.", LOG_GAME) - - scientist.mind.add_antag_datum(/datum/antagonist/abductor/scientist, T) - agent.mind.add_antag_datum(/datum/antagonist/abductor/agent, T) - - spawned_mobs += list(agent, scientist) - - return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/alien_infestation.dm b/code/modules/events/ghost_role/alien_infestation.dm deleted file mode 100644 index f4078e52e658..000000000000 --- a/code/modules/events/ghost_role/alien_infestation.dm +++ /dev/null @@ -1,82 +0,0 @@ -/datum/round_event_control/alien_infestation - name = "Alien Infestation" - typepath = /datum/round_event/ghost_role/alien_infestation - weight = 5 - - min_players = 10 - - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_ENTITIES - description = "A xenomorph larva spawns on a random vent." - -/datum/round_event_control/alien_infestation/can_spawn_event(players_amt, allow_magic = FALSE) - . = ..() - if(!.) - return . - - for(var/mob/living/carbon/alien/A in GLOB.player_list) - if(A.stat != DEAD) - return FALSE - -/datum/round_event/ghost_role/alien_infestation - announce_when = 400 - - minimum_required = 1 - role_name = "alien larva" - - // 50% chance of being incremented by one - var/spawncount = 1 - fakeable = TRUE - - -/datum/round_event/ghost_role/alien_infestation/setup() - announce_when = rand(announce_when, announce_when + 50) - if(prob(50)) - spawncount++ - -/datum/round_event/ghost_role/alien_infestation/announce(fake) - var/living_aliens = FALSE - for(var/mob/living/carbon/alien/A in GLOB.player_list) - if(A.stat != DEAD) - living_aliens = TRUE - - if(living_aliens || fake) - priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", ANNOUNCER_ALIENS) - - -/datum/round_event/ghost_role/alien_infestation/spawn_role() - var/list/vents = list() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/atmospherics/components/unary/vent_pump)) - if(QDELETED(temp_vent)) - continue - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] - if(!temp_vent_parent) - continue//no parent vent - //Stops Aliens getting stuck in small networks. - //See: Security, Virology - if(temp_vent_parent.other_atmos_machines.len > 20) - vents += temp_vent - - if(!vents.len) - message_admins("An event attempted to spawn an alien but no suitable vents were found. Shutting down.") - return MAP_ERROR - - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, pic_source = /mob/living/carbon/alien/larva, role_name_text = role_name) - - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - - while(spawncount > 0 && vents.len && candidates.len) - var/obj/vent = pick_n_take(vents) - var/mob/dead/observer/selected = pick_n_take(candidates) - var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) - new_xeno.key = selected.key - new_xeno.move_into_vent(vent) - - spawncount-- - message_admins("[ADMIN_LOOKUPFLW(new_xeno)] has been made into an alien by an event.") - new_xeno.log_message("was spawned as an alien by an event.", LOG_GAME) - spawned_mobs += new_xeno - - return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/blob.dm b/code/modules/events/ghost_role/blob.dm deleted file mode 100644 index 70640512699d..000000000000 --- a/code/modules/events/ghost_role/blob.dm +++ /dev/null @@ -1,44 +0,0 @@ -/datum/round_event_control/blob - name = "Blob" - typepath = /datum/round_event/ghost_role/blob - weight = 10 - max_occurrences = 1 - - min_players = 20 - - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_ENTITIES - description = "Spawns a new blob overmind." - -/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 - return FALSE - - return ..() - -/datum/round_event/ghost_role/blob - announce_chance = 0 - role_name = "blob overmind" - fakeable = TRUE - -/datum/round_event/ghost_role/blob/announce(fake) - if(!fake) - return //the mob itself handles this. - priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", ANNOUNCER_OUTBREAK5) - -/datum/round_event/ghost_role/blob/spawn_role() - if(!GLOB.blobstart.len) - return MAP_ERROR - var/icon/blob_icon = icon('icons/mob/nonhuman-player/blob.dmi', icon_state = "blob_core") - blob_icon.Blend("#9ACD32", ICON_MULTIPLY) - blob_icon.Blend(icon('icons/mob/nonhuman-player/blob.dmi', "blob_core_overlay"), ICON_OVERLAY) - var/image/blob_image = image(blob_icon) - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_BLOB, role = ROLE_BLOB, pic_source = blob_image, role_name_text = role_name) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - var/mob/dead/observer/new_blob = pick(candidates) - var/mob/camera/blob/BC = new_blob.become_overmind() - spawned_mobs += BC - message_admins("[ADMIN_LOOKUPFLW(BC)] has been made into a blob overmind by an event.") - BC.log_message("was spawned as a blob overmind by an event.", LOG_GAME) - return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/changeling_event.dm b/code/modules/events/ghost_role/changeling_event.dm deleted file mode 100644 index 43b4ca48af57..000000000000 --- a/code/modules/events/ghost_role/changeling_event.dm +++ /dev/null @@ -1,32 +0,0 @@ -/* -* Changeling midround spawn event. Takes a ghost volunteer and stuffs them into a changeling with their own identity and a flesh space suit. -* They arrive via a meateor, which collides with the station. They are expected to find their own way into the station by whatever means necessary. -* The midround changeling experience is, by nature, more difficult than playing as a roundstart crew changeling. -* -*/ - -/datum/round_event_control/changeling - name = "Changeling Meteor" - typepath = /datum/round_event/ghost_role/changeling - weight = 8 - max_occurrences = 3 - min_players = 20 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_ENTITIES - description = "A meteor containing a changeling is summoned and thrown at the exterior of the station." - -/datum/round_event/ghost_role/changeling - minimum_required = 1 - role_name = "space changeling" - fakeable = FALSE - -/datum/round_event/ghost_role/changeling/spawn_role() - var/list/mob/dead/observer/candidate = SSpolling.poll_ghost_candidates(check_jobban = ROLE_CHANGELING, role = ROLE_CHANGELING_MIDROUND, pic_source = /obj/item/melee/arm_blade, role_name_text = role_name) - - if(!candidate.len) - return NOT_ENOUGH_PLAYERS - - spawned_mobs += generate_changeling_meteor(pick_n_take(candidate)) - - if(spawned_mobs) - return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/fugitive_event.dm b/code/modules/events/ghost_role/fugitive_event.dm deleted file mode 100644 index 1556eb045589..000000000000 --- a/code/modules/events/ghost_role/fugitive_event.dm +++ /dev/null @@ -1,151 +0,0 @@ -#define TEAM_BACKSTORY_SIZE 4 - -/datum/round_event_control/fugitives - name = "Spawn Fugitives" - typepath = /datum/round_event/ghost_role/fugitives - max_occurrences = 1 - min_players = 20 - earliest_start = 30 MINUTES //deadchat sink, lets not even consider it early on. - category = EVENT_CATEGORY_INVASION - description = "Fugitives will hide on the station, followed by hunters." - map_flags = EVENT_SPACE_ONLY - -/datum/round_event/ghost_role/fugitives - minimum_required = 1 - role_name = "fugitive" - fakeable = FALSE - -/datum/round_event/ghost_role/fugitives/spawn_role() - var/turf/landing_turf = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) - if(isnull(landing_turf)) - return MAP_ERROR - var/list/possible_backstories = list() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_FUGITIVE, role = ROLE_FUGITIVE, pic_source = /obj/item/card/id/advanced/prisoner) - - if(!length(candidates)) - return NOT_ENOUGH_PLAYERS - - if(length(candidates) < TEAM_BACKSTORY_SIZE || prob(30 - (length(candidates) * 2))) //Solo backstories are always considered if a larger backstory cannot be filled out. Otherwise, it's a rare chance that gets rarer if more people sign up. - possible_backstories += list(FUGITIVE_BACKSTORY_WALDO, FUGITIVE_BACKSTORY_INVISIBLE) //less common as it comes with magicks and is kind of immershun shattering - - if(length(candidates) >= TEAM_BACKSTORY_SIZE)//group refugees - possible_backstories += list(FUGITIVE_BACKSTORY_PRISONER, FUGITIVE_BACKSTORY_CULTIST, FUGITIVE_BACKSTORY_SYNTH) - - var/backstory = pick(possible_backstories) - var/member_size = 3 - var/leader - switch(backstory) - if(FUGITIVE_BACKSTORY_SYNTH) - leader = pick_n_take(candidates) - if(FUGITIVE_BACKSTORY_WALDO, FUGITIVE_BACKSTORY_INVISIBLE) - member_size = 0 //solo refugees have no leader so the member_size gets bumped to one a bit later - var/list/members = list() - var/list/spawned_mobs = list() - if(isnull(leader)) - member_size++ //if there is no leader role, then the would be leader is a normal member of the team. - - for(var/i in 1 to member_size) - members += pick_n_take(candidates) - - for(var/mob/dead/selected in members) - var/mob/living/carbon/human/S = gear_fugitive(selected, landing_turf, backstory) - spawned_mobs += S - if(!isnull(leader)) - gear_fugitive_leader(leader, landing_turf, backstory) - - //after spawning - playsound(src, 'sound/weapons/emitter.ogg', 50, TRUE) - new /obj/item/storage/toolbox/mechanical(landing_turf) //so they can actually escape maint - var/hunter_backstory = pick( - HUNTER_PACK_COPS, - HUNTER_PACK_RUSSIAN, - HUNTER_PACK_BOUNTY, - HUNTER_PACK_PSYKER, - ) - addtimer(CALLBACK(src, PROC_REF(spawn_hunters), hunter_backstory), 10 MINUTES) - role_name = "fugitive hunter" - return SUCCESSFUL_SPAWN - -/datum/round_event/ghost_role/fugitives/proc/gear_fugitive(mob/dead/selected, turf/landing_turf, backstory) //spawns normal fugitive - var/datum/mind/player_mind = new /datum/mind(selected.key) - player_mind.active = TRUE - var/mob/living/carbon/human/S = new(landing_turf) - player_mind.transfer_to(S) - player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/fugitive)) - player_mind.special_role = ROLE_FUGITIVE - player_mind.add_antag_datum(/datum/antagonist/fugitive) - var/datum/antagonist/fugitive/fugitiveantag = player_mind.has_antag_datum(/datum/antagonist/fugitive) - fugitiveantag.greet(backstory) - - switch(backstory) - if(FUGITIVE_BACKSTORY_PRISONER) - S.equipOutfit(/datum/outfit/prisoner) - if(FUGITIVE_BACKSTORY_CULTIST) - S.equipOutfit(/datum/outfit/yalp_cultist) - if(FUGITIVE_BACKSTORY_WALDO) - S.equipOutfit(/datum/outfit/waldo) - if(FUGITIVE_BACKSTORY_SYNTH) - S.equipOutfit(/datum/outfit/synthetic) - if(FUGITIVE_BACKSTORY_INVISIBLE) - S.equipOutfit(/datum/outfit/invisible_man) - message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Fugitive by an event.") - S.log_message("was spawned as a Fugitive by an event.", LOG_GAME) - spawned_mobs += S - return S - -///special spawn for one member. it can be used for a special mob or simply to give one normal member special items. -/datum/round_event/ghost_role/fugitives/proc/gear_fugitive_leader(mob/dead/leader, turf/landing_turf, backstory) - var/datum/mind/player_mind = new /datum/mind(leader.key) - player_mind.active = TRUE - //if you want to add a fugitive with a special leader in the future, make this switch with the backstory - var/mob/living/carbon/human/S = gear_fugitive(leader, landing_turf, backstory) - var/obj/item/choice_beacon/augments/A = new(landing_turf) - S.put_in_hands(A) - new /obj/item/autosurgeon(landing_turf) - -//security team gets called in after 10 minutes of prep to find the refugees -/datum/round_event/ghost_role/fugitives/proc/spawn_hunters(backstory) - var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a group of [backstory]?", check_jobban = ROLE_FUGITIVE_HUNTER, pic_source = /obj/machinery/sleeper, role_name_text = backstory) - shuffle_inplace(candidates) - - var/datum/map_template/shuttle/hunter/ship - switch(backstory) - if(HUNTER_PACK_COPS) - ship = new /datum/map_template/shuttle/hunter/space_cop - if(HUNTER_PACK_RUSSIAN) - ship = new /datum/map_template/shuttle/hunter/russian - if(HUNTER_PACK_BOUNTY) - ship = new /datum/map_template/shuttle/hunter/bounty - if(HUNTER_PACK_PSYKER) - ship = new /datum/map_template/shuttle/hunter/psyker - - var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width) - var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height) - var/z = SSmapping.empty_space.z_value - var/turf/placement_turf = locate(x,y,z) - if(!placement_turf) - CRASH("Fugitive Hunters (Created from fugitive event) found no turf to load in") - if(!ship.load(placement_turf)) - CRASH("Loading [backstory] ship failed!") - - for(var/turf/shuttle_turf in ship.get_affected_turfs(placement_turf)) - for(var/obj/effect/mob_spawn/ghost_role/human/fugitive/spawner in shuttle_turf) - if(length(candidates)) - var/mob/our_candidate = candidates[1] - var/mob/spawned_mob = spawner.create_from_ghost(our_candidate) - candidates -= our_candidate - notify_ghosts( - "[spawner.prompt_name] has awoken: [spawned_mob]!", - source = spawned_mob, - header = "Come look!", - ) - else - notify_ghosts( - "[spawner.prompt_name] spawner has been created!", - source = spawner, - header = "Spawn Here!", - ) - - priority_announce("Unidentified ship detected near the station.") - -#undef TEAM_BACKSTORY_SIZE diff --git a/code/modules/events/ghost_role/morph_event.dm b/code/modules/events/ghost_role/morph_event.dm deleted file mode 100644 index c9133863f8c0..000000000000 --- a/code/modules/events/ghost_role/morph_event.dm +++ /dev/null @@ -1,38 +0,0 @@ -/datum/round_event_control/morph - name = "Spawn Morph" - typepath = /datum/round_event/ghost_role/morph - weight = 0 - max_occurrences = 1 - category = EVENT_CATEGORY_ENTITIES - description = "Spawns a hungry shapeshifting blobby creature." - min_wizard_trigger_potency = 4 - max_wizard_trigger_potency = 7 - -/datum/round_event/ghost_role/morph - minimum_required = 1 - role_name = "morphling" - -/datum/round_event/ghost_role/morph/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, pic_source = /mob/living/basic/morph, role_name_text = "morph") - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick_n_take(candidates) - - var/datum/mind/player_mind = new /datum/mind(selected.key) - player_mind.active = TRUE - - var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) - if(isnull(spawn_loc)) - return MAP_ERROR - - var/mob/living/basic/morph/corpus_accipientis = new(spawn_loc) - player_mind.transfer_to(corpus_accipientis) - player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/morph)) - player_mind.special_role = ROLE_MORPH - player_mind.add_antag_datum(/datum/antagonist/morph) - SEND_SOUND(corpus_accipientis, sound('sound/magic/mutate.ogg')) - message_admins("[ADMIN_LOOKUPFLW(corpus_accipientis)] has been made into a morph by an event.") - corpus_accipientis.log_message("was spawned as a morph by an event.", LOG_GAME) - spawned_mobs += corpus_accipientis - return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/nightmare.dm b/code/modules/events/ghost_role/nightmare.dm deleted file mode 100644 index ffb206c476dd..000000000000 --- a/code/modules/events/ghost_role/nightmare.dm +++ /dev/null @@ -1,41 +0,0 @@ -/datum/round_event_control/nightmare - name = "Spawn Nightmare" - typepath = /datum/round_event/ghost_role/nightmare - max_occurrences = 1 - min_players = 20 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_ENTITIES - description = "Spawns a nightmare, aiming to darken the station." - min_wizard_trigger_potency = 6 - max_wizard_trigger_potency = 7 - -/datum/round_event/ghost_role/nightmare - minimum_required = 1 - role_name = "nightmare" - fakeable = FALSE - -/datum/round_event/ghost_role/nightmare/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_NIGHTMARE, role_name_text = role_name) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick(candidates) - - var/datum/mind/player_mind = new /datum/mind(selected.key) - player_mind.active = TRUE - - var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) - if(isnull(spawn_loc)) - return MAP_ERROR - - var/mob/living/carbon/human/S = new (spawn_loc) - player_mind.transfer_to(S) - player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/nightmare)) - player_mind.special_role = ROLE_NIGHTMARE - player_mind.add_antag_datum(/datum/antagonist/nightmare) - S.set_species(/datum/species/shadow/nightmare) - playsound(S, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Nightmare by an event.") - S.log_message("was spawned as a Nightmare by an event.", LOG_GAME) - spawned_mobs += S - return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/operative.dm b/code/modules/events/ghost_role/operative.dm index fcea52e3c023..db84410a1c67 100644 --- a/code/modules/events/ghost_role/operative.dm +++ b/code/modules/events/ghost_role/operative.dm @@ -6,6 +6,9 @@ category = EVENT_CATEGORY_INVASION description = "A single nuclear operative assaults the station." +/datum/round_event_control/operative/can_spawn_event(players_amt, allow_magic) + return ..() && SSdynamic.antag_events_enabled + /datum/round_event/ghost_role/operative minimum_required = 1 role_name = "lone operative" @@ -25,9 +28,8 @@ var/mob/living/carbon/human/operative = new(spawn_location) operative.randomize_human_appearance(~RANDOMIZE_SPECIES) operative.dna.update_dna_identity() - var/datum/mind/Mind = new /datum/mind(selected.key) - Mind.set_assigned_role(SSjob.GetJobType(/datum/job/lone_operative)) - Mind.special_role = ROLE_LONE_OPERATIVE + var/datum/mind/Mind = new /datum/mind(chosen_one.key) + Mind.set_assigned_role(SSjob.get_job_type(/datum/job/lone_operative)) Mind.active = TRUE Mind.transfer_to(operative) if(!operative.client?.prefs.read_preference(/datum/preference/toggle/nuke_ops_species)) diff --git a/code/modules/events/ghost_role/revenant_event.dm b/code/modules/events/ghost_role/revenant_event.dm deleted file mode 100644 index 6cdfc2c4c9e5..000000000000 --- a/code/modules/events/ghost_role/revenant_event.dm +++ /dev/null @@ -1,65 +0,0 @@ -#define REVENANT_SPAWN_THRESHOLD 20 - -/datum/round_event_control/revenant - name = "Spawn Revenant" // Did you mean 'griefghost'? - typepath = /datum/round_event/ghost_role/revenant - weight = 7 - max_occurrences = 1 - min_players = 5 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_ENTITIES - description = "Spawns an angry, soul sucking ghost." - min_wizard_trigger_potency = 4 - max_wizard_trigger_potency = 7 - - -/datum/round_event/ghost_role/revenant - var/ignore_mobcheck = FALSE - role_name = "revenant" - -/datum/round_event/ghost_role/revenant/New(my_processing = TRUE, new_ignore_mobcheck = FALSE) - ..() - ignore_mobcheck = new_ignore_mobcheck - -/datum/round_event/ghost_role/revenant/spawn_role() - if(!ignore_mobcheck) - var/deadMobs = 0 - for(var/mob/M in GLOB.dead_mob_list) - deadMobs++ - if(deadMobs < REVENANT_SPAWN_THRESHOLD) - message_admins("Event attempted to spawn a revenant, but there were only [deadMobs]/[REVENANT_SPAWN_THRESHOLD] dead mobs.") - return WAITING_FOR_SOMETHING - - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, pic_source = /mob/living/basic/revenant) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - - var/mob/dead/observer/selected = pick_n_take(candidates) - - var/list/spawn_locs = list() - - for(var/mob/living/L in GLOB.dead_mob_list) //look for any dead bodies - var/turf/T = get_turf(L) - if(T && is_station_level(T.z)) - spawn_locs += T - if(!spawn_locs.len || spawn_locs.len < 15) //look for any morgue trays, crematoriums, ect if there weren't alot of dead bodies on the station to pick from - for(var/obj/structure/bodycontainer/bc in GLOB.bodycontainers) - var/turf/T = get_turf(bc) - if(T && is_station_level(T.z)) - spawn_locs += T - if(!spawn_locs.len) //If we can't find any valid spawnpoints, try the carp spawns - spawn_locs += find_space_spawn() - if(!spawn_locs.len) //If we can't find either, just spawn the revenant at the player's location - spawn_locs += get_turf(selected) - if(!spawn_locs.len) //If we can't find THAT, then just give up and cry - return MAP_ERROR - - var/mob/living/basic/revenant/revvie = new(pick(spawn_locs)) - revvie.key = selected.key - message_admins("[ADMIN_LOOKUPFLW(revvie)] has been made into a revenant by an event.") - revvie.log_message("was spawned as a revenant by an event.", LOG_GAME) - spawned_mobs += revvie - qdel(selected) - return SUCCESSFUL_SPAWN - -#undef REVENANT_SPAWN_THRESHOLD diff --git a/code/modules/events/ghost_role/slaughter_event.dm b/code/modules/events/ghost_role/slaughter_event.dm deleted file mode 100644 index f4628344d2f5..000000000000 --- a/code/modules/events/ghost_role/slaughter_event.dm +++ /dev/null @@ -1,40 +0,0 @@ -/datum/round_event_control/slaughter - name = "Spawn Slaughter Demon" - typepath = /datum/round_event/ghost_role/slaughter - weight = 1 //Very rare - max_occurrences = 1 - earliest_start = 1 HOURS - min_players = 20 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_ENTITIES - description = "Spawns a slaughter demon, to hunt by travelling through pools of blood." - min_wizard_trigger_potency = 6 - max_wizard_trigger_potency = 7 - -/datum/round_event/ghost_role/slaughter - minimum_required = 1 - role_name = "slaughter demon" - -/datum/round_event/ghost_role/slaughter/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, pic_source = /mob/living/basic/demon/slaughter, role_name_text = role_name) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick_n_take(candidates) - - var/datum/mind/player_mind = new /datum/mind(selected.key) - player_mind.active = TRUE - - var/spawn_location = find_space_spawn() - if(!spawn_location) - return MAP_ERROR //This sends an error message further up. - var/mob/living/basic/demon/slaughter/spawned = new(spawn_location) - new /obj/effect/dummy/phased_mob(spawn_location, spawned) - - player_mind.transfer_to(spawned) - spawned.generate_antagonist_status() - - message_admins("[ADMIN_LOOKUPFLW(spawned)] has been made into a slaughter demon by an event.") - spawned.log_message("was spawned as a slaughter demon by an event.", LOG_GAME) - spawned_mobs += spawned - return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/space_dragon.dm b/code/modules/events/ghost_role/space_dragon.dm deleted file mode 100644 index 0a328f6dc8d8..000000000000 --- a/code/modules/events/ghost_role/space_dragon.dm +++ /dev/null @@ -1,41 +0,0 @@ -/datum/round_event_control/space_dragon - name = "Spawn Space Dragon" - typepath = /datum/round_event/ghost_role/space_dragon - weight = 7 - max_occurrences = 1 - min_players = 20 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_ENTITIES - description = "Spawns a space dragon, which will try to take over the station." - min_wizard_trigger_potency = 6 - max_wizard_trigger_potency = 7 - -/datum/round_event/ghost_role/space_dragon - minimum_required = 1 - role_name = "Space Dragon" - announce_when = 10 - -/datum/round_event/ghost_role/space_dragon/announce(fake) - priority_announce("A large organic energy flux has been recorded near [station_name()], please stand by.", "Lifesign Alert") - -/datum/round_event/ghost_role/space_dragon/spawn_role() - - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_SPACE_DRAGON, role = ROLE_SPACE_DRAGON, pic_source = /mob/living/basic/space_dragon) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick(candidates) - var/key = selected.key - - var/spawn_location = find_space_spawn() - if(isnull(spawn_location)) - return MAP_ERROR - - var/mob/living/basic/space_dragon/dragon = new (spawn_location) - dragon.key = key - dragon.mind.add_antag_datum(/datum/antagonist/space_dragon) - playsound(dragon, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - message_admins("[ADMIN_LOOKUPFLW(dragon)] has been made into a Space Dragon by an event.") - dragon.log_message("was spawned as a Space Dragon by an event.", LOG_GAME) - spawned_mobs += dragon - return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/space_ninja.dm b/code/modules/events/ghost_role/space_ninja.dm deleted file mode 100644 index ffa28e6e1c4f..000000000000 --- a/code/modules/events/ghost_role/space_ninja.dm +++ /dev/null @@ -1,49 +0,0 @@ -/datum/round_event_control/space_ninja - name = "Spawn Space Ninja" - typepath = /datum/round_event/ghost_role/space_ninja - max_occurrences = 1 - weight = 10 - earliest_start = 20 MINUTES - min_players = 20 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_INVASION - description = "A space ninja infiltrates the station." - -/datum/round_event/ghost_role/space_ninja - minimum_required = 1 - role_name = "Space Ninja" - -/datum/round_event/ghost_role/space_ninja/spawn_role() - var/spawn_location = find_space_spawn() - if(isnull(spawn_location)) - return MAP_ERROR - - //selecting a candidate player - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_NINJA, role = ROLE_NINJA, pic_source = /obj/item/energy_katana) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected_candidate = pick(candidates) - var/key = selected_candidate.key - - //spawn the ninja and assign the candidate - var/mob/living/carbon/human/ninja = create_space_ninja(spawn_location) - ninja.key = key - ninja.mind.add_antag_datum(/datum/antagonist/ninja) - spawned_mobs += ninja - message_admins("[ADMIN_LOOKUPFLW(ninja)] has been made into a space ninja by an event.") - ninja.log_message("was spawned as a ninja by an event.", LOG_GAME) - - return SUCCESSFUL_SPAWN - - -//=======//NINJA CREATION PROCS//=======// - -/proc/create_space_ninja(spawn_loc) - var/mob/living/carbon/human/new_ninja = new(spawn_loc) - new_ninja.randomize_human_appearance(~(RANDOMIZE_NAME|RANDOMIZE_SPECIES)) - var/new_name = "[pick(GLOB.ninja_titles)] [pick(GLOB.ninja_names)]" - new_ninja.name = new_name - new_ninja.real_name = new_name - new_ninja.dna.update_dna_identity() - return new_ninja diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm index a8c54996d18a..7c89ff101752 100644 --- a/code/modules/events/holiday/vday.dm +++ b/code/modules/events/holiday/vday.dm @@ -53,13 +53,11 @@ /datum/round_event/valentines/proc/forge_valentines_objective(mob/living/lover, mob/living/date) var/datum/antagonist/valentine/valentine = new() valentine.date = date.mind - lover.mind.special_role = "valentine" lover.mind.add_antag_datum(valentine) //These really should be teams but i can't be assed to incorporate third wheels right now /datum/round_event/valentines/proc/forge_third_wheel(mob/living/sad_one, mob/living/date_one, mob/living/date_two) var/datum/antagonist/valentine/third_wheel/third_wheel = new() third_wheel.date = pick(date_one.mind, date_two.mind) - sad_one.mind.special_role = "valentine" sad_one.mind.add_antag_datum(third_wheel) /datum/round_event/valentines/start() diff --git a/code/modules/events/shuttle_insurance.dm b/code/modules/events/shuttle_insurance.dm index 4fce9c556b8b..4442291a9ed9 100644 --- a/code/modules/events/shuttle_insurance.dm +++ b/code/modules/events/shuttle_insurance.dm @@ -43,7 +43,7 @@ /datum/round_event/shuttle_insurance/start() insurance_message = new("Shuttle Insurance", "Hey, pal, this is the [ship_name]. Can't help but notice you're rocking a wild and crazy shuttle there with NO INSURANCE! Crazy. What if something happened to it, huh?! We've done a quick evaluation on your rates in this sector and we're offering [insurance_evaluation] to cover for your shuttle in case of any disaster.", list("Purchase Insurance.","Reject Offer.")) insurance_message.answer_callback = CALLBACK(src, PROC_REF(answered)) - SScommunications.send_message(insurance_message, unique = TRUE) + GLOB.communications_controller.send_message(insurance_message, unique = TRUE) /datum/round_event/shuttle_insurance/proc/answered() if(EMERGENCY_AT_LEAST_DOCKED) diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm deleted file mode 100644 index ab518fd3af11..000000000000 --- a/code/modules/events/spider_infestation.dm +++ /dev/null @@ -1,36 +0,0 @@ -/datum/round_event_control/spider_infestation - name = "Spider Infestation" - typepath = /datum/round_event/spider_infestation - weight = 10 - max_occurrences = 1 - min_players = 20 - dynamic_should_hijack = TRUE - category = EVENT_CATEGORY_ENTITIES - description = "Spawns spider eggs, ready to hatch." - min_wizard_trigger_potency = 5 - max_wizard_trigger_potency = 7 - -/datum/round_event/spider_infestation - announce_when = 400 - var/spawncount = 2 - -/datum/round_event/spider_infestation/setup() - announce_when = rand(announce_when, announce_when + 50) - -/datum/round_event/spider_infestation/announce(fake) - priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", ANNOUNCER_ALIENS) - -/datum/round_event/spider_infestation/start() - create_midwife_eggs(spawncount) - -/proc/create_midwife_eggs(amount) - while(amount > 0) - var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) - if(isnull(spawn_loc)) - return //Admins will have already been notified of the spawning failure at this point - var/obj/effect/mob_spawn/ghost_role/spider/midwife/new_eggs = new (spawn_loc) - new_eggs.amount_grown = 98 - amount-- - log_game("Midwife spider eggs were spawned via an event.") - return TRUE - diff --git a/code/modules/events/wizard/imposter.dm b/code/modules/events/wizard/imposter.dm index 829942261427..6c6f3f84b3e7 100644 --- a/code/modules/events/wizard/imposter.dm +++ b/code/modules/events/wizard/imposter.dm @@ -35,7 +35,6 @@ imposter.wiz_team = master.wiz_team master.wiz_team.add_member(imposter) I.mind.add_antag_datum(imposter) - I.mind.special_role = "imposter" I.log_message("is an imposter!", LOG_ATTACK, color="red") //? SEND_SOUND(I, sound('sound/effects/magic.ogg')) announce_to_ghosts(I) diff --git a/code/modules/events/wizard/rpgtitles.dm b/code/modules/events/wizard/rpgtitles.dm index 37eae2459f62..68df2bd27cb7 100644 --- a/code/modules/events/wizard/rpgtitles.dm +++ b/code/modules/events/wizard/rpgtitles.dm @@ -38,7 +38,7 @@ GLOBAL_DATUM(rpgtitle_controller, /datum/rpgtitle_controller) /datum/rpgtitle_controller/proc/on_crewmember_join(datum/source, mob/living/new_crewmember, rank) SIGNAL_HANDLER - var/datum/job/job = SSjob.GetJob(rank) + var/datum/job/job = SSjob.get_job(rank) //we must prepare for the mother of all strings new_crewmember.maptext_height = max(new_crewmember.maptext_height, 32) diff --git a/code/modules/jobs/job_exp.dm b/code/modules/jobs/job_exp.dm index b2c051a4e1c7..ed86ecdbfcd2 100644 --- a/code/modules/jobs/job_exp.dm +++ b/code/modules/jobs/job_exp.dm @@ -155,7 +155,7 @@ GLOBAL_PROTECT(exp_to_update) if(isobserver(mob)) play_records[EXP_TYPE_GHOST] = minutes - else if(isliving(mob)) + else if(isliving(mob) && !isnull(mob.mind)) var/mob/living/living_mob = mob var/list/mob_exp_list = living_mob.get_exp_list(minutes) if(!length(mob_exp_list)) diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 7a9f503ccd87..2b4c274ff442 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -419,10 +419,10 @@ if(visualsOnly) return - var/datum/job/equipped_job = SSjob.GetJobType(jobtype) + var/datum/job/equipped_job = SSjob.get_job_type(jobtype) if(!equipped_job) - equipped_job = SSjob.GetJob(equipped.job) + equipped_job = SSjob.get_job(equipped.job) var/obj/item/card/id/card = equipped.wear_id @@ -654,3 +654,7 @@ /datum/job/proc/after_latejoin_spawn(mob/living/spawning) SHOULD_CALL_PARENT(TRUE) SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN, src, spawning) + +/// Called when a mob that has this job is admin respawned +/datum/job/proc/on_respawn(mob/new_character) + SSjob.equip_rank(new_character, new_character.mind.assigned_role, new_character.client) diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm index a19a8d1e5762..95b1c9265e73 100644 --- a/code/modules/jobs/job_types/ai.dm +++ b/code/modules/jobs/job_types/ai.dm @@ -92,3 +92,6 @@ /datum/job/ai/get_radio_information() return "Prefix your message with :[MODE_KEY_BINARY] to speak with cyborgs and other AIs." + +/datum/job/ai/on_respawn(mob/new_character) + new_character.AIize() diff --git a/code/modules/jobs/job_types/antagonists/clown_operative.dm b/code/modules/jobs/job_types/antagonists/clown_operative.dm deleted file mode 100644 index 7c1a333da2c7..000000000000 --- a/code/modules/jobs/job_types/antagonists/clown_operative.dm +++ /dev/null @@ -1,2 +0,0 @@ -/datum/job/clown_operative - title = ROLE_CLOWN_OPERATIVE diff --git a/code/modules/jobs/job_types/antagonists/nuclear_operative.dm b/code/modules/jobs/job_types/antagonists/nuclear_operative.dm index 6fbfdd50994b..f2e38bf27d74 100644 --- a/code/modules/jobs/job_types/antagonists/nuclear_operative.dm +++ b/code/modules/jobs/job_types/antagonists/nuclear_operative.dm @@ -1,8 +1,27 @@ /datum/job/nuclear_operative - title = ROLE_NUCLEAR_OPERATIVE + title = ROLE_OPERATIVE /datum/job/nuclear_operative/get_roundstart_spawn_point() return pick(GLOB.nukeop_start) /datum/job/nuclear_operative/get_latejoin_spawn_point() return pick(GLOB.nukeop_start) + +/datum/job/nuclear_operative/leader + +/datum/job/nuclear_operative/leader/get_roundstart_spawn_point() + return pick(GLOB.nukeop_leader_start) + +/datum/job/nuclear_operative/leader/get_latejoin_spawn_point() + return pick(GLOB.nukeop_leader_start) + +/datum/job/nuclear_operative/clown_operative + title = ROLE_CLOWN_OPERATIVE + +/datum/job/nuclear_operative/clown_operative/leader + +/datum/job/nuclear_operative/clown_operative/leader/get_roundstart_spawn_point() + return pick(GLOB.nukeop_leader_start) + +/datum/job/nuclear_operative/clown_operative/leader/get_latejoin_spawn_point() + return pick(GLOB.nukeop_leader_start) diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 4445c9da81a0..0f7210b4496c 100644 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -45,7 +45,7 @@ /obj/item/skillchip/sabrage = 5, ) - job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS + job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS | JOB_ANTAG_PROTECTED rpg_title = "Star Duke" voice_of_god_power = 1.4 //Command staff has authority diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm index a68fd7782592..4898674af7e3 100644 --- a/code/modules/jobs/job_types/cook.dm +++ b/code/modules/jobs/job_types/cook.dm @@ -88,7 +88,7 @@ ..() if(!change_hat) return - var/datum/job/cook/other_chefs = SSjob.GetJobType(jobtype) + var/datum/job/cook/other_chefs = SSjob.get_job_type(jobtype) if(other_chefs) // If there's other Chefs, you're a Cook if(other_chefs.cooks > 0)//Cooks id_trim = /datum/id_trim/job/cook diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm index 6b92a6c1acb3..7158b1226abc 100644 --- a/code/modules/jobs/job_types/cyborg.dm +++ b/code/modules/jobs/job_types/cyborg.dm @@ -34,3 +34,6 @@ /datum/job/cyborg/get_radio_information() return "Prefix your message with :[MODE_KEY_BINARY] to speak with other cyborgs and AI." + +/datum/job/cyborg/on_respawn(mob/new_character) + new_character.Robotize(TRUE) diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm index 3eee6faf4913..b60b3416e997 100644 --- a/code/modules/jobs/job_types/detective.dm +++ b/code/modules/jobs/job_types/detective.dm @@ -48,7 +48,7 @@ /obj/item/reagent_containers/cup/glass/flask/det, ) rpg_title = "Thiefcatcher" //I guess they caught them all rip thief... - job_flags = STATION_JOB_FLAGS + job_flags = STATION_JOB_FLAGS | JOB_ANTAG_PROTECTED job_tone = "objection" diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index 5073bd32b49e..108012f7d07b 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -37,7 +37,7 @@ /obj/item/book/manual/wiki/security_space_law, ) rpg_title = "Guard Leader" - job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS + job_flags = STATION_JOB_FLAGS | HEAD_OF_STAFF_JOB_FLAGS | JOB_ANTAG_PROTECTED voice_of_god_power = 1.4 //Command staff has authority diff --git a/code/modules/jobs/job_types/prisoner.dm b/code/modules/jobs/job_types/prisoner.dm index 5bc4e7538956..2520be1e7220 100644 --- a/code/modules/jobs/job_types/prisoner.dm +++ b/code/modules/jobs/job_types/prisoner.dm @@ -25,7 +25,7 @@ /obj/item/pen/blue, ) rpg_title = "Defeated Miniboss" - job_flags = STATION_JOB_FLAGS | JOB_CANNOT_OPEN_SLOTS & ~JOB_REOPEN_ON_ROUNDSTART_LOSS + job_flags = STATION_JOB_FLAGS | JOB_CANNOT_OPEN_SLOTS | JOB_ANTAG_PROTECTED & ~JOB_REOPEN_ON_ROUNDSTART_LOSS /datum/job/prisoner/New() . = ..() diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index ed85ebc0ee0a..8d51a824f350 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -45,7 +45,7 @@ /obj/item/melee/baton/security/boomerang/loaded = 1 ) rpg_title = "Guard" - job_flags = STATION_JOB_FLAGS + job_flags = STATION_JOB_FLAGS | JOB_ANTAG_PROTECTED /datum/job/security_officer/get_titles(only_selectable = FALSE) . = ..() diff --git a/code/modules/jobs/job_types/station_trait/bridge_assistant.dm b/code/modules/jobs/job_types/station_trait/bridge_assistant.dm index 0a086b0fc8c1..f7c60a1dc461 100644 --- a/code/modules/jobs/job_types/station_trait/bridge_assistant.dm +++ b/code/modules/jobs/job_types/station_trait/bridge_assistant.dm @@ -34,7 +34,7 @@ ) rpg_title = "Royal Guard" allow_bureaucratic_error = FALSE - job_flags = STATION_JOB_FLAGS | STATION_TRAIT_JOB_FLAGS + job_flags = STATION_JOB_FLAGS | STATION_TRAIT_JOB_FLAGS | JOB_ANTAG_PROTECTED ignore_human_authority = TRUE /datum/job/bridge_assistant/after_spawn(mob/living/spawned, client/player_client) diff --git a/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm b/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm index 2e387b5c33a8..c11c931a3ef9 100644 --- a/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm +++ b/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm @@ -16,7 +16,7 @@ ) rpg_title = "Beast of Burden" allow_bureaucratic_error = FALSE - job_flags = STATION_TRAIT_JOB_FLAGS | JOB_ANNOUNCE_ARRIVAL | JOB_NEW_PLAYER_JOINABLE | JOB_EQUIP_RANK + job_flags = STATION_TRAIT_JOB_FLAGS | JOB_ANNOUNCE_ARRIVAL | JOB_NEW_PLAYER_JOINABLE | JOB_EQUIP_RANK |JOB_ANTAG_BLACKLISTED /datum/job/cargo_gorilla/get_roundstart_spawn_point() if (length(GLOB.gorilla_start)) @@ -47,4 +47,4 @@ to_chat(spawned, span_boldnotice("You are Cargorilla, a pacifist friend of the station and carrier of freight.")) to_chat(spawned, span_notice("You can pick up crates by clicking on them, and drop them by clicking on the ground.")) - spawned.mind.special_role = "Cargorilla" + LAZYADD(spawned.mind.special_roles, "Cargorilla") diff --git a/code/modules/jobs/job_types/station_trait/veteran_advisor.dm b/code/modules/jobs/job_types/station_trait/veteran_advisor.dm index 50607967f8ab..795e4caad7d3 100644 --- a/code/modules/jobs/job_types/station_trait/veteran_advisor.dm +++ b/code/modules/jobs/job_types/station_trait/veteran_advisor.dm @@ -37,7 +37,7 @@ ) rpg_title = "Royal Advisor" allow_bureaucratic_error = FALSE - job_flags = STATION_JOB_FLAGS | STATION_TRAIT_JOB_FLAGS + job_flags = STATION_JOB_FLAGS | STATION_TRAIT_JOB_FLAGS | JOB_ANTAG_PROTECTED crewmonitor_priority = 18 diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index 97db1a333c9c..0f669b3aa4cc 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -42,7 +42,7 @@ /obj/item/storage/box/lethalshot = 5 ) rpg_title = "Jailor" - job_flags = STATION_JOB_FLAGS | JOB_BOLD_SELECT_TEXT + job_flags = STATION_JOB_FLAGS | JOB_BOLD_SELECT_TEXT | JOB_ANTAG_PROTECTED /datum/outfit/job/warden name = "Warden" diff --git a/code/modules/meteors/meteor_mode_controller.dm b/code/modules/meteors/meteor_mode_controller.dm new file mode 100644 index 000000000000..d19d5522bb77 --- /dev/null +++ b/code/modules/meteors/meteor_mode_controller.dm @@ -0,0 +1,31 @@ +/// Global that controls meteor mode +GLOBAL_DATUM(meteor_mode, /datum/meteor_mode_controller) + +/// All this datum does is regularly spawn meteors until the round ends. +/datum/meteor_mode_controller + /// Delay before meteors start falling + var/meteordelay = 5 MINUTES + /// Every [x] minutes, more meteors will spawn + var/rampupdelta = 5 + +/datum/meteor_mode_controller/proc/start_meteor() + if(datum_flags & DF_ISPROCESSING) + return + START_PROCESSING(SSprocessing, src) + +/datum/meteor_mode_controller/process(seconds_per_tick) + if(meteordelay > world.time - SSticker.round_start_time) + return + + var/list/wavetype = GLOB.meteors_normal + var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 + + if (prob(meteorminutes)) + wavetype = GLOB.meteors_threatening + + if (prob(meteorminutes/2)) + wavetype = GLOB.meteors_catastrophic + + var/ramp_up_final = clamp(round(meteorminutes / rampupdelta), 1, 10) + + spawn_meteors(ramp_up_final, wavetype) diff --git a/code/modules/meteors/meteor_spawning.dm b/code/modules/meteors/meteor_spawning.dm index d19d3aff0a5a..ecdb3f127134 100644 --- a/code/modules/meteors/meteor_spawning.dm +++ b/code/modules/meteors/meteor_spawning.dm @@ -77,10 +77,7 @@ * Arguments: * * candidate - The mob (player) to be transformed into a changeling and meteored. */ -/proc/generate_changeling_meteor(mob/dead/selected) - var/datum/mind/player_mind = new(selected.key) - player_mind.active = TRUE - +/proc/generate_changeling_meteor(datum/mind/player_mind) var/turf/picked_start if (SSmapping.is_planetary()) @@ -101,8 +98,7 @@ new_changeling.forceMove(changeling_meteor) //Place our payload inside of its vessel - player_mind.transfer_to(new_changeling) - player_mind.special_role = ROLE_CHANGELING_MIDROUND + player_mind.transfer_to(new_changeling, force_key_move = TRUE) player_mind.add_antag_datum(/datum/antagonist/changeling/space) SEND_SOUND(new_changeling, 'sound/magic/mutate.ogg') message_admins("[ADMIN_LOOKUPFLW(new_changeling)] has been made into a space changeling by an event.") diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 1b7a82b267cd..7fab5fd6a6fb 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -126,7 +126,7 @@ return GENERIC_JOB_UNAVAILABLE_ERROR /mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) - var/datum/job/job = SSjob.GetJob(rank) + var/datum/job/job = SSjob.get_job(rank) if(!(job.job_flags & JOB_NEW_PLAYER_JOINABLE)) return JOB_UNAVAILABLE_GENERIC if((job.current_positions >= job.total_positions) && job.total_positions != -1) @@ -171,9 +171,9 @@ SSticker.queued_players -= src SSticker.queue_delay = 4 - var/datum/job/job = SSjob.GetJob(rank) + var/datum/job/job = SSjob.get_job(rank) - if(!SSjob.AssignRole(src, job, TRUE)) + if(!SSjob.assign_role(src, job, TRUE)) tgui_alert(usr, "There was an unexpected error putting you into your requested job. If you cannot join with any job, you should contact an admin.") return FALSE @@ -226,14 +226,8 @@ GLOB.joined_player_list += character.ckey - if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_RECALL, SHUTTLE_IDLE) - SSdynamic.make_antag_chance(humanc) - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergency_call_time)*0.5) - SSdynamic.make_antag_chance(humanc) + if(CONFIG_GET(flag/allow_latejoin_antagonists) && !EMERGENCY_PAST_POINT_OF_NO_RETURN && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. + SSdynamic.on_latejoin(humanc) if(humanc) if(job.job_flags & JOB_ASSIGN_QUIRKS) @@ -307,23 +301,24 @@ // Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not available" // Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role // This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong -/mob/dead/new_player/proc/check_preferences() +/mob/dead/new_player/proc/check_job_preferences(warn = TRUE) if(!client) return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. if(client.prefs.read_preference(/datum/preference/choiced/jobless_role) != RETURNTOLOBBY) return TRUE // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. - var/has_antags = FALSE - if(client.prefs.be_special.len > 0) - has_antags = TRUE + var/has_antags = length(client.prefs.be_special) > 0 if(client.prefs.job_preferences.len == 0) - if(!ineligible_for_roles) - to_chat(src, span_danger("You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.")) - ineligible_for_roles = TRUE + if(warn) + to_chat(src, span_danger("You have no jobs enabled, along with return to lobby if job is unavailable. \ + This makes you ineligible for any round start role, please update your job preferences.")) unready() if(has_antags) - log_admin("[src.ckey] has no jobs enabled, return to lobby if job is unavailable enabled and [client.prefs.be_special.len] antag preferences enabled. The player has been forcefully returned to the lobby.") - message_admins("[src.ckey] has no jobs enabled, return to lobby if job is unavailable enabled and [client.prefs.be_special.len] antag preferences enabled. This is an old antag rolling technique. The player has been asked to update their job preferences and has been forcefully returned to the lobby.") + log_admin("[src.ckey] has no jobs enabled, return to lobby if job is unavailable enabled and [client.prefs.be_special.len] \ + antag preferences enabled. The player has been forcefully returned to the lobby.") + message_admins("[src.ckey] has no jobs enabled, return to lobby if job is unavailable enabled and [client.prefs.be_special.len] \ + antag preferences enabled. This is an old antag rolling technique. The player has been asked to update their job preferences \ + and has been forcefully returned to the lobby.") return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well return TRUE diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm index a745618f1a32..0c75bf4525cd 100644 --- a/code/modules/mob/dead/new_player/preferences_setup.dm +++ b/code/modules/mob/dead/new_player/preferences_setup.dm @@ -88,13 +88,13 @@ for(var/job in job_preferences) if(job_preferences[job] > highest_pref) - preview_job = SSjob.GetJob(job) + preview_job = SSjob.get_job(job) highest_pref = job_preferences[job] return preview_job /datum/preferences/proc/render_new_preview_appearance(mob/living/carbon/human/dummy/mannequin, show_job_clothes = TRUE) - var/datum/job/no_job = SSjob.GetJobType(/datum/job/unassigned) + var/datum/job/no_job = SSjob.get_job_type(/datum/job/unassigned) var/datum/job/preview_job = get_highest_priority_job() || no_job if(preview_job) diff --git a/code/modules/mob/living/basic/drone/interaction.dm b/code/modules/mob/living/basic/drone/interaction.dm index debb5c7b33d7..dea5fc854299 100644 --- a/code/modules/mob/living/basic/drone/interaction.dm +++ b/code/modules/mob/living/basic/drone/interaction.dm @@ -157,7 +157,7 @@ to_chat(src, "Your onboard antivirus has initiated lockdown. Motor servos are impaired, ventilation access is denied, and your display reports that you are hacked to all nearby.") hacked = TRUE set_shy(FALSE) - mind.special_role = "hacked drone" + LAZYADD(mind.special_roles, "Hacked Drone") REMOVE_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) speed = 1 //gotta go slow message_admins("[ADMIN_LOOKUPFLW(src)] became a hacked drone hellbent on destroying the station!") @@ -173,7 +173,7 @@ to_chat(src, "Having been restored, your onboard antivirus reports the all-clear and you are able to perform all actions again.") hacked = FALSE set_shy(initial(shy)) - mind.special_role = null + LAZYREMOVE(mind.special_roles, "Hacked Drone") ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) speed = initial(speed) message_admins("[ADMIN_LOOKUPFLW(src)], a hacked drone, was restored to factory defaults!") diff --git a/code/modules/mob/living/basic/space_fauna/demon/demon.dm b/code/modules/mob/living/basic/space_fauna/demon/demon.dm index ccecbf2cd461..22646790c37e 100644 --- a/code/modules/mob/living/basic/space_fauna/demon/demon.dm +++ b/code/modules/mob/living/basic/space_fauna/demon/demon.dm @@ -67,8 +67,7 @@ if(isnull(antag_type)) return // we weren't built for this proc to run - mind.set_assigned_role(SSjob.GetJobType(/datum/job/slaughter_demon)) - mind.special_role = ROLE_SLAUGHTER_DEMON + mind.set_assigned_role(SSjob.get_job_type(/datum/job/slaughter_demon)) mind.add_antag_datum(antag_type) SEND_SOUND(src, 'sound/magic/demon_dies.ogg') diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm index 2f5bc61b5973..35f040b3ac39 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -125,8 +125,7 @@ return TRUE generated_objectives_and_spells = TRUE - mind.set_assigned_role(SSjob.GetJobType(/datum/job/revenant)) - mind.special_role = ROLE_REVENANT + mind.set_assigned_role(SSjob.get_job_type(/datum/job/revenant)) SEND_SOUND(src, sound('sound/effects/ghost.ogg')) mind.add_antag_datum(/datum/antagonist/revenant) return TRUE diff --git a/code/modules/mob/living/brain/posibrain.dm b/code/modules/mob/living/brain/posibrain.dm index b476299b1d8c..4959321ebdd9 100644 --- a/code/modules/mob/living/brain/posibrain.dm +++ b/code/modules/mob/living/brain/posibrain.dm @@ -139,7 +139,7 @@ GLOBAL_VAR(posibrain_notify_cooldown) brainmob.timeofdeath = transferred_user.timeofdeath brainmob.revive() if(brainmob.mind) - brainmob.mind.set_assigned_role(SSjob.GetJobType(posibrain_job_path)) + brainmob.mind.set_assigned_role(SSjob.get_job_type(posibrain_job_path)) if(transferred_user.mind) transferred_user.mind.transfer_to(brainmob) @@ -162,7 +162,7 @@ GLOBAL_VAR(posibrain_notify_cooldown) var/policy = get_policy(ROLE_POSIBRAIN) if(policy) to_chat(brainmob, policy) - brainmob.mind.set_assigned_role(SSjob.GetJobType(posibrain_job_path)) + brainmob.mind.set_assigned_role(SSjob.get_job_type(posibrain_job_path)) brainmob.revive() visible_message(new_mob_message) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 78dc6329775d..311f50549627 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -940,7 +940,6 @@ /mob/living/carbon/human/get_exp_list(minutes) . = ..() - if(mind.assigned_role.title in SSjob.name_occupations) .[mind.assigned_role.title] = minutes diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 67b84d821be9..dbb9a405204e 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -295,7 +295,7 @@ clone.age = age dna.transfer_identity(clone, transfer_SE = TRUE, transfer_species = TRUE) - clone.dress_up_as_job(SSjob.GetJob(job)) + clone.dress_up_as_job(SSjob.get_job(job)) for(var/datum/quirk/original_quircks as anything in quirks) clone.add_quirk(original_quircks.type, override_client = client, announce = FALSE) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index bf11c7d9cf4b..e26b182a4db6 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -2557,8 +2557,11 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/proc/get_exp_list(minutes) var/list/exp_list = list() - if(mind && mind.special_role && !(mind.datum_flags & DF_VAR_EDITED)) - exp_list[mind.special_role] = minutes + if(!(mind.datum_flags & DF_VAR_EDITED)) + for(var/datum/antagonist/antag as anything in mind?.antag_datums) + var/flag_to_check = antag.jobban_flag || antag.pref_flag + if(flag_to_check) + exp_list[flag_to_check] = minutes if(mind.assigned_role.title in GLOB.exp_specialmap[EXP_TYPE_SPECIAL]) exp_list[mind.assigned_role.title] = minutes diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index f3e733207a5d..afcf49ebd4a4 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -133,7 +133,7 @@ if(target_ai.mind) target_ai.mind.transfer_to(src) - if(mind.special_role) + if(is_antag()) to_chat(src, span_userdanger("You have been installed as an AI! ")) to_chat(src, span_danger("You must obey your silicon laws above all else. Your objectives will consider you to be dead.")) if(!mind.has_ever_been_ai) @@ -1156,11 +1156,7 @@ /mob/living/silicon/ai/get_exp_list(minutes) . = ..() - - var/datum/job/ai/ai_job_ref = SSjob.GetJobType(/datum/job/ai) - - .[ai_job_ref.title] = minutes - + .[/datum/job/ai::title] = minutes /mob/living/silicon/ai/get_voice(add_id_name) if(ai_voicechanger?.changing_voice) diff --git a/code/modules/mob/living/silicon/ai/ai_say.dm b/code/modules/mob/living/silicon/ai/ai_say.dm index c3b9c0267f39..255dd42cbda1 100644 --- a/code/modules/mob/living/silicon/ai/ai_say.dm +++ b/code/modules/mob/living/silicon/ai/ai_say.dm @@ -23,7 +23,7 @@ /mob/living/silicon/ai/compose_job(atom/movable/speaker, message_langs, raw_message, radio_freq) //Also includes the for AI hrefs, for convenience. - return "[radio_freq ? " (" + speaker.GetJob() + ")" : ""]" + "[speaker.GetSource() ? "" : ""]" + return "[radio_freq ? " (" + speaker.get_job() + ")" : ""]" + "[speaker.GetSource() ? "" : ""]" /mob/living/silicon/ai/try_speak(message, ignore_spam = FALSE, forced = null, filterproof = FALSE) // AIs cannot speak if silent AI is on. diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 8fa7fd9354f9..323a1519fcd1 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1033,10 +1033,7 @@ /mob/living/silicon/robot/get_exp_list(minutes) . = ..() - - var/datum/job/cyborg/cyborg_job_ref = SSjob.GetJobType(/datum/job/cyborg) - - .[cyborg_job_ref.title] = minutes + .[/datum/job/cyborg::title] = minutes /mob/living/silicon/robot/proc/untip_roleplay() to_chat(src, span_notice("Your frustration has empowered you! You can now right yourself faster!")) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 4db34d582a6a..256828240831 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -340,7 +340,7 @@ var/mob/living/carbon/human/new_clown = user for(var/obj/item/to_strip in new_clown.get_equipped_items()) new_clown.dropItemToGround(to_strip) - new_clown.dress_up_as_job(SSjob.GetJobType(/datum/job/clown)) + new_clown.dress_up_as_job(SSjob.get_job_type(/datum/job/clown)) clowned_mob_refs += clown_ref return TRUE diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 4d15d747bf7f..29cadca9861e 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -225,39 +225,22 @@ return " \[[real_name]\]" return "" -// moved out of admins.dm because things other than admin procs were calling this. /** - * Returns TRUE if the game has started and we're either an AI with a 0th law, or we're someone with a special role/antag datum - * If allow_fake_antags is set to FALSE, Valentines, ERTs, and any such roles with FLAG_FAKE_ANTAG won't pass. -*/ -/proc/is_special_character(mob/M, allow_fake_antags = FALSE) - if(!SSticker.HasRoundStarted()) - return FALSE - if(!istype(M)) - return FALSE - if(iscyborg(M)) //as a borg you're now beholden to your laws rather than greentext - return FALSE - - - // Returns TRUE if AI has a zeroth law *and* either has a special role *or* an antag datum. - if(isAI(M)) - var/mob/living/silicon/ai/A = M - return (A.laws?.zeroth && (A.mind?.special_role || !isnull(M.mind?.antag_datums))) + * Checks if this mob is an antag + * By default excludes antags like Valentines, which are "fake antags" + */ +/mob/proc/is_antag(blacklisted_antag_flags = ANTAG_FAKE) + for(var/datum/antagonist/antag_datum as anything in mind?.antag_datums) + if(!blacklisted_antag_flags || !(antag_datum.antag_flags & blacklisted_antag_flags)) + return TRUE - if(M.mind?.special_role) - return TRUE + return FALSE - // Turns 'faker' to TRUE if the antag datum is fake. If it's not fake, returns TRUE directly. - var/faker = FALSE - for(var/datum/antagonist/antag_datum as anything in M.mind?.antag_datums) - if((antag_datum.antag_flags & FLAG_FAKE_ANTAG)) - faker = TRUE - else - return TRUE +/mob/living/silicon/robot/is_antag(blacklisted_antag_flags) + return FALSE - // If 'faker' was assigned TRUE in the above loop and the argument 'allow_fake_antags' is set to TRUE, this passes. - // Else, return FALSE. - return (faker && allow_fake_antags) +/mob/living/silicon/ai/is_antag(blacklisted_antag_flags) + return ..() && !!(laws?.zeroth) // AIs only count as antags if they have a zeroth law (apparently) /** * Fancy notifications for ghosts @@ -362,16 +345,12 @@ if(usr) log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.") message_admins("[key_name_admin(usr)] has offered control of ([ADMIN_LOOKUPFLW(M)]) to ghosts") - var/poll_message = "Do you want to play as [M.real_name]?" - if(M.mind) - poll_message = "[poll_message] Job: [M.mind.assigned_role.title]." - if(M.mind.special_role) - poll_message = "[poll_message] Status: [M.mind.special_role]." - else - var/datum/antagonist/A = M.mind.has_antag_datum(/datum/antagonist/) - if(A) - poll_message = "[poll_message] Status: [A.name]." - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob(poll_message, check_jobban = ROLE_PAI, poll_time = 10 SECONDS, target_mob = M, pic_source = M, role_name_text = "ghost control") + var/whomst = span_danger(M.real_name) + if(M.mind && !is_unassigned_job(M.mind?.assigned_role)) + whomst += "Job: [span_notice(M.mind.assigned_role.title)]." + if(length(M.mind?.get_special_roles())) + whomst += "Status: [span_boldnotice(english_list(M.mind?.get_special_roles()))]." + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [whomst]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, checked_target = M, alert_pic = M, role_name_text = "ghost control") if(LAZYLEN(candidates)) var/mob/dead/observer/C = pick(candidates) @@ -409,13 +388,17 @@ /mob/proc/get_policy_keywords() . = list() . += "[type]" - if(mind) - if(mind.assigned_role.policy_index) - . += mind.assigned_role.policy_index - . += mind.assigned_role.title //A bit redunant, but both title and policy index are used - . += mind.special_role //In case there's something special leftover, try to avoid - for(var/datum/antagonist/antag_datum as anything in mind.antag_datums) - . += "[antag_datum.type]" + if(isnull(mind)) + return + if(mind.assigned_role.policy_index) + . += mind.assigned_role.policy_index + . += mind.assigned_role.title //A bit redunant, but both title and policy index are used + for(var/datum/antagonist/antag_datum as anything in mind.antag_datums) + . += "[antag_datum.type]" + if(antag_datum.pref_flag) + . += antag_datum.pref_flag + if(antag_datum.jobban_flag) + . += antag_datum.jobban_flag ///Can the mob see reagents inside of containers? /mob/proc/can_see_reagents() diff --git a/code/modules/mob/mob_lists.dm b/code/modules/mob/mob_lists.dm index 9fd097a1fd6a..af74e8f8dec0 100644 --- a/code/modules/mob/mob_lists.dm +++ b/code/modules/mob/mob_lists.dm @@ -96,13 +96,13 @@ ///Adds the cliented mob reference to the list of living player-mobs. If the mob is an antag, it adds it to the list of living antag player-mobs. /mob/proc/add_to_current_living_players() GLOB.alive_player_list |= src - if(mind && (mind.special_role || length(mind.antag_datums))) + if(is_antag(NONE)) add_to_current_living_antags() ///Removes the mob reference from the list of living player-mobs. If the mob is an antag, it removes it from the list of living antag player-mobs. /mob/proc/remove_from_current_living_players() GLOB.alive_player_list -= src - if(LAZYLEN(mind?.antag_datums)) + if(is_antag(NONE)) remove_from_current_living_antags() @@ -112,9 +112,10 @@ return for (var/datum/antagonist/antagonist in mind.antag_datums) - if (antagonist.count_against_dynamic_roll_chance) - GLOB.current_living_antags |= src - return + if (antagonist.antag_flags & ANTAG_SKIP_GLOBAL_LIST) + continue + GLOB.current_living_antags |= src + return ///Removes the mob reference from the list of living antag player-mobs. /mob/proc/remove_from_current_living_antags() diff --git a/code/modules/mob_spawn/ghost_roles/unused_roles.dm b/code/modules/mob_spawn/ghost_roles/unused_roles.dm index d9cdd699ade7..4ca259fa47c3 100644 --- a/code/modules/mob_spawn/ghost_roles/unused_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/unused_roles.dm @@ -273,7 +273,7 @@ /obj/effect/mob_spawn/ghost_role/human/syndicatespace/special(mob/living/new_spawn) . = ..() new_spawn.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) - var/datum/job/spawn_job = SSjob.GetJobType(spawner_job_path) + var/datum/job/spawn_job = SSjob.get_job_type(spawner_job_path) var/policy = get_policy(spawn_job.policy_index) if(policy) to_chat(new_spawn, span_bold("[policy]")) diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index 0942217b16d8..b94499907570 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -245,7 +245,7 @@ spawned_mob.key = mob_possessor.key var/datum/mind/spawned_mind = spawned_mob.mind if(spawned_mind) - spawned_mob.mind.set_assigned_role_with_greeting(SSjob.GetJobType(spawner_job_path)) + spawned_mob.mind.set_assigned_role_with_greeting(SSjob.get_job_type(spawner_job_path)) spawned_mind.name = spawned_mob.real_name if(show_flavor) diff --git a/code/modules/modular_computers/computers/item/pda.dm b/code/modules/modular_computers/computers/item/pda.dm index 1d3687eabf44..7d85432739f0 100644 --- a/code/modules/modular_computers/computers/item/pda.dm +++ b/code/modules/modular_computers/computers/item/pda.dm @@ -211,7 +211,7 @@ if(from_message_menu) log_bomber(null, null, target, "'s tablet exploded as [target.p_they()] tried to open their tablet message menu because of a recent tablet bomb.") else - log_bomber(bomber, "successfully tablet-bombed", target, "as [target.p_they()] tried to reply to a rigged tablet message [bomber && !is_special_character(bomber) ? "(SENT BY NON-ANTAG)" : ""]") + log_bomber(bomber, "successfully tablet-bombed", target, "as [target.p_they()] tried to reply to a rigged tablet message [bomber?.is_antag() ? "" : "(SENT BY NON-ANTAG)"]") if (ismob(loc)) var/mob/loc_mob = loc diff --git a/code/modules/modular_computers/file_system/programs/jobmanagement.dm b/code/modules/modular_computers/file_system/programs/jobmanagement.dm index 8d4db5ffb08d..2d5184088639 100644 --- a/code/modules/modular_computers/file_system/programs/jobmanagement.dm +++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm @@ -62,7 +62,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) switch(action) if("PRG_open_job") var/edit_job_target = params["target"] - var/datum/job/j = SSjob.GetJob(edit_job_target) + var/datum/job/j = SSjob.get_job(edit_job_target) if(!can_edit_job(j) || !can_open_job(j)) return TRUE if(opened_positions[edit_job_target] >= 0) @@ -74,7 +74,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) return TRUE if("PRG_close_job") var/edit_job_target = params["target"] - var/datum/job/j = SSjob.GetJob(edit_job_target) + var/datum/job/j = SSjob.get_job(edit_job_target) if(!can_edit_job(j) || !can_close_job(j)) return TRUE //Allow instant closing without cooldown if a position has been opened before @@ -87,7 +87,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) return TRUE if("PRG_priority") var/priority_target = params["target"] - var/datum/job/j = SSjob.GetJob(priority_target) + var/datum/job/j = SSjob.get_job(priority_target) if(!can_edit_job(j)) return TRUE if(j.total_positions <= j.current_positions) diff --git a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm index ce6cd1620ba6..e8b3824c549b 100644 --- a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm +++ b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm @@ -631,7 +631,7 @@ // Log in the talk log source.log_talk(message, LOG_PDA, tag="[shell_addendum][rigged ? "Rigged" : ""] PDA: [computer.saved_identification] to [signal.format_target()]") if(rigged) - log_bomber(sender, "sent a rigged PDA message (Name: [fake_name]. Job: [fake_job]) to [english_list(stringified_targets)] [!is_special_character(sender) ? "(SENT BY NON-ANTAG)" : ""]") + log_bomber(sender, "sent a rigged PDA message (Name: [fake_name]. Job: [fake_job]) to [english_list(stringified_targets)] [sender.is_antag() ? "" : "(SENT BY NON-ANTAG)"]") // Show it to ghosts var/ghost_message = span_game_say("[span_name(signal.format_sender())] [rigged ? "(as [span_name(fake_name)]) Rigged " : ""]PDA Message --> [span_name("[signal.format_target()]")]: \"[signal.format_message()]\"") diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index afdead3fd380..cfbfc81b3a56 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -363,16 +363,12 @@ /obj/projectile/magic/wipe/proc/possession_test(mob/living/carbon/target) var/datum/brain_trauma/special/imaginary_friend/trapped_owner/trauma = target.gain_trauma(/datum/brain_trauma/special/imaginary_friend/trapped_owner) - var/poll_message = "Do you want to play as [target.real_name]?" - if(target.mind) - poll_message = "[poll_message] Job:[target.mind.assigned_role.title]." - if(target.mind && target.mind.special_role) - poll_message = "[poll_message] Status:[target.mind.special_role]." - else if(target.mind) - var/datum/antagonist/A = target.mind.has_antag_datum(/datum/antagonist/) - if(A) - poll_message = "[poll_message] Status:[A.name]." - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob(poll_message, check_jobban = ROLE_PAI, poll_time = 10 SECONDS, target_mob = target, pic_source = target, role_name_text = "bolt of possession") + var/whomst = span_danger(target.real_name) + if(!is_unassigned_job(target.mind?.assigned_role)) + whomst += "Job: [span_notice(target.mind.assigned_role.title)]." + if(length(target.mind?.get_special_roles())) + whomst += "Status: [span_boldnotice(english_list(target.mind.get_special_roles()))]." + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [whomst]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, checked_target = target, alert_pic = target, role_name_text = "bolt of possession") if(target.stat == DEAD)//boo. return if(LAZYLEN(candidates)) diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index 79a97b40a6bc..3f610fc23844 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -158,7 +158,7 @@ if(!tool.tool_behaviour) return ..() // Only antags are given the training and knowledge to disassemble this thing. - if(!is_special_character(user)) + if(!user.is_antag()) if(user.combat_mode) return ITEM_INTERACT_SKIP_TO_ATTACK balloon_alert(user, "you can't find an obvious maintenance hatch!") diff --git a/code/modules/spells/spell_types/self/mime_vow.dm b/code/modules/spells/spell_types/self/mime_vow.dm index bd666786b962..18744a9a4e5b 100644 --- a/code/modules/spells/spell_types/self/mime_vow.dm +++ b/code/modules/spells/spell_types/self/mime_vow.dm @@ -31,6 +31,6 @@ cast_on.log_message("broke [cast_on.p_their()] vow of silence.", LOG_GAME) cast_on.add_mood_event("vow", /datum/mood_event/broken_vow) REMOVE_TRAIT(cast_on, TRAIT_MIMING, "[type]") - var/datum/job/mime/mime_job = SSjob.GetJob(JOB_MIME) + var/datum/job/mime/mime_job = SSjob.get_job(JOB_MIME) mime_job.total_positions += 1 qdel(src) diff --git a/code/modules/unit_tests/anonymous_themes.dm b/code/modules/unit_tests/anonymous_themes.dm index a58f60eab3bd..99cd4f82802a 100644 --- a/code/modules/unit_tests/anonymous_themes.dm +++ b/code/modules/unit_tests/anonymous_themes.dm @@ -12,7 +12,7 @@ client.prefs.write_preference(GLOB.preference_entries[/datum/preference/name/real_name], "Prefs Biddle") - human.apply_prefs_job(client, SSjob.GetJobType(/datum/job/assistant)) + human.apply_prefs_job(client, SSjob.get_job_type(/datum/job/assistant)) TEST_ASSERT_NOTEQUAL(human.real_name, "Prefs Biddle", "apply_prefs_job didn't randomize human name with an anonymous theme") TEST_ASSERT_EQUAL(client.prefs.read_preference(/datum/preference/name/real_name), "Prefs Biddle", "Anonymous theme overrode original prefs") diff --git a/code/modules/unit_tests/dummy_spawn.dm b/code/modules/unit_tests/dummy_spawn.dm index ed359f962b7d..6ec59eaaa506 100644 --- a/code/modules/unit_tests/dummy_spawn.dm +++ b/code/modules/unit_tests/dummy_spawn.dm @@ -16,7 +16,7 @@ /datum/unit_test/dummy_spawn_outfit/Run() var/mob/living/carbon/human/dummy/lad = allocate(/mob/living/carbon/human/dummy) for(var/datum/job/one_two_three as anything in subtypesof(/datum/job)) - var/datum/job/can_you_hear_this = SSjob.GetJobType(one_two_three) + var/datum/job/can_you_hear_this = SSjob.get_job_type(one_two_three) if(!can_you_hear_this) log_test("\tJob type [one_two_three] could not be retrieved from SSjob") continue diff --git a/code/modules/unit_tests/dynamic_ruleset_sanity.dm b/code/modules/unit_tests/dynamic_ruleset_sanity.dm index 552959220c80..b42b34f99df3 100644 --- a/code/modules/unit_tests/dynamic_ruleset_sanity.dm +++ b/code/modules/unit_tests/dynamic_ruleset_sanity.dm @@ -1,24 +1,13 @@ -/// Verifies that roundstart dynamic rulesets are setup properly without external configuration. -/datum/unit_test/dynamic_roundstart_ruleset_sanity - -/datum/unit_test/dynamic_roundstart_ruleset_sanity/Run() - for (var/datum/dynamic_ruleset/roundstart/ruleset as anything in subtypesof(/datum/dynamic_ruleset/roundstart)) - var/has_scaling_cost = initial(ruleset.scaling_cost) - var/is_lone = initial(ruleset.flags) & (LONE_RULESET | HIGH_IMPACT_RULESET) - - if (has_scaling_cost && is_lone) - TEST_FAIL("[ruleset] has a scaling_cost, but is also a lone/highlander ruleset.") - else if (!has_scaling_cost && !is_lone) - TEST_FAIL("[ruleset] has no scaling cost, but is also not a lone/highlander ruleset.") +/// Verifies that dynamic rulesets are setup properly without external configuration. +/datum/unit_test/dynamic_ruleset_sanity +/datum/unit_test/dynamic_ruleset_sanity/Run() for (var/datum/dynamic_ruleset/midround/ruleset as anything in subtypesof(/datum/dynamic_ruleset/midround)) - if(initial(ruleset.abstract_type) == ruleset) - if(initial(ruleset.weight)) - TEST_FAIL("[ruleset] is abstract and should never run, it should also have 0 weight set.") + if(!initial(ruleset.config_tag)) continue - var/midround_ruleset_style = initial(ruleset.midround_ruleset_style) - if (midround_ruleset_style != MIDROUND_RULESET_STYLE_HEAVY && midround_ruleset_style != MIDROUND_RULESET_STYLE_LIGHT) - TEST_FAIL("[ruleset] has an invalid midround_ruleset_style, it should be MIDROUND_RULESET_STYLE_HEAVY or MIDROUND_RULESET_STYLE_LIGHT") + var/midround_ruleset_style = initial(ruleset.midround_type) + if (midround_ruleset_style != HEAVY_MIDROUND && midround_ruleset_style != LIGHT_MIDROUND) + TEST_FAIL("[ruleset] has an invalid midround_ruleset_style, it should be HEAVY_MIDROUND or LIGHT_MIDROUND") /// Verifies that dynamic rulesets have unique antag_flag. /datum/unit_test/dynamic_unique_antag_flags @@ -27,13 +16,17 @@ var/list/known_antag_flags = list() for (var/datum/dynamic_ruleset/ruleset as anything in subtypesof(/datum/dynamic_ruleset)) - if (isnull(initial(ruleset.antag_datum))) + if(!initial(ruleset.config_tag)) continue - var/antag_flag = initial(ruleset.antag_flag) + var/antag_flag = initial(ruleset.pref_flag) + // null antag flag is valid for rulesets with no associated preferecne if (isnull(antag_flag)) - TEST_FAIL("[ruleset] has a null antag_flag!") + // however if you set preview_antag_datum, it is assumed you do have a preference, and thus should have a flag + if (initial(ruleset.preview_antag_datum)) + TEST_FAIL("[ruleset] sets preview_antag_datum, but has no pref_flag! \ + If you want to use a preview antag datum, you must set a pref_flag.") continue if (antag_flag in known_antag_flags) diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_clownoperativemidround.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_clownoperativemidround.png new file mode 100644 index 0000000000000000000000000000000000000000..d61c10e74871c60a24a3573afb470553fdf7996a GIT binary patch literal 2004 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 1) - if(!length(item.restricted_roles) && !length(item.restricted_species)) - uplink_items += item - continue - if((uplink_handler.assigned_role in item.restricted_roles) || (uplink_handler.assigned_species in item.restricted_species)) - uplink_items += item - continue - uplink_handler.extra_purchasable += create_uplink_sales(uplink_sale_count, /datum/uplink_category/discounts, 1, uplink_items) - -/// The Advanced Traitor antagonist datum. -/datum/antagonist/traitor/advanced - name = "Advanced Traitor" // Changed to just "Traitor" on spawn, but can be changed by the player. - ui_name = null // We have our own UI - hijack_speed = 0.5 // Edited to ([starting_tc] / 40). - employer = "The Syndicate" // Can be changed by the player. - give_objectives = FALSE - should_give_codewords = FALSE - give_uplink = FALSE - finalize_antag = FALSE - can_assign_self_objectives = FALSE - /// List of objectives traitors can get in addition to the base ones - var/static/list/traitor_objectives = list( - "exile" = /datum/objective/exile, - ) - /// Typepath of what advanced antag datum gets instantiated to this antag. - var/advanced_antag_path = /datum/advanced_antag_datum/traitor - -/datum/antagonist/traitor/advanced/New(give_objectives = FALSE) - . = ..() - src.give_objectives = FALSE - -/datum/antagonist/traitor/advanced/on_gain() - if(!GLOB.admin_objective_list) - generate_admin_objective_list() - - var/list/objectives_to_choose = GLOB.admin_objective_list.Copy() - objectives_to_choose -= blacklisted_similar_objectives - objectives_to_choose += traitor_objectives - - set_name_on_add() - - linked_advanced_datum = new advanced_antag_path(src) - linked_advanced_datum.setup_advanced_antag() - linked_advanced_datum.possible_objectives = objectives_to_choose - return ..() - -/datum/antagonist/traitor/advanced/proc/set_name_on_add() - name = "Traitor" - -/// Greet the antag with big menacing text. -/datum/antagonist/traitor/advanced/greet() - linked_advanced_datum.greet_message(owner.current) - -/datum/antagonist/traitor/advanced/roundend_report() - var/list/result = list() - - result += printplayer(owner) - result += "[owner] was a/an [linked_advanced_datum.name][employer? " employed by [employer]":""]." - if(linked_advanced_datum.backstory) - result += "[owner]'s backstory was the following:
[linked_advanced_datum.backstory]" - - var/TC_uses = 0 - var/uplink_true = FALSE - var/purchases = "" - - if(linked_advanced_datum.finalized) - LAZYINITLIST(GLOB.uplink_purchase_logs_by_key) - var/datum/uplink_purchase_log/log = GLOB.uplink_purchase_logs_by_key[owner.key] - if(log) - uplink_true = TRUE - TC_uses = log.total_spent - purchases += log.generate_render(FALSE) - - if(LAZYLEN(linked_advanced_datum.our_goals)) - result += "[owner]'s objectives:" - var/count = 1 - for(var/datum/advanced_antag_goal/goal as anything in linked_advanced_datum.our_goals) - result += goal.get_roundend_text(count++) - if(linked_advanced_datum.finalized) - result += "
They were afforded [linked_advanced_datum.starting_points] tc to accomplish these tasks." - - if(uplink_true && linked_advanced_datum.finalized) - var/uplink_text = span_bold("(used [TC_uses] TC)") - uplink_text += "[purchases]" - result += uplink_text - else - result += span_bold("
The [name] never obtained their uplink!") - - return result.Join("
") - -/datum/antagonist/traitor/advanced/roundend_report_footer() - return "
And thus ends another story on board [station_name()]." - -/// The advanced antag datum for traitor. -/datum/advanced_antag_datum/traitor - name = "Advanced Traitor" - employer = "The Syndicate" - starting_points = 8 - /// Hijack speed = (starting telecrystals * this modifier) - var/hijack_speed_modifier = 0.025 - -/datum/advanced_antag_datum/traitor/modify_antag_points() - var/datum/component/uplink/made_uplink = linked_antagonist.owner.find_syndicate_uplink() - if(!made_uplink) - return - - starting_points = get_antag_points_from_goals() - made_uplink.uplink_handler.telecrystals = starting_points - made_uplink.uplink_handler.progression_points = 1000 * (starting_points + 1) - linked_antagonist.hijack_speed = (starting_points * hijack_speed_modifier) // 20 tc traitor = 0.5 (default traitor hijack speed) - -/datum/advanced_antag_datum/traitor/get_antag_points_from_goals() - var/finalized_starting_tc = ADV_TRAITOR_INITIAL_TC - for(var/datum/advanced_antag_goal/goal as anything in our_goals) - finalized_starting_tc += (goal.intensity * ADV_TRAITOR_TC_PER_INTENSITY) - - return min(finalized_starting_tc, ADV_TRAITOR_MAX_TC) - -/datum/advanced_antag_datum/traitor/get_finalize_text() - return "Finalizing will send you your uplink to your preferred location with [get_antag_points_from_goals()] telecrystals. You can still edit your goals after finalizing!" - -/datum/advanced_antag_datum/traitor/set_employer(employer) - . = ..() - var/datum/antagonist/traitor/our_traitor = linked_antagonist - our_traitor.employer = src.employer diff --git a/maplestation_modules/code/modules/antagonists/infiltrator/advanced_infiltrator.dm b/maplestation_modules/code/modules/antagonists/infiltrator/advanced_infiltrator.dm deleted file mode 100644 index bea7c5a48b39..000000000000 --- a/maplestation_modules/code/modules/antagonists/infiltrator/advanced_infiltrator.dm +++ /dev/null @@ -1,26 +0,0 @@ -/// -- Advanced Antag Datum for Infiltrator. -- -/datum/advanced_antag_datum/traitor/infiltrator - name = "Advanced Infiltrator" - hijack_speed_modifier = 0.05 - -/datum/advanced_antag_datum/traitor/infiltrator/get_finalize_text() - return "Finalizing will grant you an uplink implant with [get_antag_points_from_goals()] telecrystals. You can still edit your goals after finalizing!" - -/datum/advanced_antag_datum/traitor/infiltrator/greet_message(mob/antagonist) - to_chat(antagonist, span_alertsyndie("You are an [name]!")) - antagonist.playsound_local(get_turf(antagonist), 'maplestation_modules/sound/radiodrum.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) - addtimer(CALLBACK(src, PROC_REF(greet_message_two), antagonist), 3 SECONDS) - -/datum/advanced_antag_datum/traitor/infiltrator/greet_message_two(mob/antagonist) - to_chat(antagonist, span_danger("You are an agent sent to infiltrate [station_name()]! You can set your goals to whatever you think would make an interesting story or round. You have access to your goal panel via verb in your IC tab.")) - addtimer(CALLBACK(src, PROC_REF(greet_message_three), antagonist), 3 SECONDS) - -/datum/advanced_antag_datum/traitor/infiltrator/podspawn - name = "Advanced Infiltrator (Pod spawn)" - -/datum/advanced_antag_datum/traitor/infiltrator/podspawn/get_finalize_text() - return "Finalizing will grant you an uplink implant with [get_antag_points_from_goals()] telecrystals, and will drop pod you into a random maintenance room on the station. You can still edit your goals after finalizing!" - -/datum/advanced_antag_datum/traitor/infiltrator/podspawn/greet_message_two(mob/antagonist) - to_chat(antagonist, span_danger("You are an agent preparing to infiltrate [station_name()]! You can set your goals to whatever you think would make an interesting story or round. Finalizing your goals will drop you into a random maintenance room on the station. You have access to your goal panel via verb in your IC tab.")) - addtimer(CALLBACK(src, PROC_REF(greet_message_three), antagonist), 3 SECONDS) diff --git a/maplestation_modules/code/modules/antagonists/infiltrator/infiltrator.dm b/maplestation_modules/code/modules/antagonists/infiltrator/infiltrator.dm deleted file mode 100644 index f4450ea84c69..000000000000 --- a/maplestation_modules/code/modules/antagonists/infiltrator/infiltrator.dm +++ /dev/null @@ -1,175 +0,0 @@ -/// -- Infiltrator antag. Advanced traitors but they get some nukops gear in their uplink. -- -/datum/antagonist/traitor/advanced/intiltrator - name = "Infiltrator" - ui_name = null - hijack_speed = 1 - advanced_antag_path = /datum/advanced_antag_datum/traitor/infiltrator - antag_hud_name = "synd" - -/datum/antagonist/traitor/advanced/intiltrator/on_gain() - equip_infiltrator_outfit() - return ..() - -/datum/antagonist/traitor/advanced/intiltrator/set_name_on_add() - name = "Infiltrator" - -/datum/antagonist/traitor/advanced/intiltrator/apply_innate_effects(mob/living/mob_override) - var/mob/living/living_antag = mob_override || owner.current - add_team_hud(living_antag) - living_antag.faction |= ROLE_SYNDICATE - owner.set_assigned_role(SSjob.GetJobType(/datum/job/infiltrator)) - owner.special_role = ROLE_INFILTRATOR - -/datum/antagonist/traitor/advanced/intiltrator/remove_innate_effects(mob/living/mob_override) - var/mob/living/living_antag = mob_override || owner.current - living_antag.faction -= ROLE_SYNDICATE - owner.set_assigned_role(SSjob.GetJobType(/datum/job/unassigned)) - owner.special_role = null - -/datum/antagonist/traitor/advanced/intiltrator/on_removal() - var/obj/item/implant/uplink/infiltrator/infiltrator_implant = locate() in owner.current - var/obj/item/implant/weapons_auth/weapons_implant = locate() in owner.current - if(infiltrator_implant) - to_chat(owner.current, span_danger("You hear a whirring in your ear as your [infiltrator_implant.name] deactivates and becomes non-functional!")) - qdel(infiltrator_implant) - if(weapons_implant) - to_chat(owner.current, span_danger("You hear a click in your [prob(50) ? "right" : "left"] arm as your [weapons_implant.name] deactivates and becomes non-functional!")) - qdel(weapons_implant) - - var/obj/item/organ/brain/their_brain = owner.current.get_organ_slot(ORGAN_SLOT_BRAIN) - if(their_brain) - var/obj/item/skillchip/disk_verifier/disky_chip = locate() in their_brain - if(disky_chip) - their_brain.remove_skillchip(disky_chip) - qdel(disky_chip) - - return ..() - -/datum/antagonist/traitor/advanced/intiltrator/roundend_report() - var/list/result = list() - - result += printplayer(owner) - result += "[owner] was a/an [linked_advanced_datum.name], sent to infiltrate [station_name()][employer? ", employed by [employer]":""]." - if(linked_advanced_datum.backstory) - result += "[owner]'s backstory was the following:
[linked_advanced_datum.backstory]" - - var/TC_uses = 0 - var/uplink_true = FALSE - var/purchases = "" - - if(linked_advanced_datum.finalized) - LAZYINITLIST(GLOB.uplink_purchase_logs_by_key) - var/datum/uplink_purchase_log/log = GLOB.uplink_purchase_logs_by_key[owner.key] - if(log) - uplink_true = TRUE - TC_uses = log.total_spent - purchases += log.generate_render(FALSE) - - if(LAZYLEN(linked_advanced_datum.our_goals)) - result += "[owner]'s objectives:" - var/count = 1 - for(var/datum/advanced_antag_goal/goal as anything in linked_advanced_datum.our_goals) - result += goal.get_roundend_text(count++) - if(linked_advanced_datum.finalized) - result += "
They were afforded [linked_advanced_datum.starting_points] tc to accomplish these tasks." - - if(uplink_true && linked_advanced_datum.finalized) - var/uplink_text = span_bold("(used [TC_uses] TC)") - uplink_text += "[purchases]" - result += uplink_text - else - result += span_bold("
The [name] never obtained their uplink!") - - return result.Join("
") - -/datum/antagonist/traitor/advanced/intiltrator/roundend_report_footer() - return "
And thus ends another attempted Syndicate infiltration on board [station_name()]." - -/datum/antagonist/traitor/advanced/intiltrator/finalize_antag() - var/mob/living/carbon/human/traitor_mob = owner.current - if (!istype(traitor_mob)) - return - - var/obj/item/implant/uplink/infiltrator/infiltrator_implant = new() - var/obj/item/implant/weapons_auth/weapons_implant = new() - infiltrator_implant.implant(traitor_mob, traitor_mob, TRUE, TRUE) - weapons_implant.implant(traitor_mob, traitor_mob, TRUE, TRUE) - if(!silent) - to_chat(traitor_mob, span_boldnotice("[employer] has cunningly implanted you with an [infiltrator_implant.name] to assist in your infiltration. You can trigger the uplink to stealthily access it.")) - to_chat(traitor_mob, span_boldnotice("[employer] has wisely implanted you with a [weapons_implant.name] to allow you to use syndicate weaponry. You can now fire weapons with Syndicate firing pins.")) - - // MELBERT TODO; Fix this upstream - var/datum/component/uplink/made = infiltrator_implant.GetComponent(/datum/component/uplink) - made?.uplink_handler?.uplink_flag = infiltrator_implant.uplink_flag - - handle_uplink() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) - -/datum/antagonist/traitor/advanced/intiltrator/proc/equip_infiltrator_outfit(strip = FALSE) - if(!ishuman(owner.current)) - return FALSE - var/mob/living/carbon/human/human_current = owner.current - if(strip) - human_current.delete_equipment() - human_current.equipOutfit(/datum/outfit/syndicate_infiltrator) - return TRUE - -/datum/antagonist/traitor/advanced/intiltrator/pod_spawn - name = "Infiltrator (Pod spawn)" - advanced_antag_path = /datum/advanced_antag_datum/traitor/infiltrator/podspawn - -/datum/antagonist/traitor/advanced/intiltrator/pod_spawn/finalize_antag() - . = ..() - SStgui.close_user_uis(owner, linked_advanced_datum) - if(!spawn_infiltrator_pod(owner.current, silent)) - message_admins("Cannot pod-spawn [owner.current] as infiltrator - they have not been launched anywhere. Consider sending them via pod manually.") - -/datum/antagonist/traitor/advanced/intiltrator/pod_spawn/proc/spawn_infiltrator_pod(mob/living/infiltrator, silent) - if(!istype(infiltrator)) - return FALSE - - // Spawns us somewhere in maintenance via drop pod. - var/turf/picked_turf = find_maintenance_spawn(TRUE) - if(isnull(picked_turf)) - return FALSE - - var/obj/structure/closet/supplypod/infiltrator_pod = new(null, /datum/pod_style/syndicate) - infiltrator_pod.explosionSize = list(0, 0, 1, 1) - infiltrator_pod.bluespace = TRUE - - var/turf/randomized_picked_turf = find_obstruction_free_location(3, picked_turf) || picked_turf - - if(!silent) - to_chat(infiltrator, span_alertwarning("\nYou are being deployed via drop pod into [get_area_name(randomized_picked_turf, TRUE)] to begin your infiltration of [station_name()].")) - infiltrator.forceMove(infiltrator_pod) - return new /obj/effect/pod_landingzone(randomized_picked_turf, infiltrator_pod) - -/// infiltrator uplink implant. -/obj/item/implant/uplink/infiltrator - name = "infiltrator uplink implant" - uplink_flag = UPLINK_INFILTRATOR - -/datum/outfit/syndicate_infiltrator - name = "Syndicate Infiltrator" - - uniform = /obj/item/clothing/under/syndicate - shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/color/black - back = /obj/item/storage/backpack/fireproof - ears = /obj/item/radio/headset - id = /obj/item/card/id/advanced/black - id_trim = /datum/id_trim/syndicom/infiltrator - skillchips = list(/obj/item/skillchip/disk_verifier) - backpack_contents = list(/obj/item/storage/box/survival/syndie = 1, /obj/item/knife/combat/survival) - -/datum/outfit/syndicate_infiltrator/post_equip(mob/living/carbon/human/human_equipper, visualsOnly) - . = ..() - var/obj/item/card/id/worn_id = human_equipper.wear_id - if(worn_id) - worn_id.registered_name = human_equipper.real_name - worn_id.update_label() - worn_id.update_icon() - -/datum/id_trim/syndicom/infiltrator - assignment = "Syndicate Infiltrator" - access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) diff --git a/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm b/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm index bcd404bd6177..83e9f2af7d19 100644 --- a/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm +++ b/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm @@ -247,7 +247,7 @@ GLOBAL_LIST_INIT_TYPED(all_loadout_categories, /datum/loadout_category, init_loa if(job_greyscale_palettes[jobtype]) return job_greyscale_palettes[jobtype] - var/datum/job/job = SSjob.GetJobType(jobtype) + var/datum/job/job = SSjob.get_job_type(jobtype) if(job.department_for_prefs && job_greyscale_palettes[job.department_for_prefs]) return job_greyscale_palettes[job.department_for_prefs] diff --git a/tgstation.dme b/tgstation.dme index 9201bd23fa6c..381575432f1e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -88,7 +88,6 @@ #include "code\__DEFINES\door.dm" #include "code\__DEFINES\drone.dm" #include "code\__DEFINES\dye_keys.dm" -#include "code\__DEFINES\dynamic.dm" #include "code\__DEFINES\economy.dm" #include "code\__DEFINES\electrified_buckle.dm" #include "code\__DEFINES\events.dm" @@ -719,16 +718,15 @@ #include "code\controllers\subsystem\wardrobe.dm" #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\wiremod_composite.dm" +#include "code\controllers\subsystem\dynamic\__dynamic_defines.dm" +#include "code\controllers\subsystem\dynamic\_dynamic_ruleset.dm" +#include "code\controllers\subsystem\dynamic\_dynamic_tier.dm" #include "code\controllers\subsystem\dynamic\dynamic.dm" -#include "code\controllers\subsystem\dynamic\dynamic_hijacking.dm" -#include "code\controllers\subsystem\dynamic\dynamic_logging.dm" -#include "code\controllers\subsystem\dynamic\dynamic_midround_rolling.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_latejoin.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_midround.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_roundstart.dm" -#include "code\controllers\subsystem\dynamic\dynamic_unfavorable_situation.dm" -#include "code\controllers\subsystem\dynamic\ruleset_picking.dm" +#include "code\controllers\subsystem\dynamic\dynamic_admin.dm" +#include "code\controllers\subsystem\dynamic\dynamic_ruleset_latejoin.dm" +#include "code\controllers\subsystem\dynamic\dynamic_ruleset_midround.dm" +#include "code\controllers\subsystem\dynamic\dynamic_ruleset_roundstart.dm" +#include "code\controllers\subsystem\dynamic\dynamic_testing.dm" #include "code\controllers\subsystem\movement\ai_movement.dm" #include "code\controllers\subsystem\movement\cliff_falling.dm" #include "code\controllers\subsystem\movement\hyperspace_drift.dm" @@ -3242,7 +3240,6 @@ #include "code\modules\antagonists\obsessed\obsessed.dm" #include "code\modules\antagonists\paradox_clone\paradox_clone.dm" #include "code\modules\antagonists\pirate\pirate.dm" -#include "code\modules\antagonists\pirate\pirate_event.dm" #include "code\modules\antagonists\pirate\pirate_gangs.dm" #include "code\modules\antagonists\pirate\pirate_outfits.dm" #include "code\modules\antagonists\pirate\pirate_roles.dm" @@ -3253,6 +3250,7 @@ #include "code\modules\antagonists\revenant\revenant_blight.dm" #include "code\modules\antagonists\revolution\enemy_of_the_state.dm" #include "code\modules\antagonists\revolution\revolution.dm" +#include "code\modules\antagonists\revolution\revolution_handler.dm" #include "code\modules\antagonists\santa\santa.dm" #include "code\modules\antagonists\sentient_creature\sentient_creature.dm" #include "code\modules\antagonists\separatist\nation_creation.dm" @@ -3956,9 +3954,9 @@ #include "code\modules\events\camerafailure.dm" #include "code\modules\events\carp_migration.dm" #include "code\modules\events\communications_blackout.dm" -#include "code\modules\events\creep_awakening.dm" #include "code\modules\events\disease_outbreak.dm" #include "code\modules\events\dust.dm" +#include "code\modules\events\dynamic_tweak.dm" #include "code\modules\events\earthquake.dm" #include "code\modules\events\electrical_storm.dm" #include "code\modules\events\fake_virus.dm" @@ -3979,7 +3977,6 @@ #include "code\modules\events\scrubber_overflow.dm" #include "code\modules\events\shuttle_catastrophe.dm" #include "code\modules\events\shuttle_insurance.dm" -#include "code\modules\events\spider_infestation.dm" #include "code\modules\events\stray_cargo.dm" #include "code\modules\events\supermatter_surge.dm" #include "code\modules\events\tram_malfunction.dm" @@ -3998,20 +3995,8 @@ #include "code\modules\events\anomaly\anomaly_pyro.dm" #include "code\modules\events\anomaly\anomaly_vortex.dm" #include "code\modules\events\ghost_role\_ghost_role.dm" -#include "code\modules\events\ghost_role\abductor.dm" -#include "code\modules\events\ghost_role\alien_infestation.dm" -#include "code\modules\events\ghost_role\blob.dm" -#include "code\modules\events\ghost_role\changeling_event.dm" -#include "code\modules\events\ghost_role\fugitive_event.dm" -#include "code\modules\events\ghost_role\morph_event.dm" -#include "code\modules\events\ghost_role\nightmare.dm" #include "code\modules\events\ghost_role\operative.dm" -#include "code\modules\events\ghost_role\revenant_event.dm" #include "code\modules\events\ghost_role\sentience.dm" -#include "code\modules\events\ghost_role\sentient_disease.dm" -#include "code\modules\events\ghost_role\slaughter_event.dm" -#include "code\modules\events\ghost_role\space_dragon.dm" -#include "code\modules\events\ghost_role\space_ninja.dm" #include "code\modules\events\holiday\easter.dm" #include "code\modules\events\holiday\halloween.dm" #include "code\modules\events\holiday\vday.dm" @@ -4325,7 +4310,6 @@ #include "code\modules\jobs\job_types\antagonists\abductor_agent.dm" #include "code\modules\jobs\job_types\antagonists\abductor_scientist.dm" #include "code\modules\jobs\job_types\antagonists\abductor_solo.dm" -#include "code\modules\jobs\job_types\antagonists\clown_operative.dm" #include "code\modules\jobs\job_types\antagonists\lone_operative.dm" #include "code\modules\jobs\job_types\antagonists\morph.dm" #include "code\modules\jobs\job_types\antagonists\nightmare.dm" @@ -4562,6 +4546,7 @@ #include "code\modules\mapping\space_management\zlevel_manager.dm" #include "code\modules\meteors\meteor_changeling.dm" #include "code\modules\meteors\meteor_dark_matteor.dm" +#include "code\modules\meteors\meteor_mode_controller.dm" #include "code\modules\meteors\meteor_spawning.dm" #include "code\modules\meteors\meteor_types.dm" #include "code\modules\meteors\meteor_waves.dm" diff --git a/tgui/packages/tgui/interfaces/DynamicAdmin.tsx b/tgui/packages/tgui/interfaces/DynamicAdmin.tsx new file mode 100644 index 000000000000..d494c561e0f2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DynamicAdmin.tsx @@ -0,0 +1,739 @@ +import { useState } from 'react'; +import { + BlockQuote, + Box, + Button, + Dropdown, + Flex, + Input, + LabeledList, + NoticeBox, + Section, + Stack, + Tabs, + Tooltip, +} from 'tgui-core/components'; +import { BooleanLike } from 'tgui-core/react'; +import { createSearch } from 'tgui-core/string'; + +import { useBackend } from '../backend'; +import { Window } from '../layouts'; + +type RulesetCount = Record; + +type DynamicConfig = Record; + +type typePath = string; + +type Player = { + key: string; +}; + +type RulesetReport = RulesetType & { + index: number; + selected_players: Player[]; + hidden: BooleanLike; +}; + +type RulesetType = { + name: string; + id: string; + typepath: typePath; + admin_disabled: BooleanLike; +}; + +type Data = { + current_tier?: { + number: number; + name: string; + }; + ruleset_count?: RulesetCount; + full_config?: DynamicConfig; + queued_rulesets: RulesetReport[]; + active_rulesets: RulesetReport[]; + all_rulesets: Record; + time_until_lights: number; + time_until_heavies: number; + time_until_latejoins: number; + time_until_next_midround: number; + time_until_next_latejoin: number; + failed_latejoins: number; + light_midround_chance: number; + heavy_midround_chance: number; + latejoin_chance: number; + roundstarted: BooleanLike; + config_even_enabled: BooleanLike; + light_chance_maxxed: BooleanLike; + heavy_chance_maxxed: BooleanLike; + latejoin_chance_maxxed: BooleanLike; + next_dynamic_tick: number; + antag_events_enabled: BooleanLike; +}; + +function formatTime(seconds: number): string { + seconds /= 10; + if (seconds < 0) { + return 'never'; + } + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = Math.round(seconds % 60); + + return `${hours}h ${minutes}m ${secs}s`; +} + +function getPlayerString(players: Player[]): string { + if (players.length === 0) { + return 'No one'; + } else if (players.length === 1) { + return players[0].key; + } else if (players.length === 2) { + return `${players[0].key} and ${players[1].key}`; + } + let playerString = ''; + for (let i = 0; i < players.length; i++) { + playerString += players[i].key; + if (i < players.length - 1) { + playerString += ', '; + } + if (i === players.length - 2) { + playerString += 'and '; + } + } + return playerString; +} + +function readableRulesesetCategory(ruleset_category: string): string { + // Replace underlines with spaces and auto-capitalize first letter of every word + return ruleset_category + .replace(/_/g, ' ') + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +const StatusPanel = () => { + const { data, act } = useBackend(); + const { + current_tier, + ruleset_count, + time_until_lights, + time_until_heavies, + time_until_latejoins, + time_until_next_midround, + time_until_next_latejoin, + failed_latejoins, + light_midround_chance, + heavy_midround_chance, + latejoin_chance, + roundstarted, + light_chance_maxxed, + heavy_chance_maxxed, + latejoin_chance_maxxed, + next_dynamic_tick, + } = data; + + if (!current_tier) { + return ( + + + + + + ); + } + + return ( + + + + {current_tier.number} ({current_tier.name}) + + {!roundstarted && ( + + )} + + {ruleset_count && + Object.entries(ruleset_count).map(([name, count]) => ( + + + {count} + {(name !== 'roundstart' || !roundstarted) && ( + + + + + + ) : ( + <> + + + + + {time_until_next_midround > 0 + ? formatTime(time_until_next_midround) + : `Next dynamic tick (${formatTime(next_dynamic_tick)})`} + + + + + + + + + + + + + {light_midround_chance}% + + + + + + + + + + )} + {time_until_heavies > 0 ? ( + + + + {formatTime(time_until_heavies)} + + + + + + + ) : ( + <> + + + + + {time_until_next_midround > 0 + ? formatTime(time_until_next_midround) + : `Next dynamic tick (${formatTime(next_dynamic_tick)})`} + + + + + + + + + + + + + {heavy_midround_chance}% + + + + + + + + + + )} + {time_until_latejoins > 0 ? ( + + + + {formatTime(time_until_latejoins)} + + + + + + + ) : ( + <> + + + + + {time_until_next_latejoin + ? formatTime(time_until_next_latejoin) + : 'Next latejoin'} + + + + + + + + + + + + + {latejoin_chance}% ({failed_latejoins} failed attempts) + + + + + + + + + + )} + + ); +}; + +// This just reports the entire config +const ConfigPanel = () => { + const { data, act } = useBackend(); + const { full_config } = data; + // Config given to us is basically just a big json object + // Future TODO make this a whole functional config editor + + if (!full_config) { + return ( + + No config loaded - refer to repo defaults for reference. + + ); + } + + const configKeys = Object.keys(full_config); + const [shownConfig, setShownConfig] = useState(configKeys[0]); + + return ( + + + + This is the current config read by the dynamic system when running + rulesets. If you want to edit these values temporarily, you can do so + via View Variables. (Note: Editing tier configs has no effect after + roundstart) + + + + setShownConfig(key)} + /> + + +
+ + {Object.entries(full_config[shownConfig]).map( + ([config_name, config]) => ( + + + {JSON.stringify(config)} + + + ), + )} + +
+
+
+ ); +}; + +// This is where you can see queued rulesets, active rulesets, and trigger new ones +const RulesetsPanel = () => { + const { data, act } = useBackend(); + const { all_rulesets, queued_rulesets, active_rulesets, roundstarted } = data; + + const any_admin_disabled = Object.values(all_rulesets).some((ruleset_list) => + ruleset_list.some((ruleset) => ruleset.admin_disabled), + ); + const all_admin_disabled = Object.values(all_rulesets).every((ruleset_list) => + ruleset_list.every((ruleset) => ruleset.admin_disabled), + ); + + const [searchText, setSearchText] = useState(''); + const searchFilter = createSearch( + searchText, + (ruleset: RulesetType) => ruleset.name, + ); + + return ( + + +
+ + {queued_rulesets.length === 0 ? ( + + No rulesets queued. + + ) : ( + queued_rulesets.map((ruleset) => ( + +
+
+ + +
+ + {active_rulesets.length === 0 ? ( + + No rulesets active. + + ) : ( + active_rulesets.map((ruleset) => ( + + + + {ruleset.name} ({ruleset.id}) + + + + act('hide_ruleset', { + ruleset_index: ruleset.index, + }) + } + /> + + +
+ Selected: {getPlayerString(ruleset.selected_players)} +
+
+ )) + )} +
+
+
+ + +
+ + + + + } + > + + {Object.entries(all_rulesets).map( + ([ruleset_category, ruleset_list]) => ( + + + +

{readableRulesesetCategory(ruleset_category)}

+
+ {ruleset_list + .filter(searchFilter) + .sort((a, b) => (a.name > b.name ? 1 : -1)) + .map((ruleset, index) => ( + + + {ruleset_category === 'roundstart' || + ruleset_category === 'latejoin' ? ( + +
+
+
+ ); +}; + +enum TABS { + Status = 'Status', + Rulesets = 'Rulesets', + Config = 'Config', +} + +export const DynamicAdmin = () => { + const { act, data } = useBackend(); + const { config_even_enabled, antag_events_enabled } = data; + + // disable config tab if config_even_enabled is false + const tabs_filtered = Object.keys(TABS).filter( + (tab) => tab !== TABS.Config || config_even_enabled, + ); + + const [currentTab, setCurrentTab] = useState(tabs_filtered[0]); + + let componentShown; + + switch (currentTab) { + case TABS.Status: + componentShown = ; + break; + case TABS.Config: + componentShown = ; + break; + case TABS.Rulesets: + componentShown = ; + break; + default: + componentShown = ; + } + + return ( + + +
+ act('toggle_antag_events')} + > + Antag Events + + + + } + > + + {tabs_filtered.map((tab) => ( + setCurrentTab(tab)} + > + {tab} + + ))} + + {componentShown} +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DynamicTester.tsx b/tgui/packages/tgui/interfaces/DynamicTester.tsx new file mode 100644 index 000000000000..af86fff955bd --- /dev/null +++ b/tgui/packages/tgui/interfaces/DynamicTester.tsx @@ -0,0 +1,145 @@ +import { useState } from 'react'; +import { + Box, + NumberInput, + Section, + Stack, + Table, + Tabs, + Tooltip, +} from 'tgui-core/components'; + +import { useBackend } from '../backend'; +import { Window } from '../layouts'; + +type RulesetReport = { + name: string; + weight: number; + max_candidates: number; + min_candidates: number; + comment: string | null; +}; + +type Data = { + tier: number; + num_players: number; + roundstart_ruleset_report: RulesetReport[]; + midround_ruleset_report: RulesetReport[]; +}; + +enum TABS { + Roundstart = 'Roundstart', + Midrounds = 'Midrounds', +} + +export const DynamicTester = () => { + const { data, act } = useBackend(); + const { + tier, + num_players, + roundstart_ruleset_report, + midround_ruleset_report, + } = data; + + const [tab, setTab] = useState(Object.keys(TABS)[0]); + + const ruleset_report = + tab === TABS.Roundstart + ? roundstart_ruleset_report + : midround_ruleset_report; + + const total_weight = ruleset_report.reduce( + (acc, report) => acc + report.weight, + 0, + ); + const rulesets_with_weight_percentages = ruleset_report.map((report) => { + const percentage = Math.round((report.weight / total_weight) * 100); + return { + ...report, + percentage, + }; + }); + + return ( + + +
+ + + Tier: + act('set_tier', { tier: e })} + /> + + + Number of players: + act('set_num_players', { num_players: e })} + /> + + + + + {Object.keys(TABS).map((tabName) => ( + setTab(tabName)} + > + {tabName} + + ))} + + + + + + Ruleset + Weight + Odds + Max Antags + Min Antags + + {rulesets_with_weight_percentages.map((report) => ( + + {report.comment ? ( + + + + {report.name} + + + + ) : ( + {report.name} + )} + {report.weight} + {report.percentage}% + {report.max_candidates} + {report.min_candidates} + + ))} +
+
+
+
+
+
+ ); +}; From 0f46bf86f0b3ea734224f24af332f1b7455edc26 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 9 Jul 2025 07:16:25 -0500 Subject: [PATCH 05/21] Adds config for roundstart blue alert (#92015) ## About The Pull Request Adds a config option `roundstart_blue_alert` which determines if the station is put on blue alert on roundstart. **Greenshifts** are unaffected, they will still have a unique announcement indicating it's a greenshift **Roundstart reports** are unaffected, they will be sent regardless. ## Why It's Good For The Game Some servers put more player agency on command to control the report level, some servers re-theme the levels so blue is more of an involved thing, some servers put more weight on the current level, etc. Giving the option of staying on green until someone decides to up it is neat I guess. Before doing this I tried to find when this was even added - to see what the justification was and make sure I wasn't violating it - and wasn't successful. R4407 had "`Security Level Elevated`" in their reports but didn't have security levels implemented, so it was purely fluff. ## Changelog :cl: Melbert config: Adds "roundstart_blue_alert", allowing you to disable roundstart blue alert. Defaults to "on". /:cl: --- code/controllers/configuration/entries/game_options.dm | 3 +++ code/datums/communications.dm | 9 ++++++++- config/game_options.txt | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 873c37317dc2..c15811d3360b 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -164,6 +164,9 @@ /datum/config_entry/string/alert_delta default = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." +/datum/config_entry/flag/roundstart_blue_alert + default = TRUE + /datum/config_entry/flag/revival_pod_plants /datum/config_entry/number/revival_brain_life diff --git a/code/datums/communications.dm b/code/datums/communications.dm index af5021feec4f..df60c17db85a 100644 --- a/code/datums/communications.dm +++ b/code/datums/communications.dm @@ -117,7 +117,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n SSstation.announcer.get_rand_report_sound(), color_override = "green", ) - else + else if(CONFIG_GET(flag/roundstart_blue_alert)) if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE) SSsecurity_level.set_level(SEC_LEVEL_BLUE, announce = FALSE) priority_announce( @@ -127,6 +127,13 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n ANNOUNCER_INTERCEPT, color_override = SSsecurity_level.current_security_level.announcement_color, ) + else + priority_announce( + "A summary of the station's situation has been copied and printed to all communications consoles.", + "Security Report", + SSstation.announcer.get_rand_report_sound(), + ) + #endif return . diff --git a/config/game_options.txt b/config/game_options.txt index ca298a397ceb..8d27aa7ffcec 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -81,6 +81,11 @@ ALERT_DELTA Destruction of the station is imminent. All crew are instructed to o ## The total weight of station goals possible for a round (allows multiple goals) STATION_GOAL_BUDGET 1 +## If TRUE / 1, station is raised to blue alert at roundstart. +## If FALSE / 0, station remains at green alert. +## Roundstart command report and greendshift announcements are unaffected. +ROUNDSTART_BLUE_ALERT 1 + ## GAME MODES ### ## Uncomment to not send a roundstart intercept report. Gamemodes may override this. From f2859ad04d875fffac63398d18b81b5ef5b427a8 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 12 Feb 2026 18:11:24 -0600 Subject: [PATCH 06/21] Make it go --- code/__DEFINES/antagonists.dm | 2 + .../signals/signals_mob/signals_mob_carbon.dm | 2 +- code/__DEFINES/dcs/signals/signals_object.dm | 2 + code/__DEFINES/jobs.dm | 2 +- code/__DEFINES/logging.dm | 1 + code/__DEFINES/preferences.dm | 1 + code/__DEFINES/role_preferences.dm | 1 + code/__HELPERS/game.dm | 2 +- code/__HELPERS/logging/game.dm | 3 + code/__HELPERS/mobs.dm | 14 +- .../configuration/entries/general.dm | 4 + .../subsystem/dynamic/dynamic_admin.dm | 4 +- .../dynamic/dynamic_ruleset_midround.dm | 35 ++- .../dynamic/dynamic_ruleset_roundstart.dm | 5 +- .../subsystem/dynamic/dynamic_testing.dm | 2 +- code/controllers/subsystem/job.dm | 112 ++++++--- code/controllers/subsystem/polling.dm | 227 ++++++++++++------ code/controllers/subsystem/ticker.dm | 2 +- code/datums/brain_damage/split_personality.dm | 7 +- code/datums/candidate_poll.dm | 40 ++- code/datums/communications.dm | 4 +- .../datums/components/ghost_direct_control.dm | 23 +- code/datums/diseases/transformation.dm | 10 +- code/datums/elements/uplink_reimburse.dm | 8 +- code/game/gamemodes/objective_items.dm | 4 +- .../effects/anomalies/anomalies_ectoplasm.dm | 2 +- .../anomalies/anomalies_pyroclastic.dm | 7 +- code/game/objects/items/cards_ids.dm | 2 +- code/game/objects/items/dice.dm | 18 +- code/game/objects/items/dna_probe.dm | 49 ---- code/game/objects/items/stacks/telecrystal.dm | 2 +- .../items/storage/boxes/clothes_boxes.dm | 5 - code/modules/admin/admin_verbs.dm | 2 +- code/modules/admin/antag_panel.dm | 6 +- code/modules/admin/fun_balloon.dm | 8 +- code/modules/admin/topic.dm | 6 - code/modules/admin/verbs/admin.dm | 2 +- code/modules/admin/verbs/admingame.dm | 6 +- code/modules/admin/verbs/ert.dm | 2 +- code/modules/admin/verbs/secrets.dm | 4 +- .../antagonists/_common/antag_datum.dm | 14 +- .../antagonists/_common/antag_spawner.dm | 61 +++-- .../antagonists/abductor/abductee/abductee.dm | 1 + code/modules/antagonists/abductor/abductor.dm | 7 +- .../abductor/machinery/experiment.dm | 2 +- .../battlecruiser/battlecruiser.dm | 2 +- code/modules/antagonists/blob/blob_antag.dm | 3 +- .../antagonists/brainwashing/brainwashing.dm | 2 +- code/modules/antagonists/brother/brother.dm | 7 +- .../antagonists/changeling/changeling.dm | 1 + code/modules/antagonists/clown_ops/clownop.dm | 1 - code/modules/antagonists/cult/cult.dm | 1 + code/modules/antagonists/cult/cult_comms.dm | 2 +- code/modules/antagonists/cult/runes.dm | 9 +- .../antagonists/disease/disease_datum.dm | 1 - .../antagonists/evil_clone/evil_clone.dm | 2 +- .../antagonists/heretic/heretic_antag.dm | 1 + .../antagonists/heretic/heretic_knowledge.dm | 5 +- .../antagonists/heretic/heretic_monsters.dm | 5 +- .../heretic/knowledge/flesh_lore.dm | 10 +- .../heretic/structures/lock_final.dm | 2 +- .../antagonists/hypnotized/hypnotized.dm | 2 +- .../antagonists/nukeop/datums/operative.dm | 2 +- .../nukeop/datums/operative_leader.dm | 7 - .../nukeop/datums/operative_team.dm | 30 +-- .../nukeop/equipment/nuclear_challenge.dm | 199 --------------- code/modules/antagonists/nukeop/outfits.dm | 23 ++ code/modules/antagonists/obsessed/obsessed.dm | 1 + .../antagonists/revolution/revolution.dm | 1 + .../revolution/revolution_handler.dm | 4 +- .../traitor/contractor/contract_teammate.dm | 29 --- .../antagonists/traitor/datum_traitor.dm | 8 +- code/modules/antagonists/wizard/wizard.dm | 2 +- code/modules/client/preferences/sounds.dm | 6 + .../clothing/head/mind_monkey_helmet.dm | 7 +- code/modules/events/ghost_role/operative.dm | 8 +- code/modules/events/ghost_role/sentience.dm | 6 +- .../events/ghost_role/sentient_disease.dm | 15 +- code/modules/events/holiday/vday.dm | 2 +- code/modules/events/holiday/xmas.dm | 17 +- code/modules/events/wizard/imposter.dm | 8 +- code/modules/jobs/job_types/_job.dm | 4 + .../jobs/job_types/head_of_personnel.dm | 39 +++ .../logging/categories/log_category_game.dm | 5 + .../ruins/spaceruin_code/caravanambush.dm | 8 - .../ruins/spaceruin_code/cyborgmothership.dm | 8 - .../modules/mob/dead/new_player/new_player.dm | 2 +- code/modules/mob/living/basic/bots/_bots.dm | 2 +- .../living/basic/bots/cleanbot/cleanbot.dm | 2 +- .../mob/living/basic/bots/medbot/medbot.dm | 2 +- .../living/basic/drone/extra_drone_types.dm | 2 +- .../living/basic/guardian/guardian_creator.dm | 27 ++- .../living/basic/guardian/guardian_verbs.dm | 9 +- .../space_fauna/revenant/revenant_items.dm | 16 +- code/modules/mob/living/living.dm | 7 +- .../mob/living/simple_animal/bot/bot.dm | 2 +- .../mob/living/simple_animal/bot/honkbot.dm | 2 +- .../mob/living/simple_animal/bot/mulebot.dm | 1 - .../hostile/mining_mobs/elites/elite.dm | 6 +- code/modules/mob/mob_helpers.dm | 7 +- code/modules/mob/transform_procs.dm | 9 +- .../computers/item/disks/virus_disk.dm | 2 +- code/modules/projectiles/projectile/magic.dm | 13 +- .../modules/shuttle/battlecruiser_starfury.dm | 2 +- .../shuttle_events/player_controlled.dm | 75 ------ code/modules/shuttle/white_ship.dm | 8 - code/modules/uplink/uplink_devices.dm | 4 + .../modules/uplink/uplink_items/contractor.dm | 27 +-- config/logging.txt | 3 + maplestation.dme | 65 +---- .../advanced_cult/advanced_cult.dm | 3 +- sound/ambience/antag/abductee.ogg | Bin 0 -> 169081 bytes sound/ambience/antag/attribution.txt | 11 + sound/ambience/antag/ayylien.ogg | Bin 0 -> 149054 bytes sound/ambience/antag/brainwashed.ogg | Bin 0 -> 327901 bytes sound/ambience/antag/hypnotized.ogg | Bin 0 -> 52524 bytes sound/misc/prompt1.ogg | Bin 0 -> 91247 bytes strings/antagonist_flavor/traitor_flavor.json | 8 + strings/traitor_flavor.json | 101 -------- 119 files changed, 687 insertions(+), 949 deletions(-) delete mode 100644 code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm delete mode 100644 code/modules/shuttle/shuttle_events/player_controlled.dm create mode 100644 sound/ambience/antag/abductee.ogg create mode 100644 sound/ambience/antag/attribution.txt create mode 100644 sound/ambience/antag/ayylien.ogg create mode 100644 sound/ambience/antag/brainwashed.ogg create mode 100644 sound/ambience/antag/hypnotized.ogg create mode 100644 sound/misc/prompt1.ogg delete mode 100644 strings/traitor_flavor.json diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 0cef14d35aed..b784f5de90b6 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -284,6 +284,8 @@ GLOBAL_LIST_INIT(human_invader_antagonists, list( #define DESTROY_AI_PROB(denominator) (100 / denominator) /// If the destroy AI objective doesn't roll, chance that we'll get a maroon instead. If this prob fails, they will get a generic assassinate objective instead. #define MAROON_PROB 30 +/// Probability that any job related objective is picked +#define JOB_PROB 40 /// How many telecrystals a normal traitor starts with #define TELECRYSTALS_DEFAULT 20 diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index dc2f80927a3e..72b5add560c4 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -122,7 +122,7 @@ ///Applied preferences to a human #define COMSIG_HUMAN_PREFS_APPLIED "human_prefs_applied" -///Whenever EquipRanked is called, called after job is set +///Whenever equip_ranked is called, called after job is set #define COMSIG_JOB_RECEIVED "job_received" ///from /datum/species/handle_fire. Called when the human is set on fire and burning clothes and stuff #define COMSIG_HUMAN_BURNING "human_burning" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 9a3d0055de0b..13783e8682d4 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -270,6 +270,8 @@ #define COMSIG_ITEM_ATTEMPT_TC_REIMBURSE "item_attempt_tc_reimburse" ///Called when a holoparasite/guardiancreator is used. #define COMSIG_TRAITOR_ITEM_USED(type) "traitor_item_used_[type]" +/// Called after an item is refunded +#define COMSIG_ITEM_TC_REIMBURSED "item_tc_reimbursed" // /obj/item/clothing signals diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 82f0d20945ab..397b420b6bda 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -191,7 +191,7 @@ #define JOB_ANNOUNCE_ARRIVAL (1<<0) /// Whether the mob is added to the crew manifest. #define JOB_CREW_MANIFEST (1<<1) -/// Whether the mob is equipped through SSjob.EquipRank() on spawn. +/// Whether the mob is equipped through SSjob.equip_rank() on spawn. #define JOB_EQUIP_RANK (1<<2) /// Whether the job is considered a regular crew member of the station. Equipment such as AI and cyborgs not included. #define JOB_CREW_MEMBER (1<<3) diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index 59f34a9555e0..b7f59978806a 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -148,6 +148,7 @@ #define LOG_CATEGORY_GAME_TRAITOR "game-traitor" #define LOG_CATEGORY_GAME_VOTE "game-vote" #define LOG_CATEGORY_GAME_WHISPER "game-whisper" +#define LOG_CATEGORY_GAME_GHOST_POLLS "game-ghost-polls" // HREF categories #define LOG_CATEGORY_HREF "href" diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index 45b77f74457d..d574c5817b60 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -87,6 +87,7 @@ #define TGUI_LAYOUT_LIST "list" //Job preferences levels +#define JP_ANY 0 #define JP_LOW 1 #define JP_MEDIUM 2 #define JP_HIGH 3 diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 4731186c0b01..d0f6d4aace73 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -77,6 +77,7 @@ #define ROLE_SLAUGHTER_DEMON "Slaughter Demon" #define ROLE_WIZARD_APPRENTICE "apprentice" #define ROLE_SYNDICATE_MONKEY "Syndicate Monkey Agent" +#define ROLE_CONTRACTOR_SUPPORT "Contractor Support Unit" //Spawner roles #define ROLE_ANCIENT_CREW "Ancient Crew" diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 3f0bfd1ca656..701763ea483f 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -184,7 +184,7 @@ //First we spawn a dude. var/mob/living/carbon/human/new_character = new//The mob being spawned. - SSjob.SendToLateJoin(new_character) + SSjob.send_to_late_join(new_character) ghost_player.client.prefs.safe_transfer_prefs_to(new_character) new_character.dna.update_dna_identity() diff --git a/code/__HELPERS/logging/game.dm b/code/__HELPERS/logging/game.dm index fd0fb533d312..c1103f73a3c2 100644 --- a/code/__HELPERS/logging/game.dm +++ b/code/__HELPERS/logging/game.dm @@ -30,3 +30,6 @@ /proc/log_vote(text, list/data) logger.Log(LOG_CATEGORY_GAME_VOTE, text, data) +/// Logging for ghost polls +/proc/log_ghost_poll(text, list/data) + logger.Log(LOG_CATEGORY_GAME_GHOST_POLLS, text, data) diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 8c83f6d5d432..9021d4a6b19c 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -485,17 +485,21 @@ GLOBAL_LIST_INIT(skin_tone_names, list( . += borg //Returns a list of AI's -/proc/active_ais(check_mind=FALSE, z = null) +/proc/active_ais(check_mind = FALSE, z = null, skip_syndicate = FALSE, only_syndicate = FALSE) . = list() for(var/mob/living/silicon/ai/ai as anything in GLOB.ai_list) if(ai.stat == DEAD) continue if(ai.control_disabled) continue - if(check_mind) - if(!ai.mind) - continue - if(z && !(z == ai.z) && (!is_station_level(z) || !is_station_level(ai.z))) //if a Z level was specified, AND the AI is not on the same level, AND either is off the station... + var/syndie_ai = istype(ai, /mob/living/silicon/ai/weak_syndie) + if(skip_syndicate && syndie_ai) + continue + if(only_syndicate && !syndie_ai) + continue + if(check_mind && !ai.mind) + continue + if(!isnull(z) && z != ai.z && (!is_station_level(z) || !is_station_level(ai.z))) //if a Z level was specified, AND the AI is not on the same level, AND either is off the station... continue . += ai diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index d4e05b06c345..fbc098d45436 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -118,6 +118,10 @@ /// log emotes /datum/config_entry/flag/log_emote +/// log ghost polling +/datum/config_entry/flag/log_ghost_poll + default = TRUE + /// log economy actions /datum/config_entry/flag/log_econ diff --git a/code/controllers/subsystem/dynamic/dynamic_admin.dm b/code/controllers/subsystem/dynamic/dynamic_admin.dm index 7c8cb3ecabd5..669781aeac40 100644 --- a/code/controllers/subsystem/dynamic/dynamic_admin.dm +++ b/code/controllers/subsystem/dynamic/dynamic_admin.dm @@ -15,7 +15,7 @@ ADMIN_VERB(dynamic_panel, R_ADMIN, "Dynamic Panel", "Mess with dynamic.", ADMIN_ /datum/dynamic_panel /datum/dynamic_panel/ui_state(mob/user) - return ADMIN_STATE(R_ADMIN) + return /*ADMIN_STATE(R_ADMIN)*/ GLOB.admin_state /datum/dynamic_panel/ui_close() qdel(src) @@ -185,7 +185,7 @@ ADMIN_VERB(dynamic_panel, R_ADMIN, "Dynamic Panel", "Mess with dynamic.", ADMIN_ var/list/tiers = list() for(var/datum/dynamic_tier/tier as anything in subtypesof(/datum/dynamic_tier)) tiers[initial(tier.name)] = tier - var/datum/dynamic_tier/picked = tgui_input_list(ui.user, "Pick a dynamic tier before the game starts", "Pick tier", tiers, ui_state = ADMIN_STATE(R_ADMIN)) + var/datum/dynamic_tier/picked = tgui_input_list(ui.user, "Pick a dynamic tier before the game starts", "Pick tier", tiers, ui_state = /*ADMIN_STATE(R_ADMIN)*/GLOB.admin_state) if(picked && !SSticker.HasRoundStarted()) SSdynamic.set_tier(tiers[picked]) message_admins("[key_name_admin(ui.user)] set the dynamic tier to [initial(picked.tier)].") diff --git a/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm b/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm index 40e46d4cf0e4..5c1f6a3dc540 100644 --- a/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm +++ b/code/controllers/subsystem/dynamic/dynamic_ruleset_midround.dm @@ -392,7 +392,7 @@ var/starting_points = OVERMIND_STARTING_POINTS /datum/dynamic_ruleset/midround/from_ghosts/blob/create_ruleset_body() - return new /mob/eye/blob(get_blobspawn(), starting_points) + return new /mob/camera/blob(get_blobspawn(), starting_points) /datum/dynamic_ruleset/midround/from_ghosts/blob/assign_role(datum/mind/candidate) return // everything is handled by blob new() @@ -425,7 +425,7 @@ max_antag_cap = 1 min_antag_cap = 1 repeatable_weight_decrease = 3 - signup_atom_appearance = /mob/living/basic/alien + signup_atom_appearance = /mob/living/simple_animal/hostile/alien /datum/dynamic_ruleset/midround/from_ghosts/xenomorph/New(list/dynamic_config) . = ..() @@ -492,7 +492,7 @@ candidate.add_antag_datum(/datum/antagonist/nightmare) candidate.current.set_species(/datum/species/shadow/nightmare) candidate.current.forceMove(find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE)) - playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1) + playsound(candidate.current, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) /datum/dynamic_ruleset/midround/from_ghosts/space_dragon name = "Space Dragon" @@ -522,7 +522,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/space_dragon/assign_role(datum/mind/candidate) candidate.add_antag_datum(/datum/antagonist/space_dragon) candidate.current.forceMove(find_space_spawn()) - playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1) + playsound(candidate.current, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) /datum/dynamic_ruleset/midround/from_ghosts/space_dragon/execute() . = ..() @@ -694,7 +694,7 @@ antag.original_ref = WEAKREF(good_version.mind) antag.setup_clone() - playsound(bad_version, 'sound/items/weapons/zapbang.ogg', 30, TRUE) + playsound(bad_version, 'sound/weapons/zapbang.ogg', 30, TRUE) bad_version.put_in_hands(new /obj/item/storage/toolbox/mechanical()) //so they dont get stuck in maints /datum/dynamic_ruleset/midround/from_ghosts/paradox_clone/proc/find_clone() @@ -711,6 +711,7 @@ return pick(possible_targets) return null +/* /datum/dynamic_ruleset/midround/from_ghosts/voidwalker name = "Voidwalker" config_tag = "Voidwalker" @@ -732,6 +733,7 @@ candidate.current.set_species(/datum/species/voidwalker) candidate.current.forceMove(find_space_spawn()) playsound(candidate.current, 'sound/effects/magic/ethereal_exit.ogg', 50, TRUE, -1) +*/ /datum/dynamic_ruleset/midround/from_ghosts/fugitives name = "Fugitive" @@ -782,7 +784,7 @@ var/list/hunter_backstories = list( HUNTER_PACK_BOUNTY, HUNTER_PACK_COPS, - HUNTER_PACK_MI13, + // HUNTER_PACK_MI13, HUNTER_PACK_PSYKER, HUNTER_PACK_RUSSIAN, RANDOM_BACKSTORY, @@ -823,7 +825,7 @@ HUNTER_PACK_RUSSIAN, HUNTER_PACK_BOUNTY, HUNTER_PACK_PSYKER, - HUNTER_PACK_MI13, + // HUNTER_PACK_MI13, ) . = ..() addtimer(CALLBACK(src, PROC_REF(check_spawn_hunters), hunter_backstory, 10 MINUTES), 1 MINUTES) @@ -833,7 +835,7 @@ equip_fugitive(candidate.current, team) if(length(selected_minds) > 1 && candidate == selected_minds[1]) equip_fugitive_leader(candidate.current) - playsound(candidate.current, 'sound/items/weapons/emitter.ogg', 50, TRUE) + playsound(candidate.current, 'sound/weapons/emitter.ogg', 50, TRUE) /datum/dynamic_ruleset/midround/from_ghosts/fugitives/proc/equip_fugitive(mob/living/carbon/human/fugitive, datum/team/fugitive/team) fugitive.set_species(/datum/species/human) @@ -886,8 +888,8 @@ ship = new /datum/map_template/shuttle/hunter/bounty if(HUNTER_PACK_PSYKER) ship = new /datum/map_template/shuttle/hunter/psyker - if(HUNTER_PACK_MI13) - ship = new/datum/map_template/shuttle/hunter/mi13_foodtruck + // if(HUNTER_PACK_MI13) + // ship = new/datum/map_template/shuttle/hunter/mi13_foodtruck var/x = rand(TRANSITIONEDGE, world.maxx - TRANSITIONEDGE - ship.width) var/y = rand(TRANSITIONEDGE, world.maxy - TRANSITIONEDGE - ship.height) @@ -936,10 +938,10 @@ announcement_text_list += "HEY, CAN YOU HEAR US? We're coming to your station. There's a bad guy down there, really bad guy. We need to arrest them." announcement_text_list += "We're also offering fortune telling services out of the front door if you have paying customers." announcement_title += "Fortune-Telling Entertainment Shuttle" - if(HUNTER_PACK_MI13) - announcement_text_list += "Illegal intrusion detected in the crew monitoring network. Central Command has been informed." - announcement_text_list += "Please report any suspicious individuals or behaviour to your local security team." - announcement_title += "Nanotrasen Intrusion Countermeasures Electronics" + // if(HUNTER_PACK_MI13) + // announcement_text_list += "Illegal intrusion detected in the crew monitoring network. Central Command has been informed." + // announcement_text_list += "Please report any suspicious individuals or behaviour to your local security team." + // announcement_title += "Nanotrasen Intrusion Countermeasures Electronics" if(!length(announcement_text_list)) announcement_text_list += "Unidentified ship detected near the station." @@ -992,7 +994,7 @@ /datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/create_ruleset_body() var/turf/spawnloc = find_space_spawn() . = new /mob/living/basic/demon/slaughter(spawnloc) - new /obj/effect/dummy/phased_mob/blood(spawnloc, .) + new /obj/effect/dummy/phased_mob(spawnloc, .) /datum/dynamic_ruleset/midround/from_ghosts/slaughter_demon/assign_role(datum/mind/candidate) return // handled by new() entirely @@ -1091,9 +1093,6 @@ /datum/dynamic_ruleset/midround/from_living/malf_ai/assign_role(datum/mind/candidate) candidate.add_antag_datum(/datum/antagonist/malf_ai) -/datum/dynamic_ruleset/midround/from_living/malf_ai/can_be_selected() - return ..() && !HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI) - /datum/dynamic_ruleset/midround/from_living/blob name = "Blob Infection" config_tag = "Blob Infection" diff --git a/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm b/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm index 1bdfe9476154..bf00d7f746b7 100644 --- a/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm +++ b/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm @@ -71,9 +71,6 @@ /datum/dynamic_ruleset/roundstart/malf_ai/assign_role(datum/mind/candidate) candidate.add_antag_datum(/datum/antagonist/malf_ai) -/datum/dynamic_ruleset/roundstart/malf_ai/can_be_selected() - return ..() && !HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI) - /datum/dynamic_ruleset/roundstart/blood_brother name = "Blood Brothers" config_tag = "Roundstart Blood Brothers" @@ -362,6 +359,7 @@ log_dynamic("[config_tag]: All headrevs were ineligible after the timer expired, and no replacements could be found. Ruleset canceled.") message_admins("[config_tag]: All headrevs were ineligible after the timer expired, and no replacements could be found. Ruleset canceled.") +/* /datum/dynamic_ruleset/roundstart/spies name = "Spies" config_tag = "Roundstart Spies" @@ -378,6 +376,7 @@ /datum/dynamic_ruleset/roundstart/spies/assign_role(datum/mind/candidate) candidate.add_antag_datum(/datum/antagonist/spy) +*/ /datum/dynamic_ruleset/roundstart/extended name = "Extended" diff --git a/code/controllers/subsystem/dynamic/dynamic_testing.dm b/code/controllers/subsystem/dynamic/dynamic_testing.dm index 5df1e3d29775..2b6730a0f0bc 100644 --- a/code/controllers/subsystem/dynamic/dynamic_testing.dm +++ b/code/controllers/subsystem/dynamic/dynamic_testing.dm @@ -41,7 +41,7 @@ ADMIN_VERB(dynamic_tester, R_DEBUG, "Dynamic Tester", "See dynamic probabilities update_reports() /datum/dynamic_tester/ui_state(mob/user) - return ADMIN_STATE(R_DEBUG) + return /*ADMIN_STATE(R_DEBUG)*/ GLOB.admin_state /datum/dynamic_tester/ui_close() qdel(src) diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index dab3ad8463d6..0b8e96f275d1 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -114,7 +114,7 @@ SUBSYSTEM_DEF(job) /datum/controller/subsystem/job/proc/set_overflow_role(new_overflow_role) var/datum/job/new_overflow = ispath(new_overflow_role) ? get_job_type(new_overflow_role) : get_job(new_overflow_role) if(!new_overflow) - JobDebug("Failed to set new overflow role: [new_overflow_role]") + job_debug("Failed to set new overflow role: [new_overflow_role]") CRASH("set_overflow_role failed | new_overflow_role: [isnull(new_overflow_role) ? "null" : new_overflow_role]") var/cap = CONFIG_GET(number/overflow_cap) @@ -132,7 +132,7 @@ SUBSYSTEM_DEF(job) if(!(initial(old_overflow.job_flags) & JOB_CANNOT_OPEN_SLOTS)) old_overflow.job_flags &= ~JOB_CANNOT_OPEN_SLOTS overflow_role = new_overflow.type - JobDebug("Overflow role set to : [new_overflow.type]") + job_debug("Overflow role set to : [new_overflow.type]") /datum/controller/subsystem/job/proc/setup_occupations() @@ -231,80 +231,83 @@ SUBSYSTEM_DEF(job) * * do_eligibility_checks - Set to TRUE to conduct all job eligibility tests and reject on failure. Set to FALSE if job eligibility has been tested elsewhere and they can be safely skipped. */ /datum/controller/subsystem/job/proc/assign_role(mob/dead/new_player/player, datum/job/job, latejoin = FALSE, do_eligibility_checks = TRUE) - JobDebug("Running AR, Player: [player], Job: [isnull(job) ? "null" : job], LateJoin: [latejoin]") + job_debug("Running AR, Player: [player], Job: [isnull(job) ? "null" : job], LateJoin: [latejoin]") if(!player?.mind || !job) - JobDebug("AR has failed, player has no mind or job is null, Player: [player], Rank: [isnull(job) ? "null" : job.type]") + job_debug("AR has failed, player has no mind or job is null, Player: [player], Rank: [isnull(job) ? "null" : job.type]") return FALSE if(do_eligibility_checks && (check_job_eligibility(player, job, "AR", add_job_to_log = TRUE) != JOB_AVAILABLE)) return FALSE - JobDebug("Player: [player] is now Rank: [job.title], JCP:[job.current_positions], JPL:[latejoin ? job.total_positions : job.spawn_positions]") + job_debug("Player: [player] is now Rank: [job.title], JCP:[job.current_positions], JPL:[latejoin ? job.total_positions : job.spawn_positions]") player.mind.set_assigned_role(job) unassigned -= player job.current_positions++ return TRUE -/datum/controller/subsystem/job/proc/find_occupation_candidates(datum/job/job, level) - JobDebug("Running FOC, Job: [job], Level: [job_priority_level_to_string(level)]") +/datum/controller/subsystem/job/proc/find_occupation_candidates(datum/job/job, level = 0) + job_debug("FOC: Now running, Job: [job], Level: [job_priority_level_to_string(level)]") var/list/candidates = list() for(var/mob/dead/new_player/player in unassigned) if(!player) - JobDebug("FOC player no longer exists.") + job_debug("FOC: Player no longer exists.") continue + if(!player.client) - JobDebug("FOC player client no longer exists, Player: [player]") + job_debug("FOC: Player client no longer exists, Player: [player]") continue + // Initial screening check. Does the player even have the job enabled, if they do - Is it at the correct priority level? var/player_job_level = player.client?.prefs.job_preferences[job.title] if(isnull(player_job_level)) - JobDebug("FOC player job not enabled, Player: [player]") + job_debug("FOC: Player job not enabled, Player: [player]") continue - else if(player_job_level != level) - JobDebug("FOC player job enabled at wrong level, Player: [player], TheirLevel: [job_priority_level_to_string(player_job_level)], ReqLevel: [job_priority_level_to_string(level)]") + + if(level && (player_job_level != level)) + job_debug("FOC: Player job enabled at wrong level, Player: [player], TheirLevel: [job_priority_level_to_string(player_job_level)], ReqLevel: [job_priority_level_to_string(level)]") continue - // This check handles its own output to JobDebug. + // This check handles its own output to job_debug. if(check_job_eligibility(player, job, "FOC", add_job_to_log = FALSE) != JOB_AVAILABLE) continue // They have the job enabled, at this priority level, with no restrictions applying to them. - JobDebug("FOC pass, Player: [player], Level: [job_priority_level_to_string(level)]") + job_debug("FOC: Player eligible, Player: [player], Level: [job_priority_level_to_string(level)]") candidates += player return candidates - JobDebug("GRJ Giving random job, Player: [player]") +/datum/controller/subsystem/job/proc/give_random_job(mob/dead/new_player/player) + job_debug("GRJ: Giving random job, Player: [player]") . = FALSE for(var/datum/job/job as anything in shuffle(joinable_occupations)) if(QDELETED(player)) - JobDebug("GRJ player is deleted, aborting") + job_debug("GRJ: Player is deleted, aborting") break if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1) - JobDebug("GRJ job lacks spawn positions to be eligible, Player: [player], Job: [job]") + job_debug("GRJ: Job lacks spawn positions to be eligible, Player: [player], Job: [job]") continue if(istype(job, get_job_type(overflow_role))) // We don't want to give him assistant, that's boring! - JobDebug("GRJ skipping overflow role, Player: [player], Job: [job]") + job_debug("GRJ: Skipping overflow role, Player: [player], Job: [job]") continue if(job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) //If you want a command position, select it! - JobDebug("GRJ skipping command role, Player: [player], Job: [job]") + job_debug("GRJ: Skipping command role, Player: [player], Job: [job]") continue - // This check handles its own output to JobDebug. + // This check handles its own output to job_debug. if(check_job_eligibility(player, job, "GRJ", add_job_to_log = TRUE) != JOB_AVAILABLE) continue - if(AssignRole(player, job, do_eligibility_checks = FALSE)) - JobDebug("GRJ Random job given, Player: [player], Job: [job]") + if(assign_role(player, job, do_eligibility_checks = FALSE)) + job_debug("GRJ: Random job given, Player: [player], Job: [job]") return TRUE - JobDebug("GRJ Player eligible but AssignRole failed, Player: [player], Job: [job]") - + job_debug("GRJ: Player eligible but assign_role failed, Player: [player], Job: [job]") /datum/controller/subsystem/job/proc/reset_occupations() - JobDebug("Occupations reset.") + job_debug("Occupations reset.") for(var/mob/dead/new_player/player as anything in GLOB.new_player_list) if(!player?.mind) continue @@ -317,6 +320,61 @@ SUBSYSTEM_DEF(job) set_overflow_role(overflow_role) return +/* + * Forces a random Head of Staff role to be assigned to a random eligible player. + * Returns TRUE if a player was selected and assigned the role. FALSE otherwise. + */ +/datum/controller/subsystem/job/proc/force_one_head_assignment() + var/datum/job_department/command_department = get_department_type(/datum/job_department/command) + if(!command_department) + return FALSE + for(var/level in level_order) + for(var/datum/job/job as anything in command_department.department_jobs) + if((job.current_positions >= job.total_positions) && job.total_positions != -1) + continue + var/list/candidates = find_occupation_candidates(job, level) + if(!candidates.len) + continue + var/mob/dead/new_player/candidate = pick(candidates) + // Eligibility checks done as part of find_occupation_candidates. + if(assign_role(candidate, job, do_eligibility_checks = FALSE)) + return TRUE + return FALSE + + +/** + * Attempts to fill out all possible head positions for players with that job at a a given job priority level. + * Returns the number of Head positions assigned. + * + * Arguments: + * * level - One of the JP_LOW, JP_MEDIUM, JP_HIGH or JP_ANY defines. Attempts to find candidates with head jobs at that priority only. + */ +/datum/controller/subsystem/job/proc/fill_all_head_positions_at_priority(level) + . = 0 + var/datum/job_department/command_department = get_department_type(/datum/job_department/command) + + if(!command_department) + return . + + for(var/datum/job/job as anything in command_department.department_jobs) + if((job.current_positions >= job.total_positions) && job.total_positions != -1) + continue + + var/list/candidates = find_occupation_candidates(job, level) + if(!candidates.len) + continue + + var/mob/dead/new_player/candidate = pick(candidates) + + // Eligibility checks done as part of find_occupation_candidates() above. + if(!assign_role(candidate, job, do_eligibility_checks = FALSE)) + continue + + .++ + + if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1) + job_debug("JOBS: Command Job is now full, Job: [job], Positions: [job.current_positions], Limit: [job.spawn_positions]") + /// Attempts to fill out all available AI positions. /datum/controller/subsystem/job/proc/fill_ai_positions() var/datum/job/ai_job = get_job(JOB_AI) @@ -330,7 +388,7 @@ SUBSYSTEM_DEF(job) if(candidates.len) var/mob/dead/new_player/candidate = pick(candidates) // Eligibility checks done as part of find_occupation_candidates - if(AssignRole(candidate, get_job_type(/datum/job/ai), do_eligibility_checks = FALSE)) + if(assign_role(candidate, get_job_type(/datum/job/ai), do_eligibility_checks = FALSE)) break @@ -532,7 +590,7 @@ SUBSYSTEM_DEF(job) job.announce_job(equipping) if(player_client?.holder) - if(CONFIG_GET(flag/auto_deadmin_always) || (player_client.prefs?.toggles & DEADMIN_ALWAYS)) + if(/*CONFIG_GET(flag/auto_deadmin_always) || */(player_client.prefs?.toggles & DEADMIN_ALWAYS)) player_client.holder.auto_deadmin() else handle_auto_deadmin_roles(player_client, job.title) diff --git a/code/controllers/subsystem/polling.dm b/code/controllers/subsystem/polling.dm index eecda9b08295..27433c242f8a 100644 --- a/code/controllers/subsystem/polling.dm +++ b/code/controllers/subsystem/polling.dm @@ -2,7 +2,7 @@ SUBSYSTEM_DEF(polling) name = "Polling" flags = SS_BACKGROUND | SS_NO_INIT wait = 1 SECONDS - runlevels = RUNLEVEL_GAME + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME /// List of polls currently ongoing, to be checked on next fire() var/list/datum/candidate_poll/currently_polling /// Number of polls performed since the start @@ -27,12 +27,16 @@ SUBSYSTEM_DEF(polling) * * ignore_category: Optional, A poll category. If a candidate has this category in their ignore list, they won't be polled. * * flash_window: If TRUE, the candidate's window will flash when they're polled. * * list/group: A list of candidates to poll. - * * pic_source: Optional, An /atom or an /image to display on the poll alert. + * * alert_pic: Optional, An /atom or an /image to display on the poll alert. + * * jump_target: An /atom to teleport/jump to, if alert_pic is an /atom defaults to that. * * role_name_text: Optional, A string to display in logging / the (default) question. If null, the role name will be used. * * list/custom_response_messages: Optional, A list of strings to use as responses to the poll. If null, the default responses will be used. see __DEFINES/polls.dm for valid keys to use. * * start_signed_up: If TRUE, all candidates will start signed up for the poll, making it opt-out rather than opt-in. + * * amount_to_pick: Lets you pick candidates and return a single mob or list of mobs that were chosen. + * * chat_text_border_icon: Object or path to make an icon of to decorate the chat announcement. + * * announce_chosen: Whether we should announce the chosen candidates in chat. This is ignored unless amount_to_pick is greater than 0. * - * Returns a list of all mobs who signed up for the poll. + * Returns a list of all mobs who signed up for the poll, OR, in the case that amount_to_pick is equal to 1 the singular mob/null if no available candidates. */ /datum/controller/subsystem/polling/proc/poll_candidates( question, @@ -42,28 +46,36 @@ SUBSYSTEM_DEF(polling) ignore_category = null, flash_window = TRUE, list/group = null, - pic_source, + alert_pic, + jump_target, role_name_text, list/custom_response_messages, start_signed_up = FALSE, + amount_to_pick = 0, + chat_text_border_icon, + announce_chosen = TRUE, ) - RETURN_TYPE(/list/mob) if(group.len == 0) - return list() + return if(role && !role_name_text) role_name_text = role if(role_name_text && !question) - question = "Do you want to play as [full_capitalize(role_name_text)]?" + question = "Do you want to play as [span_notice(role_name_text)]?" if(!question) question = "Do you want to play as a special role?" - log_game("Polling candidates [role_name_text ? "for [role_name_text]" : "\"[question]\""] for [DisplayTimeText(poll_time)] seconds") + log_ghost_poll("Candidate poll started.", data = list( + "role name" = role_name_text, + "poll question" = question, + "poll duration" = DisplayTimeText(poll_time), + )) // Start firing total_polls++ - var/jumpable = isatom(pic_source) ? pic_source : null + if(isnull(jump_target) && isatom(alert_pic)) + jump_target = alert_pic - var/datum/candidate_poll/new_poll = new(role_name_text, question, poll_time, ignore_category, jumpable, custom_response_messages) + var/datum/candidate_poll/new_poll = new(role_name_text, question, poll_time, ignore_category, jump_target, custom_response_messages) LAZYADD(currently_polling, new_poll) var/category = "[new_poll.poll_key]_poll_alert" @@ -72,7 +84,7 @@ SUBSYSTEM_DEF(polling) if(!candidate_mob.client) continue // Universal opt-out for all players. - if((!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles))) + if(!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles)) continue // Opt-out for admins whom are currently adminned. if((!candidate_mob.client.prefs.read_preference(/datum/preference/toggle/ghost_roles_as_admin)) && candidate_mob.client.holder) @@ -122,80 +134,146 @@ SUBSYSTEM_DEF(polling) // Image to display var/image/poll_image - if(pic_source) - if(!ispath(pic_source)) - var/atom/the_pic_source = pic_source - var/old_layer = the_pic_source.layer - var/old_plane = the_pic_source.plane - the_pic_source.plane = poll_alert_button.plane - the_pic_source.layer = FLOAT_LAYER - poll_alert_button.add_overlay(the_pic_source) - the_pic_source.layer = old_layer - the_pic_source.plane = old_plane - else - poll_image = image(pic_source, layer = FLOAT_LAYER) + if(ispath(alert_pic, /atom) || isatom(alert_pic)) + poll_image = new /mutable_appearance(alert_pic) + poll_image.pixel_z = 0 + else if(!isnull(alert_pic)) + poll_image = alert_pic else - // Just use a generic image - poll_image = image('icons/effects/effects.dmi', icon_state = "static", layer = FLOAT_LAYER) + poll_image = image('icons/effects/effects.dmi', icon_state = "static") if(poll_image) + poll_image.layer = FLOAT_LAYER poll_image.plane = poll_alert_button.plane poll_alert_button.add_overlay(poll_image) // Chat message var/act_jump = "" - if(isatom(pic_source) && isobserver(candidate_mob)) - act_jump = "\[Teleport\]" - var/act_signup = "\[[start_signed_up ? "Opt out" : "Sign Up"]\]" + var/custom_link_style_start = "" + var/custom_link_style_end = "style='color:DodgerBlue;font-weight:bold;-dm-text-outline: 1px black'" + if(isatom(alert_pic) && isobserver(candidate_mob)) + act_jump = "[custom_link_style_start]\[Teleport\]" + var/act_signup = "[custom_link_style_start]\[[start_signed_up ? "Opt out" : "Sign Up"]\]" var/act_never = "" if(ignore_category) - act_never = "\[Never For This Round\]" + act_never = "[custom_link_style_start]\[Never For This Round\]" + + if(!duplicate_message_check(alert_poll)) //Only notify people once. They'll notice if there are multiple and we don't want to spam people. + + // ghost poll prompt sound handling + // var/polling_sound_volume = candidate_mob.client?.prefs.read_preference(/datum/preference/numeric/sound_ghost_poll_prompt_volume) + SEND_SOUND(candidate_mob, sound('sound/misc/prompt1.ogg', volume = 50)) + + var/surrounding_icon + if(chat_text_border_icon) + var/image/surrounding_image + if(!ispath(chat_text_border_icon)) + var/mutable_appearance/border_image = chat_text_border_icon + surrounding_image = border_image + else + surrounding_image = image(chat_text_border_icon) + surrounding_icon = icon2html(surrounding_image, candidate_mob, extra_classes = "bigicon") + var/final_message = examine_block("[surrounding_icon] [span_ooc(question)] [surrounding_icon]\n[act_jump] [act_signup] [act_never]") + to_chat(candidate_mob, final_message) - SEND_SOUND(candidate_mob, 'sound/misc/notice2.ogg') - to_chat(candidate_mob, span_boldnotice(examine_block("Now looking for candidates [role_name_text ? "to play as \an [role_name_text]." : "\"[question]\""] [act_jump] [act_signup] [act_never]"))) // Start processing it so it updates visually the timer START_PROCESSING(SSprocessing, poll_alert_button) // Sleep until the time is up UNTIL(new_poll.finished) - return new_poll.signed_up - -/datum/controller/subsystem/polling/proc/poll_ghost_candidates(question, role, check_jobban, poll_time = 30 SECONDS, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text) + if(!amount_to_pick) + return new_poll.signed_up + if (!length(new_poll.signed_up)) + return null + for(var/pick in 1 to amount_to_pick) + // There may be less people signed up than amount_to_pick + // pick_n_take returns the default return value of null if passed an empty list, so just break in that case rather than adding null to the list. + if(!length(new_poll.signed_up)) + break + new_poll.chosen_candidates += pick_n_take(new_poll.signed_up) + if(announce_chosen) + new_poll.announce_chosen(group) + if(new_poll.chosen_candidates.len == 1) + var/chosen_one = pick(new_poll.chosen_candidates) + return chosen_one + return new_poll.chosen_candidates + +/datum/controller/subsystem/polling/proc/poll_ghost_candidates( + question, + role, + check_jobban, + poll_time = 30 SECONDS, + ignore_category = null, + flashwindow = TRUE, + alert_pic, + jump_target, + role_name_text, + list/custom_response_messages, + start_signed_up = FALSE, + amount_to_pick = 0, + chat_text_border_icon, + announce_chosen = TRUE, +) var/list/candidates = list() if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE)) - return candidates - + return for(var/mob/dead/observer/ghost_player in GLOB.player_list) candidates += ghost_player - return poll_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, candidates, pic_source, role_name_text) - -/datum/controller/subsystem/polling/proc/poll_ghost_candidates_for_mob(question, role, check_jobban, poll_time = 30 SECONDS, mob/target_mob, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text) - var/static/list/mob/currently_polling_mobs = list() - - if(currently_polling_mobs.Find(target_mob)) - return list() - - currently_polling_mobs += target_mob +#ifdef TESTING + for(var/mob/dude in GLOB.player_list) + candidates |= dude +#endif - var/list/possible_candidates = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, pic_source, role_name_text) + return poll_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, candidates, alert_pic, jump_target, role_name_text, custom_response_messages, start_signed_up, amount_to_pick, chat_text_border_icon, announce_chosen) - currently_polling_mobs -= target_mob - if(!target_mob || QDELETED(target_mob) || !target_mob.loc) - return list() - - return possible_candidates - -/datum/controller/subsystem/polling/proc/poll_ghost_candidates_for_mobs(question, role, check_jobban, poll_time = 30 SECONDS, list/mobs, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text) - var/list/candidate_list = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, pic_source, role_name_text) - - for(var/mob/potential_mob as anything in mobs) - if(QDELETED(potential_mob) || !potential_mob.loc) - mobs -= potential_mob - - if(!length(mobs)) +/datum/controller/subsystem/polling/proc/poll_ghosts_for_target( + question, + role, + check_jobban, + poll_time = 30 SECONDS, + atom/movable/checked_target, + ignore_category = null, + flashwindow = TRUE, + alert_pic, + jump_target, + role_name_text, + list/custom_response_messages, + start_signed_up = FALSE, + chat_text_border_icon, + announce_chosen = TRUE, +) + var/static/list/atom/movable/currently_polling_targets = list() + if(currently_polling_targets.Find(checked_target)) + return + currently_polling_targets += checked_target + var/mob/chosen_one = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, alert_pic, jump_target, role_name_text, custom_response_messages, start_signed_up, amount_to_pick = 1, chat_text_border_icon = chat_text_border_icon, announce_chosen = announce_chosen) + currently_polling_targets -= checked_target + if(!checked_target || QDELETED(checked_target) || !checked_target.loc) + return null + return chosen_one + +/datum/controller/subsystem/polling/proc/poll_ghosts_for_targets( + question, + role, + check_jobban, + poll_time = 30 SECONDS, + list/checked_targets, + ignore_category = null, + flashwindow = TRUE, + alert_pic, + jump_target, + role_name_text, + list/custom_response_messages, + start_signed_up = FALSE, + chat_text_border_icon, +) + var/list/candidate_list = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, alert_pic, jump_target, role_name_text, custom_response_messages, start_signed_up, chat_text_border_icon = chat_text_border_icon) + for(var/atom/movable/potential_target as anything in checked_targets) + if(QDELETED(potential_target) || !potential_target.loc) + checked_targets -= potential_target + if(!length(checked_targets)) return list() - return candidate_list /datum/controller/subsystem/polling/proc/is_eligible(mob/potential_candidate, role, check_jobban, the_ignore_category) @@ -211,7 +289,7 @@ SUBSYSTEM_DEF(polling) return FALSE if(check_jobban) - if(is_banned_from(potential_candidate.ckey, list(check_jobban, ROLE_SYNDICATE))) + if(is_banned_from(potential_candidate.ckey, list(ROLE_SYNDICATE) + check_jobban)) return FALSE return TRUE @@ -221,18 +299,20 @@ SUBSYSTEM_DEF(polling) // Trim players who aren't eligible anymore var/length_pre_trim = length(finishing_poll.signed_up) finishing_poll.trim_candidates() - log_game("Candidate poll [finishing_poll.role ? "for [finishing_poll.role]" : "\"[finishing_poll.question]\""] finished. [length_pre_trim] players signed up, [length(finishing_poll.signed_up)] after trimming") + + log_ghost_poll("Candidate poll completed.", data = list( + "role name" = finishing_poll.role, + "poll question" = finishing_poll.question, + "signed up count" = length_pre_trim, + "trimmed candidate count" = length(finishing_poll.signed_up) + )) + finishing_poll.finished = TRUE // Take care of updating the remaining screen alerts if a similar poll is found, or deleting them. if(length(finishing_poll.alert_buttons)) - var/polls_of_same_type_left = FALSE - for(var/datum/candidate_poll/running_poll as anything in currently_polling) - if(running_poll.poll_key == finishing_poll.poll_key && running_poll.time_left() > 0) - polls_of_same_type_left = TRUE - break for(var/atom/movable/screen/alert/poll_alert/alert as anything in finishing_poll.alert_buttons) - if(polls_of_same_type_left) + if(duplicate_message_check(finishing_poll)) alert.update_stacks_overlay() else alert.owner.clear_alert("[finishing_poll.poll_key]_poll_alert") @@ -241,12 +321,19 @@ SUBSYSTEM_DEF(polling) QDEL_IN(finishing_poll, 0.5 SECONDS) /datum/controller/subsystem/polling/stat_entry(msg) - msg += "Active: [length(currently_polling)] | Total: [total_polls]" + msg = "\n Active: [length(currently_polling)] | Total: [total_polls]" var/datum/candidate_poll/soonest_to_complete = get_next_poll_to_finish() if(soonest_to_complete) msg += " | Next: [DisplayTimeText(soonest_to_complete.time_left())] ([length(soonest_to_complete.signed_up)] candidates)" return ..() +///Is there a multiple of the given event type running right now? +/datum/controller/subsystem/polling/proc/duplicate_message_check(datum/candidate_poll/poll_to_check) + for(var/datum/candidate_poll/running_poll as anything in currently_polling) + if((running_poll.poll_key == poll_to_check.poll_key && running_poll != poll_to_check) && running_poll.time_left() > 0) + return TRUE + return FALSE + /datum/controller/subsystem/polling/proc/get_next_poll_to_finish() var/lowest_time_left = INFINITY var/next_poll_to_finish diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 47309b5b4212..896153e0d099 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -556,7 +556,7 @@ SUBSYSTEM_DEF(ticker) continue var/datum/job/player_assigned_role = new_player_living.mind.assigned_role if(player_assigned_role.job_flags & JOB_EQUIP_RANK) - SSjob.EquipRank(new_player_living, player_assigned_role, new_player_mob.client) + SSjob.equip_rank(new_player_living, player_assigned_role, new_player_mob.client) player_assigned_role.after_roundstart_spawn(new_player_living, new_player_mob.client) if(picked_spare_id_candidate == new_player_mob) captainless = FALSE diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm index db7eace1af27..589996a025f6 100644 --- a/code/datums/brain_damage/split_personality.dm +++ b/code/datums/brain_damage/split_personality.dm @@ -212,10 +212,9 @@ /datum/brain_trauma/severe/split_personality/brainwashing/get_ghost() set waitfor = FALSE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as [owner.real_name]'s brainwashed mind?", poll_time = 7.5 SECONDS, target_mob = stranger_backseat, pic_source = owner, role_name_text = "brainwashed mind") - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - stranger_backseat.key = C.key + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger("[owner.real_name]'s")] brainwashed mind?", poll_time = 7.5 SECONDS, checked_target = stranger_backseat, alert_pic = owner, role_name_text = "brainwashed mind") + if(chosen_one) + stranger_backseat.key = chosen_one.key else qdel(src) diff --git a/code/datums/candidate_poll.dm b/code/datums/candidate_poll.dm index e1baf19c5cc0..a2fcf62676ed 100644 --- a/code/datums/candidate_poll.dm +++ b/code/datums/candidate_poll.dm @@ -28,8 +28,16 @@ POLL_RESPONSE_TOO_LATE_TO_UNREGISTER = "It's too late to unregister yourself, selection has already begun!", POLL_RESPONSE_UNREGISTERED = "You have been unregistered as a candidate for %ROLE%. You can sign up again before the poll ends.", ) + var/list/chosen_candidates = list() -/datum/candidate_poll/New(polled_role, polled_question, poll_duration, poll_ignoring_category, poll_jumpable, list/custom_response_messages) +/datum/candidate_poll/New( + polled_role, + polled_question, + poll_duration, + poll_ignoring_category, + poll_jumpable, + list/custom_response_messages = list(), +) role = polled_role question = polled_question duration = poll_duration @@ -37,9 +45,9 @@ jump_to_me = poll_jumpable signed_up = list() time_started = world.time - poll_key = "[question]_[role || "0"]" - for(var/custom_message in custom_response_messages) - response_messages[custom_message] = custom_response_messages[custom_message] + poll_key = "[question]_[role ? role : "0"]" + if(custom_response_messages.len) + response_messages = custom_response_messages for(var/individual_message in response_messages) response_messages[individual_message] = replacetext(response_messages[individual_message], "%ROLE%", role) return ..() @@ -70,6 +78,13 @@ return FALSE signed_up += candidate + + log_ghost_poll("Player [candidate.key] signed candidate poll", data = list( + "player key" = candidate.key, + "role name" = role, + "poll question" = question, + )) + if(!silent) to_chat(candidate, span_notice(response_messages[POLL_RESPONSE_SIGNUP])) // Sign them up for any other polls with the same mob type @@ -94,6 +109,13 @@ return FALSE signed_up -= candidate + + log_ghost_poll("Player [candidate.key] removed from poll candidacy", data = list( + "player key" = candidate.key, + "role name" = role, + "poll question" = question, + )) + if(!silent) to_chat(candidate, span_danger(response_messages[POLL_RESPONSE_UNREGISTERED])) @@ -124,3 +146,13 @@ /datum/candidate_poll/proc/time_left() return duration - (world.time - time_started) + + +/// Print to chat which candidate was selected +/datum/candidate_poll/proc/announce_chosen(list/poll_recipients) + if(!length(chosen_candidates)) + return + for(var/mob/chosen in chosen_candidates) + var/client/chosen_client = chosen.client + for(var/mob/poll_recipient as anything in poll_recipients) + to_chat(poll_recipient, span_ooc("[isobserver(poll_recipient) ? FOLLOW_LINK(poll_recipient, chosen_client.mob) : null][span_warning(" [full_capitalize(role)] Poll: ")][key_name(chosen_client, include_name = FALSE)] was selected.")) diff --git a/code/datums/communications.dm b/code/datums/communications.dm index df60c17db85a..bab90027eae5 100644 --- a/code/datums/communications.dm +++ b/code/datums/communications.dm @@ -41,9 +41,9 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n else var/list/message_data = user.treat_message(input) if(syndicate) - priority_announce(html_decode(message_data["message"]), null, 'sound/announcer/announcement/announce_syndi.ogg', ANNOUNCEMENT_TYPE_SYNDICATE, has_important_message = TRUE, players = players, color_override = "red") + priority_announce(html_decode(message_data["message"]), null, 'sound/misc/announce_syndi.ogg', ANNOUNCEMENT_TYPE_SYNDICATE, has_important_message = TRUE, players = players, color_override = "red") else - priority_announce(html_decode(message_data["message"]), null, 'sound/announcer/announcement/announce.ogg', ANNOUNCEMENT_TYPE_CAPTAIN, has_important_message = TRUE, players = players) + priority_announce(html_decode(message_data["message"]), null, 'sound/misc/announce.ogg', ANNOUNCEMENT_TYPE_CAPTAIN, has_important_message = TRUE, players = players) COOLDOWN_START(src, nonsilicon_message_cooldown, COMMUNICATION_COOLDOWN) user.log_talk(input, LOG_SAY, tag="priority announcement") message_admins("[ADMIN_LOOKUPFLW(user)] has made a priority announcement.") diff --git a/code/datums/components/ghost_direct_control.dm b/code/datums/components/ghost_direct_control.dm index a131a2d3ca72..05460efa096d 100644 --- a/code/datums/components/ghost_direct_control.dm +++ b/code/datums/components/ghost_direct_control.dm @@ -16,8 +16,11 @@ /datum/component/ghost_direct_control/Initialize( ban_type = ROLE_SENTIENCE, role_name = null, + poll_question = null, poll_candidates = TRUE, + poll_announce_chosen = TRUE, poll_length = 10 SECONDS, + poll_chat_border_icon = null, poll_ignore_key = POLL_IGNORE_SENTIENCE_POTION, assumed_control_message = null, datum/callback/extra_control_checks, @@ -36,7 +39,7 @@ LAZYADD(GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")], mob_parent) if (poll_candidates) - INVOKE_ASYNC(src, PROC_REF(request_ghost_control), role_name || "[parent]", poll_length, poll_ignore_key) + INVOKE_ASYNC(src, PROC_REF(request_ghost_control), poll_question, role_name || "[parent]", poll_length, poll_ignore_key, poll_announce_chosen, poll_chat_border_icon) /datum/component/ghost_direct_control/RegisterWithParent() . = ..() @@ -70,23 +73,25 @@ examine_text += span_boldnotice("You could take control of this mob by clicking on it.") /// Send out a request for a brain -/datum/component/ghost_direct_control/proc/request_ghost_control(role_name, poll_length, poll_ignore_key) - if (!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER)) +/datum/component/ghost_direct_control/proc/request_ghost_control(poll_question, role_name, poll_length, poll_ignore_key, poll_announce_chosen, poll_chat_border_icon) + if(!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER)) return awaiting_ghosts = TRUE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates( - question = "Do you want to play as [role_name]?", + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + question = poll_question, check_jobban = ban_type, - role = ban_type, poll_time = poll_length, + checked_target = parent, ignore_category = poll_ignore_key, - pic_source = parent, + alert_pic = parent, role_name_text = role_name, + chat_text_border_icon = poll_chat_border_icon, + announce_chosen = poll_announce_chosen, ) awaiting_ghosts = FALSE - if (!LAZYLEN(candidates)) + if(isnull(chosen_one)) return - assume_direct_control(pick(candidates)) + assume_direct_control(chosen_one) /// A ghost clicked on us, they want to get in this body /datum/component/ghost_direct_control/proc/on_ghost_clicked(mob/our_mob, mob/dead/observer/hopeful_ghost) diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index c0734025a8e0..c510ce0ca855 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -84,20 +84,18 @@ /datum/disease/transformation/proc/replace_banned_player(mob/living/new_mob) // This can run well after the mob has been transferred, so need a handle on the new mob to kill it if needed. set waitfor = FALSE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as [affected_mob.real_name]?", check_jobban = bantype, role = bantype, poll_time = 5 SECONDS, target_mob = affected_mob, pic_source = affected_mob, role_name_text = "transformation victim") - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_notice(affected_mob.real_name)]?", check_jobban = bantype, role = bantype, poll_time = 5 SECONDS, checked_target = affected_mob, alert_pic = affected_mob, role_name_text = "transformation victim") + if(chosen_one) to_chat(affected_mob, span_userdanger("Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!")) - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") affected_mob.ghostize(FALSE) - affected_mob.key = C.key + affected_mob.key = chosen_one.key else to_chat(new_mob, span_userdanger("Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!")) new_mob.investigate_log("has been killed because there was no one to replace them as a job-banned player.", INVESTIGATE_DEATHS) new_mob.death() if (!QDELETED(new_mob)) new_mob.ghostize(can_reenter_corpse = FALSE) - new_mob.key = null /datum/disease/transformation/jungle_flu name = "Jungle Flu" diff --git a/code/datums/elements/uplink_reimburse.dm b/code/datums/elements/uplink_reimburse.dm index 3ff182ec2314..2753fdcf495a 100644 --- a/code/datums/elements/uplink_reimburse.dm +++ b/code/datums/elements/uplink_reimburse.dm @@ -22,7 +22,7 @@ RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) RegisterSignal(target, COMSIG_ITEM_ATTEMPT_TC_REIMBURSE, PROC_REF(reimburse)) RegisterSignal(target,COMSIG_TRAITOR_ITEM_USED(target.type), PROC_REF(used)) - + /datum/element/uplink_reimburse/Detach(datum/target) UnregisterSignal(target, list(COMSIG_ATOM_EXAMINE, COMSIG_TRAITOR_ITEM_USED(target.type), COMSIG_ITEM_ATTEMPT_TC_REIMBURSE)) @@ -47,10 +47,12 @@ to_chat(user, span_notice("You tap [uplink_comp.uplink_handler] with [refund_item], and a moment after [refund_item] disappears in a puff of red smoke!")) do_sparks(2, source = uplink_comp.uplink_handler) - uplink_comp.add_telecrystals(refundable_tc) + uplink_comp.uplink_handler.add_telecrystals(refundable_tc) + SEND_SIGNAL(refund_item, COMSIG_ITEM_TC_REIMBURSED) qdel(refund_item) + /// If the item is used, it needs to no longer be refundable /datum/element/uplink_reimburse/proc/used(datum/target) SIGNAL_HANDLER - + Detach(target) diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index 69a1a2349612..d366ed78757c 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -19,8 +19,8 @@ GLOBAL_DATUM_INIT(steal_item_handler, /datum/objective_item_handler, new()) /datum/objective_item_handler/proc/new_item_created(datum/source, obj/item/item) SIGNAL_HANDLER - if(HAS_TRAIT(item, TRAIT_ITEM_OBJECTIVE_BLOCKED)) - return + // if(HAS_TRAIT(item, TRAIT_ITEM_OBJECTIVE_BLOCKED)) + // return if(!generated_items) item.add_stealing_item_objective() return diff --git a/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm b/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm index 29a1d9b3009b..51a033f515f9 100644 --- a/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm +++ b/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm @@ -178,7 +178,7 @@ candidate_list += GLOB.current_observers_list candidate_list += GLOB.dead_player_list - var/list/candidates = SSpolling.poll_candidates("Would you like to participate in a spooky ghost swarm? (Warning: you will not be able to return to your body!)", check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, group = candidate_list, pic_source = src, role_name_text = "ghost swarm") + var/list/candidates = SSpolling.poll_candidates("Would you like to participate in a spooky ghost swarm? (Warning: you will not be able to return to your body!)", check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, group = candidate_list, alert_pic = src, role_name_text = "ghost swarm") for(var/mob/dead/observer/candidate_ghost as anything in candidates) var/mob/living/basic/ghost/swarm/new_ghost = new(get_turf(src)) ghosts_spawned += new_ghost diff --git a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm index c403fb06cbf0..b814d914b657 100644 --- a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm +++ b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm @@ -6,6 +6,8 @@ /// How many seconds between each gas release var/releasedelay = 10 aSignal = /obj/item/assembly/signaler/anomaly/pyro + /// Delay before spawning a slime + var/poll_time = 5 SECONDS /obj/effect/anomaly/pyro/Initialize(mapload, new_lifespan, drops_core) . = ..() @@ -39,11 +41,10 @@ var/datum/action/innate/slime/reproduce/repro_action = new repro_action.Grant(pyro) - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a pyroclastic anomaly slime?", check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, target_mob = pyro, ignore_category = POLL_IGNORE_PYROSLIME, pic_source = pyro, role_name_text = "pyroclastic anomaly slime") - if(!LAZYLEN(candidates)) + var/mob/dead/observer/chosen = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_SENTIENCE, poll_time = poll_time, checked_target = src, ignore_category = POLL_IGNORE_PYROSLIME, alert_pic = src, role_name_text = "pyroclastic anomaly slime") + if(isnull(chosen)) return - var/mob/dead/observer/chosen = pick(candidates) pyro.key = chosen.key pyro.mind.add_antag_datum(/datum/antagonist/pyro_slime) pyro.log_message("was made into a slime by pyroclastic anomaly", LOG_GAME) diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index d88901dcf867..e523fe8350b0 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -1302,7 +1302,7 @@ registered_name = "Emergency Response Intern" trim = /datum/id_trim/centcom/ert -/obj/item/card/id/advanced/centcom/ert +/obj/item/card/id/advanced/centcom/ert/commander registered_name = JOB_ERT_COMMANDER trim = /datum/id_trim/centcom/ert/commander diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm index 66f29dfb7906..cdaa2564970e 100644 --- a/code/game/objects/items/dice.dm +++ b/code/game/objects/items/dice.dm @@ -428,11 +428,19 @@ var/mob/living/carbon/human/human_servant = new(drop_location()) do_smoke(0, holder = src, location = drop_location()) - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as [user.real_name]'s Servant?", check_jobban = ROLE_WIZARD, role = ROLE_WIZARD, poll_time = 5 SECONDS, target_mob = human_servant, pic_source = user, role_name_text = "dice servant") - if(LAZYLEN(candidates)) - var/mob/dead/observer/candidate = pick(candidates) - message_admins("[ADMIN_LOOKUPFLW(candidate)] was spawned as Dice Servant") - human_servant.key = candidate.key + var/mob/dead/observer/chosen_one = SSpolling.poll_ghosts_for_target( + "Do you want to play as [user.real_name]'s Servant?", + check_jobban = ROLE_WIZARD, + role = ROLE_WIZARD, + poll_time = 5 SECONDS, + checked_target = human_servant, + jump_target = human_servant, + alert_pic = user, + role_name_text = "dice servant", + ) + if(!isnull(chosen_one)) + message_admins("[ADMIN_LOOKUPFLW(chosen_one)] was spawned as Dice Servant") + human_servant.key = chosen_one.key human_servant.equipOutfit(/datum/outfit/butler) var/datum/mind/servant_mind = new /datum/mind() diff --git a/code/game/objects/items/dna_probe.dm b/code/game/objects/items/dna_probe.dm index b25a3158ded2..77abe7c005ed 100644 --- a/code/game/objects/items/dna_probe.dm +++ b/code/game/objects/items/dna_probe.dm @@ -145,52 +145,3 @@ if(isanimal_or_basicmob(target) || is_type_in_typecache(target, non_simple_animals) || ismonkey(target)) return TRUE return FALSE - -#define CARP_MIX_DNA_TIMER (15 SECONDS) - -///Used for scanning carps, and then turning yourself into one. -/obj/item/dna_probe/carp_scanner - name = "Carp DNA Sampler" - desc = "Can be used to take chemical and genetic samples of animals." - ///Whether we have Carp DNA - var/carp_dna_loaded = FALSE - -/obj/item/dna_probe/carp_scanner/examine_more(mob/user) - . = ..() - if(IS_TRAITOR(user)) - . = list(span_notice("Using this on a Space Carp will harvest its DNA. Use it in-hand once complete to mutate it with yourself.")) - -/obj/item/dna_probe/carp_scanner/scan_dna(atom/target, mob/user) - if(istype(target, /mob/living/basic/carp)) - carp_dna_loaded = TRUE - playsound(src, 'sound/misc/compiler-stage2.ogg', 50) - balloon_alert(user, "dna scanned") - else - return ..() - -/obj/item/dna_probe/carp_scanner/valid_scan_target(atom/target) - if (istype(target, /mob/living/basic/carp)) - return TRUE - return ..() - -/obj/item/dna_probe/carp_scanner/attack_self(mob/user, modifiers) - . = ..() - if(!is_special_character(user)) - return - if(!carp_dna_loaded) - to_chat(user, span_notice("Space carp DNA is required to use the self-mutation mechanism!")) - return - to_chat(user, span_notice("You pull out the needle from [src] and flip the switch, and start injecting yourself with it.")) - if(!do_after(user, CARP_MIX_DNA_TIMER)) - return - var/mob/living/basic/space_dragon/new_dragon = user.change_mob_type(/mob/living/basic/space_dragon, location = loc, delete_old_mob = TRUE) - new_dragon.add_filter("anger_glow", 3, list("type" = "outline", "color" = COLOR_CARP_RIFT_RED, "size" = 5)) - new_dragon.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) - priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert") - qdel(src) - -#undef CARP_MIX_DNA_TIMER - -#undef DNA_PROBE_SCAN_PLANTS -#undef DNA_PROBE_SCAN_ANIMALS -#undef DNA_PROBE_SCAN_HUMANS diff --git a/code/game/objects/items/stacks/telecrystal.dm b/code/game/objects/items/stacks/telecrystal.dm index b62e0503de78..0429354d6c04 100644 --- a/code/game/objects/items/stacks/telecrystal.dm +++ b/code/game/objects/items/stacks/telecrystal.dm @@ -23,7 +23,7 @@ var/datum/component/uplink/hidden_uplink = uplink.GetComponent(/datum/component/uplink) if(hidden_uplink) - hidden_uplink.add_telecrystals(amount) + hidden_uplink.uplink_handler.add_telecrystals(amount) use(amount) to_chat(user, span_notice("You press [src] onto yourself and charge your hidden uplink.")) return ITEM_INTERACT_SUCCESS diff --git a/code/game/objects/items/storage/boxes/clothes_boxes.dm b/code/game/objects/items/storage/boxes/clothes_boxes.dm index a136f4664173..c1a2cee3010a 100644 --- a/code/game/objects/items/storage/boxes/clothes_boxes.dm +++ b/code/game/objects/items/storage/boxes/clothes_boxes.dm @@ -47,11 +47,6 @@ new /obj/item/clothing/head/syndicatefake(src) new /obj/item/clothing/suit/syndicatefake(src) -/obj/item/storage/box/syndie_kit/space_dragon/PopulateContents() - new /obj/item/dna_probe/carp_scanner(src) - new /obj/item/clothing/suit/hooded/carp_costume/spaceproof/old(src) - new /obj/item/clothing/mask/gas/carp(src) - /obj/item/storage/box/deputy name = "box of deputy armbands" desc = "To be issued to those authorized to act as deputy of security." diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 6f1f4f63c7a7..2f390ea4c328 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -545,7 +545,7 @@ ADMIN_VERB(spawn_debug_full_crew, R_DEBUG, "Spawn Debug Full Crew", "Creates a f qdel(new_guy) // Then equip up the human with job gear. - SSjob.EquipRank(character, job) + SSjob.equip_rank(character, job) job.after_latejoin_spawn(character) // Finally, ensure the minds are tracked and in the manifest. diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm index 2473d406e8eb..5862cab8ac6d 100644 --- a/code/modules/admin/antag_panel.dm +++ b/code/modules/admin/antag_panel.dm @@ -82,9 +82,9 @@ GLOBAL_VAR(antag_prototypes) if(!current) result += "No body!" if(current && HAS_TRAIT(current, TRAIT_MINDSHIELD)) - result += span_good("Mindshielded") - if(current && HAS_MIND_TRAIT(current, TRAIT_UNCONVERTABLE)) - result += span_good("Unconvertable") + result += span_green("Mindshielded") + // if(current && HAS_MIND_TRAIT(current, TRAIT_UNCONVERTABLE)) + // result += span_green("Unconvertable") return result /** diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm index 4d74f5425608..b65f72f8f4d7 100644 --- a/code/modules/admin/fun_balloon.dm +++ b/code/modules/admin/fun_balloon.dm @@ -87,14 +87,14 @@ if (!possessable.ckey && possessable.stat == CONSCIOUS) // Only assign ghosts to living, non-occupied mobs! bodies += possessable - var/list/candidates = SSpolling.poll_ghost_candidates_for_mobs( - question = "Would you like to be [group_name]?", + var/list/candidates = SSpolling.poll_ghosts_for_targets( + question = "Would you like to be [span_notice(group_name)]?", role = ROLE_SENTIENCE, check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, - mobs = bodies, + checked_targets = bodies, ignore_category = POLL_IGNORE_SHUTTLE_DENIZENS, - pic_source = src, + alert_pic = src, role_name_text = "sentience fun balloon", ) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index f7380b1fc266..3bf4c22df318 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1482,12 +1482,6 @@ else return - else if(href_list["force_war"]) - if(!check_rights(R_ADMIN)) - return - var/obj/item/nuclear_challenge/button = locate(href_list["force_war"]) - button.force_war() - else if(href_list["give_reinforcement"]) var/datum/team/nuclear/nuketeam = locate(href_list["give_reinforcement"]) in GLOB.antagonist_teams nuketeam.admin_spawn_reinforcement(usr) diff --git a/code/modules/admin/verbs/admin.dm b/code/modules/admin/verbs/admin.dm index 6e0c7f68e563..629423e713fa 100644 --- a/code/modules/admin/verbs/admin.dm +++ b/code/modules/admin/verbs/admin.dm @@ -32,7 +32,7 @@ ADMIN_VERB(unprison, R_ADMIN, "UnPrison", ADMIN_VERB_NO_DESCRIPTION, ADMIN_CATEG tgui_alert(user, "[prisoner.name] is not prisoned.") return - SSjob.SendToLateJoin(prisoner) + SSjob.send_to_late_join(prisoner) message_admins("[key_name_admin(user)] has unprisoned [key_name_admin(prisoner)]") log_admin("[key_name(user)] has unprisoned [key_name(prisoner)]") BLACKBOX_LOG_ADMIN_VERB("Unprison") diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm index d28622de0b65..10e53400c3b4 100644 --- a/code/modules/admin/verbs/admingame.dm +++ b/code/modules/admin/verbs/admingame.dm @@ -190,7 +190,7 @@ ADMIN_VERB(respawn_character, R_ADMIN, "Respawn Character", "Respawn a player th if(findtext(G_found.real_name,"monkey")) if(tgui_alert(user,"This character appears to have been a monkey. Would you like to respawn them as such?",,list("Yes","No")) == "Yes") var/mob/living/carbon/human/species/monkey/new_monkey = new - SSjob.SendToLateJoin(new_monkey) + SSjob.send_to_late_join(new_monkey) G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use new_monkey.key = G_found.key to_chat(new_monkey, "You have been fully respawned. Enjoy the game.", confidential = TRUE) @@ -202,7 +202,7 @@ ADMIN_VERB(respawn_character, R_ADMIN, "Respawn Character", "Respawn a player th //Ok, it's not a monkey. So, spawn a human. var/mob/living/carbon/human/new_character = new//The mob being spawned. - SSjob.SendToLateJoin(new_character) + SSjob.send_to_late_join(new_character) var/datum/record/locked/record_found //Referenced to later to either randomize or not randomize the character. if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something @@ -242,7 +242,7 @@ ADMIN_VERB(respawn_character, R_ADMIN, "Respawn Character", "Respawn a player th //Now for special roles and equipment. var/datum/antagonist/traitor/traitordatum = new_character.mind.has_antag_datum(/datum/antagonist/traitor) if(traitordatum) - SSjob.EquipRank(new_character, new_character.mind.assigned_role, new_character.client) + SSjob.equip_rank(new_character, new_character.mind.assigned_role, new_character.client) new_character.mind.give_uplink(silent = TRUE, antag_datum = traitordatum) var/skip_job_respawn = FALSE diff --git a/code/modules/admin/verbs/ert.dm b/code/modules/admin/verbs/ert.dm index a5232ca53336..5664cca1c6e0 100644 --- a/code/modules/admin/verbs/ert.dm +++ b/code/modules/admin/verbs/ert.dm @@ -121,7 +121,7 @@ var/list/spawnpoints = GLOB.emergencyresponseteamspawn var/index = 0 - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for [ertemplate.polldesc]?", check_jobban = "deathsquad", pic_source = /obj/item/card/id/advanced/centcom/ert, role_name_text = "emergency response team") + var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for [span_notice(ertemplate.polldesc)]?", check_jobban = "deathsquad", alert_pic = /obj/item/card/id/advanced/centcom/ert/commander, role_name_text = "emergency response team") var/teamSpawned = FALSE // This list will take priority over spawnpoints if not empty diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index d0d82243f35c..9e0180b192ed 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -406,7 +406,7 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w var/list/candidates = list() if (prefs["offerghosts"]["value"] == "Yes") - candidates = SSpolling.poll_ghost_candidates(replacetext(prefs["ghostpoll"]["value"], "%TYPE%", initial(pathToSpawn.name)), check_jobban = ROLE_TRAITOR, pic_source = pathToSpawn, role_name_text = "portal storm") + candidates = SSpolling.poll_ghost_candidates(replacetext(prefs["ghostpoll"]["value"], "%TYPE%", initial(pathToSpawn.name)), check_jobban = ROLE_TRAITOR, alert_pic = pathToSpawn, role_name_text = "portal storm") if (prefs["playersonly"]["value"] == "Yes" && length(candidates) < prefs["minplayers"]["value"]) message_admins("Not enough players signed up to create a portal storm, the minimum was [prefs["minplayers"]["value"]] and the number of signups [length(candidates)]") @@ -523,7 +523,7 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w if(teamsize <= 0) return FALSE - candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a Nanotrasen emergency response drone?", check_jobban = ROLE_DRONE, pic_source = /mob/living/basic/drone/classic, role_name_text = "nanotrasen emergency response drone") + candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a Nanotrasen emergency response drone?", check_jobban = ROLE_DRONE, alert_pic = /mob/living/basic/drone/classic, role_name_text = "nanotrasen emergency response drone") if(length(candidates) == 0) return FALSE diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 0d551a2ba157..be6c266f3071 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -56,7 +56,7 @@ GLOBAL_LIST_EMPTY(antagonists) /// Whether we give a hardcore random bonus for greentexting as this antagonist while playing hardcore random var/hardcore_random_bonus = FALSE /// A path to the audio stinger that plays upon gaining this datum. - // var/stinger_sound + var/stinger_sound //ANTAG UI @@ -291,9 +291,9 @@ GLOBAL_LIST_EMPTY(antagonists) var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = jobban_flag || pref_flag, role = pref_flag, poll_time = 5 SECONDS, checked_target = owner.current, alert_pic = owner.current, role_name_text = name) if(chosen_one) to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner)]) to replace a jobbanned player.") + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(owner)]) to replace a jobbanned player.") owner.current.ghostize(FALSE) - owner.current.key = C.key + owner.current.key = chosen_one.key /** * Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion. @@ -335,6 +335,14 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/greet() if(!silent) to_chat(owner.current, span_big("You are \the [src].")) + play_stinger() + +/// Plays the antag stinger sound, if we have one +/datum/antagonist/proc/play_stinger() + if(isnull(stinger_sound)) + return + + owner.current.playsound_local(get_turf(owner.current), stinger_sound, 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) /** * Proc that sends fluff or instructional messages to the player when they lose this antag datum. diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 0b5cb174c82e..b608563067a8 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -55,16 +55,15 @@ /obj/item/antag_spawner/contract/proc/poll_for_student(mob/living/carbon/human/teacher, apprentice_school) balloon_alert(teacher, "contacting apprentice...") polling = TRUE - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a wizard's [apprentice_school] apprentice?", check_jobban = ROLE_WIZARD, role = ROLE_WIZARD, poll_time = 15 SECONDS, target_mob = src, pic_source = teacher, role_name_text = "wizard apprentice") + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger("[teacher]'s")] [span_notice("[apprentice_school] apprentice")]?", check_jobban = ROLE_WIZARD, role = ROLE_WIZARD, poll_time = 15 SECONDS, checked_target = src, alert_pic = /obj/item/clothing/head/wizard/red, jump_target = src, role_name_text = "wizard apprentice", chat_text_border_icon = /obj/item/clothing/head/wizard/red) polling = FALSE - if(!LAZYLEN(candidates)) + if(isnull(chosen_one)) to_chat(teacher, span_warning("Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.")) return if(QDELETED(src) || used) return used = TRUE - var/mob/dead/observer/student = pick(candidates) - spawn_antag(student.client, get_turf(src), apprentice_school, teacher.mind) + spawn_antag(chosen_one.client, get_turf(src), apprentice_school, teacher.mind) /obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind, datum/mind/user) new /obj/effect/particle_effect/fluid/smoke(T) @@ -133,13 +132,12 @@ return to_chat(user, span_notice("You activate [src] and wait for confirmation.")) - var/list/nuke_candidates = SSpolling.poll_ghost_candidates("Do you want to play as a syndicate [borg_to_spawn ? "[LOWER_TEXT(borg_to_spawn)] cyborg":"operative"]?", check_jobban = ROLE_OPERATIVE, role = ROLE_OPERATIVE, poll_time = 15 SECONDS, ignore_category = POLL_IGNORE_SYNDICATE, pic_source = src, role_name_text = "syndicate [borg_to_spawn ? "[borg_to_spawn] cyborg":"operative"]") - if(LAZYLEN(nuke_candidates)) + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as a reinforcement [special_role_name]?", check_jobban = ROLE_OPERATIVE, role = ROLE_OPERATIVE, poll_time = 15 SECONDS, ignore_category = POLL_IGNORE_SYNDICATE, alert_pic = src, role_name_text = special_role_name, amount_to_pick = 1) + if(chosen_one) if(QDELETED(src) || !check_usability(user)) return used = TRUE - var/mob/dead/observer/G = pick(nuke_candidates) - spawn_antag(G.client, get_turf(src), "nukeop", user.mind) + spawn_antag(chosen_one.client, get_turf(src), "nukeop", user.mind) do_sparks(4, TRUE, src) qdel(src) else @@ -147,7 +145,6 @@ /obj/item/antag_spawner/nuke_ops/spawn_antag(client/our_client, turf/T, kind, datum/mind/user) var/mob/living/carbon/human/nukie = new() - var/obj/structure/closet/supplypod/pod = setup_pod() our_client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE) nukie.ckey = our_client.key var/datum/mind/op_mind = nukie.mind @@ -158,17 +155,12 @@ antag_datum = new() antag_datum.send_to_spawnpoint = FALSE - antag_datum.nukeop_outfit = use_subtypes ? pick(subtypesof(outfit)) : outfit var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop, TRUE) - op_mind.add_antag_datum(new_datum, creator_op?.get_team()) + op_mind.add_antag_datum(antag_datum, creator_op?.get_team()) LAZYADD(op_mind.special_roles, special_role_name) - if(outfit) - var/datum/antagonist/nukeop/nukie_datum = op_mind.has_antag_datum(antag_datum) - nukie_datum.nukeop_outfit = use_subtypes ? pick(subtypesof(outfit)) : outfit - var/obj/structure/closet/supplypod/pod = setup_pod() nukie.forceMove(pod) new /obj/effect/pod_landingzone(get_turf(src), pod) @@ -255,14 +247,13 @@ return if(used) return - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [initial(demon_type.name)]?", check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, poll_time = 5 SECONDS, target_mob = src, pic_source = src, role_name_text = initial(demon_type.name)) - if(LAZYLEN(candidates)) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, poll_time = 5 SECONDS, checked_target = src, alert_pic = demon_type, jump_target = src, role_name_text = initial(demon_type.name)) + if(chosen_one) if(used || QDELETED(src)) return used = TRUE - var/mob/dead/observer/summoned = pick(candidates) - user.log_message("has summoned forth the [initial(demon_type.name)] (played by [key_name(summoned)]) using a [name].", LOG_GAME) // has to be here before we create antag otherwise we can't get the ckey of the demon - spawn_antag(summoned.client, get_turf(src), initial(demon_type.name), user.mind) + user.log_message("has summoned forth the [initial(demon_type.name)] (played by [key_name(chosen_one)]) using a [name].", LOG_GAME) // has to be here before we create antag otherwise we can't get the ckey of the demon + spawn_antag(chosen_one.client, get_turf(src), initial(demon_type.name), user.mind) to_chat(user, shatter_msg) to_chat(user, veil_msg) playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, TRUE) @@ -335,15 +326,22 @@ return to_chat(user, span_notice("You activate [src] and wait for confirmation.")) - var/list/baddie_candidates = SSpolling.poll_ghost_candidates("Do you want to play as a [role_to_play]?", check_jobban = poll_role_check, role = poll_role_check, poll_time = 10 SECONDS, ignore_category = poll_ignore_category, pic_source = src, role_name_text = role_to_play) - if(!LAZYLEN(baddie_candidates)) + var/mob/chosen_one = SSpolling.poll_ghost_candidates( + check_jobban = poll_role_check, + role = poll_role_check, + poll_time = 10 SECONDS, + ignore_category = poll_ignore_category, + alert_pic = src, + role_name_text = role_to_play, + amount_to_pick = 1 + ) + if(isnull(chosen_one)) to_chat(user, span_warning(fail_text)) return if(QDELETED(src) || !check_usability(user)) return used = TRUE - var/mob/dead/observer/ghostie = pick(baddie_candidates) - spawn_antag(ghostie.client, get_turf(src), user) + spawn_antag(chosen_one.client, get_turf(src), user) do_sparks(4, TRUE, src) qdel(src) @@ -376,6 +374,21 @@ spawned_mob.forceMove(pod) new /obj/effect/pod_landingzone(get_turf(src), pod) +/obj/item/antag_spawner/loadout/contractor + name = "contractor support beacon" + desc = "A beacon sold to the most prestigeous syndicate members, a single-use radio for calling immediate backup." + icon = 'icons/obj/devices/voice.dmi' + icon_state = "nukietalkie" + outfit = /datum/outfit/contractor_partner + use_subtypes = FALSE + antag_datum = /datum/antagonist/traitor/contractor_support + poll_ignore_category = ROLE_TRAITOR + role_to_play = ROLE_CONTRACTOR_SUPPORT + +/obj/item/antag_spawner/loadout/contractor/do_special_things(mob/living/carbon/human/contractor_support, mob/user) + to_chat(contractor_support, "\n[span_alertwarning("[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.")]") + to_chat(contractor_support, "[span_alertwarning("Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.")]") + /obj/item/antag_spawner/loadout/monkey_man name = "monkey agent beacon" desc = "Call up some backup from ARC for monkey mayhem." diff --git a/code/modules/antagonists/abductor/abductee/abductee.dm b/code/modules/antagonists/abductor/abductee/abductee.dm index f1e657a558ea..1fd5dc4c74a9 100644 --- a/code/modules/antagonists/abductor/abductee/abductee.dm +++ b/code/modules/antagonists/abductor/abductee/abductee.dm @@ -9,6 +9,7 @@ roundend_category = "abductees" antagpanel_category = ANTAG_GROUP_ABDUCTORS antag_hud_name = "abductee" + stinger_sound = 'sound/ambience/antag/abductee.ogg' /datum/antagonist/abductee/on_gain() give_objective() diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index 8d62cf16d7a9..493af969824b 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -7,6 +7,8 @@ show_in_antagpanel = FALSE //should only show subtypes show_to_ghosts = TRUE suicide_cry = "FOR THE MOTHERSHIP!!" // They can't even talk but y'know + stinger_sound = 'sound/ambience/antag/ayylien.ogg' + var/datum/team/abductor_team/team var/sub_role var/outfit @@ -72,12 +74,11 @@ owner.set_assigned_role(SSjob.get_job_type(role_job)) objectives += team.objectives finalize_abductor() - // We don't want abductors to be converted by other antagonists - owner.add_traits(list(TRAIT_ABDUCTOR_TRAINING, TRAIT_UNCONVERTABLE), ABDUCTOR_ANTAGONIST) + ADD_TRAIT(owner, TRAIT_ABDUCTOR_TRAINING, ABDUCTOR_ANTAGONIST) return ..() /datum/antagonist/abductor/on_removal() - owner.remove_traits(list(TRAIT_ABDUCTOR_TRAINING, TRAIT_UNCONVERTABLE), ABDUCTOR_ANTAGONIST) + REMOVE_TRAIT(owner, TRAIT_ABDUCTOR_TRAINING, ABDUCTOR_ANTAGONIST) return ..() /datum/antagonist/abductor/greet() diff --git a/code/modules/antagonists/abductor/machinery/experiment.dm b/code/modules/antagonists/abductor/machinery/experiment.dm index b5254310c6b3..72bf6ae2f40a 100644 --- a/code/modules/antagonists/abductor/machinery/experiment.dm +++ b/code/modules/antagonists/abductor/machinery/experiment.dm @@ -190,7 +190,7 @@ H.forceMove(console.pad.teleport_target) return //Area not chosen / It's not safe area - teleport to arrivals - SSjob.SendToLateJoin(H, FALSE) + SSjob.send_to_late_join(H, FALSE) return /obj/machinery/abductor/experiment/update_icon_state() diff --git a/code/modules/antagonists/battlecruiser/battlecruiser.dm b/code/modules/antagonists/battlecruiser/battlecruiser.dm index ab1a87b9c625..ec1f702a107b 100644 --- a/code/modules/antagonists/battlecruiser/battlecruiser.dm +++ b/code/modules/antagonists/battlecruiser/battlecruiser.dm @@ -20,7 +20,7 @@ antag_hud_name = "battlecruiser_crew" antagpanel_category = ANTAG_GROUP_SYNDICATE pref_flag = ROLE_BATTLECRUISER_CREW - stinger_sound = 'sound/music/antag/ops.ogg' + stinger_sound = 'sound/ambience/antag/ops.ogg' /// Team to place the crewmember on. var/datum/team/battlecruiser/battlecruiser_team diff --git a/code/modules/antagonists/blob/blob_antag.dm b/code/modules/antagonists/blob/blob_antag.dm index 3066fe74590d..9f9ae93749fb 100644 --- a/code/modules/antagonists/blob/blob_antag.dm +++ b/code/modules/antagonists/blob/blob_antag.dm @@ -6,6 +6,7 @@ show_in_antagpanel = FALSE pref_flag = ROLE_BLOB ui_name = "AntagInfoBlob" + stinger_sound = 'sound/ambience/antag/blobalert.ogg' /// Action to release a blob infection var/datum/action/innate/blobpop/pop_action /// Initial points for a human blob @@ -184,5 +185,3 @@ /obj/effect/dummy/phased_mob/can_blob_attack() return FALSE - - diff --git a/code/modules/antagonists/brainwashing/brainwashing.dm b/code/modules/antagonists/brainwashing/brainwashing.dm index af5bfd2b53e0..95680cac25a6 100644 --- a/code/modules/antagonists/brainwashing/brainwashing.dm +++ b/code/modules/antagonists/brainwashing/brainwashing.dm @@ -30,7 +30,7 @@ /datum/antagonist/brainwashed name = "\improper Brainwashed Victim" pref_flag = ROLE_BRAINWASHED - stinger_sound = 'sound/music/antag/brainwashed.ogg' + stinger_sound = 'sound/ambience/antag/brainwashed.ogg' roundend_category = "brainwashed victims" show_in_antagpanel = TRUE antag_hud_name = "brainwashed" diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 8a80081e286d..202b0bd4ec97 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -9,6 +9,7 @@ suicide_cry = "FOR MY BROTHER!!" antag_moodlet = /datum/mood_event/focused hardcore_random_bonus = TRUE + stinger_sound = 'sound/ambience/antag/tatoralert.ogg' VAR_PRIVATE datum/team/brother_team/team @@ -53,7 +54,7 @@ if (!istype(carbon_owner)) return carbon_owner.AddComponentFrom(REF(src), /datum/component/can_flash_from_behind) - RegisterSignal(carbon_owner, COMSIG_MOB_SUCCESSFUL_FLASHED_MOB, PROC_REF(on_mob_successful_flashed_mob)) + RegisterSignal(carbon_owner, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON, PROC_REF(on_mob_successful_flashed_carbon)) /// Take away the ability to add more brothers /datum/antagonist/brother/proc/remove_conversion_skills() @@ -61,7 +62,7 @@ return var/mob/living/carbon/carbon_owner = owner.current carbon_owner.RemoveComponentSource(REF(src), /datum/component/can_flash_from_behind) - UnregisterSignal(carbon_owner, COMSIG_MOB_SUCCESSFUL_FLASHED_MOB) + UnregisterSignal(carbon_owner, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON) /datum/antagonist/brother/proc/on_mob_successful_flashed_carbon(mob/living/source, mob/living/carbon/flashed, obj/item/assembly/flash/flash) SIGNAL_HANDLER @@ -81,7 +82,7 @@ flashed.balloon_alert(source, "[flashed.p_theyre()] loyal to someone else!") return - if (HAS_TRAIT(flashed, TRAIT_MINDSHIELD) || flashed.mind.assigned_role?.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY) + if (HAS_TRAIT(flashed, TRAIT_MINDSHIELD)) flashed.balloon_alert(source, "[flashed.p_they()] resist!") return diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index 43c933afe4ad..7fd3be635cc2 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -14,6 +14,7 @@ can_assign_self_objectives = TRUE default_custom_objective = "Consume the station's most valuable genomes." hardcore_random_bonus = TRUE + stinger_sound = 'sound/ambience/antag/ling_alert.ogg' /// Whether to give this changeling objectives or not var/give_objectives = TRUE /// Weather we assign objectives which compete with other lings diff --git a/code/modules/antagonists/clown_ops/clownop.dm b/code/modules/antagonists/clown_ops/clownop.dm index c9dac60c06b8..9448835fc64f 100644 --- a/code/modules/antagonists/clown_ops/clownop.dm +++ b/code/modules/antagonists/clown_ops/clownop.dm @@ -46,7 +46,6 @@ roundend_category = "clown operatives" antagpanel_category = ANTAG_GROUP_CLOWNOPS nukeop_outfit = /datum/outfit/syndicate/clownop/leader - challengeitem = /obj/item/nuclear_challenge/clownops suicide_cry = "HAPPY BIRTHDAY!!" /datum/antagonist/nukeop/leader/clownop/apply_innate_effects(mob/living/mob_override) diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 5da9203e7756..edf923ee423c 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -7,6 +7,7 @@ preview_outfit = /datum/outfit/cultist pref_flag = ROLE_CULTIST antag_hud_name = "cult" + stinger_sound = 'sound/ambience/antag/bloodcult/bloodcult_gain.ogg' ///The vote ability Cultists have to elect someone to be the leader. var/datum/action/innate/cult/mastervote/vote_ability diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm index ef0cc722aa3e..f78c1d73e8ca 100644 --- a/code/modules/antagonists/cult/cult_comms.dm +++ b/code/modules/antagonists/cult/cult_comms.dm @@ -127,7 +127,7 @@ if(B.current && B.current != Nominee && !B.current.incapacitated()) SEND_SOUND(B.current, 'sound/magic/exit_blood.ogg') asked_cultists += B.current - var/list/yes_voters = SSpolling.poll_candidates("[Nominee] seeks to lead your cult, do you support [Nominee.p_them()]?", poll_time = 30 SECONDS, group = asked_cultists, pic_source = Nominee, role_name_text = "cult master") + var/list/yes_voters = SSpolling.poll_candidates("[Nominee] seeks to lead your cult, do you support [Nominee.p_them()]?", poll_time = 30 SECONDS, group = asked_cultists, alert_pic = Nominee, role_name_text = "cult master") if(QDELETED(Nominee) || Nominee.incapacitated()) team.cult_vote_called = FALSE for(var/datum/mind/B in team.members) diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 919d2b8d9703..7d55146de95b 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -739,13 +739,12 @@ GLOBAL_VAR_INIT(narsie_summon_count, 0) if(!mob_to_revive.client || mob_to_revive.client.is_afk()) set waitfor = FALSE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [mob_to_revive.real_name], an inactive blood cultist?", check_jobban = ROLE_CULTIST, role = ROLE_CULTIST, poll_time = 5 SECONDS, target_mob = mob_to_revive, pic_source = mob_to_revive) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger(mob_to_revive.real_name)], an [span_notice("inactive blood cultist")]?", check_jobban = ROLE_CULTIST, role = ROLE_CULTIST, poll_time = 5 SECONDS, checked_target = mob_to_revive, alert_pic = mob_to_revive, role_name_text = "inactive cultist") + if(chosen_one) to_chat(mob_to_revive.mind, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form.") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.") + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.") mob_to_revive.ghostize(FALSE) - mob_to_revive.key = C.key + mob_to_revive.key = chosen_one.key else fail_invoke() return diff --git a/code/modules/antagonists/disease/disease_datum.dm b/code/modules/antagonists/disease/disease_datum.dm index cd166b5782b2..79dd95f93a46 100644 --- a/code/modules/antagonists/disease/disease_datum.dm +++ b/code/modules/antagonists/disease/disease_datum.dm @@ -7,7 +7,6 @@ /datum/antagonist/disease/on_gain() owner.set_assigned_role(SSjob.get_job_type(/datum/job/sentient_disease)) - owner.special_role = ROLE_SENTIENT_DISEASE var/datum/objective/O = new /datum/objective/disease_infect() O.owner = owner objectives += O diff --git a/code/modules/antagonists/evil_clone/evil_clone.dm b/code/modules/antagonists/evil_clone/evil_clone.dm index ef8455c3574d..a5dad0b9112e 100644 --- a/code/modules/antagonists/evil_clone/evil_clone.dm +++ b/code/modules/antagonists/evil_clone/evil_clone.dm @@ -1,7 +1,7 @@ /// Antag datum associated with the experimental cloner /datum/antagonist/evil_clone name = "\improper Evil Clone" - stinger_sound = 'sound/music/antag/hypnotized.ogg' + stinger_sound = 'sound/ambience/antag/hypnotized.ogg' pref_flag = ROLE_EVIL_CLONE roundend_category = "evil clones" show_in_antagpanel = TRUE diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm index 820728dd2829..f68540292b37 100644 --- a/code/modules/antagonists/heretic/heretic_antag.dm +++ b/code/modules/antagonists/heretic/heretic_antag.dm @@ -26,6 +26,7 @@ can_assign_self_objectives = TRUE default_custom_objective = "Turn a department into a testament for your dark knowledge." hardcore_random_bonus = TRUE + stinger_sound = 'sound/ambience/antag/ecult_op.ogg' /// Whether we give this antagonist objectives on gain. var/give_objectives = TRUE /// Whether we've ascended! (Completed one of the final rituals) diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 7e6177d8b90d..42b8b18bad69 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -547,14 +547,13 @@ animate(summoned, 10 SECONDS, alpha = 155) message_admins("A [summoned.name] is being summoned by [ADMIN_LOOKUPFLW(user)] in [ADMIN_COORDJMP(summoned)].") - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [summoned.name]?", check_jobban = ROLE_HERETIC, poll_time = 10 SECONDS, target_mob = summoned, ignore_category = poll_ignore_define, pic_source = summoned, role_name_text = summoned.name) - if(!LAZYLEN(candidates)) + var/mob/picked_candidate = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_HERETIC, poll_time = 10 SECONDS, checked_target = summoned, ignore_category = poll_ignore_define, alert_pic = summoned, role_name_text = summoned.name) + if(isnull(picked_candidate)) loc.balloon_alert(user, "ritual failed, no ghosts!") animate(summoned, 0.5 SECONDS, alpha = 0) QDEL_IN(summoned, 0.6 SECONDS) return FALSE - var/mob/dead/observer/picked_candidate = pick(candidates) // Ok let's make them an interactable mob now, since we got a ghost summoned.alpha = 255 REMOVE_TRAIT(summoned, TRAIT_NO_TRANSFORM, REF(src)) diff --git a/code/modules/antagonists/heretic/heretic_monsters.dm b/code/modules/antagonists/heretic/heretic_monsters.dm index 5ce974014f9a..2bcbe31ee259 100644 --- a/code/modules/antagonists/heretic/heretic_monsters.dm +++ b/code/modules/antagonists/heretic/heretic_monsters.dm @@ -8,13 +8,10 @@ antag_hud_name = "heretic_beast" suicide_cry = "MY MASTER SMILES UPON ME!!" show_in_antagpanel = FALSE + stinger_sound = 'sound/ambience/antag/ecult_op.ogg' /// Our master (a heretic)'s mind. var/datum/mind/master -/datum/antagonist/heretic_monster/on_gain() - . = ..() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)//subject to change - /datum/antagonist/heretic_monster/on_removal() if(!silent) if(master?.current) diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index 9b3e38e38477..085204fb3b42 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -176,15 +176,13 @@ if(!soon_to_be_ghoul.mind || !soon_to_be_ghoul.client) message_admins("[ADMIN_LOOKUPFLW(user)] is creating a voiceless dead of a body with no player.") - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [soon_to_be_ghoul.real_name], a voiceless dead?", check_jobban = ROLE_HERETIC, role = ROLE_HERETIC, poll_time = 5 SECONDS, target_mob = soon_to_be_ghoul, pic_source = soon_to_be_ghoul, role_name_text = "voiceless dead") - if(!LAZYLEN(candidates)) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger(soon_to_be_ghoul.real_name)], a [span_notice("voiceless dead")]?", check_jobban = ROLE_HERETIC, role = ROLE_HERETIC, poll_time = 5 SECONDS, checked_target = soon_to_be_ghoul, alert_pic = mutable_appearance('icons/mob/human/human.dmi', "husk"), jump_target = soon_to_be_ghoul, role_name_text = "voiceless dead") + if(isnull(chosen_one)) loc.balloon_alert(user, "ritual failed, no ghosts!") return FALSE - - var/mob/dead/observer/chosen_candidate = pick(candidates) - message_admins("[key_name_admin(chosen_candidate)] has taken control of ([key_name_admin(soon_to_be_ghoul)]) to replace an AFK player.") + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(soon_to_be_ghoul)]) to replace an AFK player.") soon_to_be_ghoul.ghostize(FALSE) - soon_to_be_ghoul.key = chosen_candidate.key + soon_to_be_ghoul.key = chosen_one.key selected_atoms -= soon_to_be_ghoul make_ghoul(user, soon_to_be_ghoul) diff --git a/code/modules/antagonists/heretic/structures/lock_final.dm b/code/modules/antagonists/heretic/structures/lock_final.dm index 8cb6c06f3cb0..759bc8aa55e3 100644 --- a/code/modules/antagonists/heretic/structures/lock_final.dm +++ b/code/modules/antagonists/heretic/structures/lock_final.dm @@ -37,7 +37,7 @@ /// Ask ghosts if they want to make some noise /obj/structure/lock_tear/proc/poll_ghosts() - var/list/candidates = SSpolling.poll_ghost_candidates("Would you like to be a random eldritch monster attacking the crew?", check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HERETIC_MONSTER, pic_source = src, role_name_text = "eldritch monster") + var/list/candidates = SSpolling.poll_ghost_candidates("Would you like to be a random [span_notice("eldritch monster")] attacking the crew?", check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HERETIC_MONSTER, alert_pic = src, role_name_text = "eldritch monster") while(LAZYLEN(candidates)) var/mob/dead/observer/candidate = pick_n_take(candidates) ghost_to_monster(candidate, should_ask = FALSE) diff --git a/code/modules/antagonists/hypnotized/hypnotized.dm b/code/modules/antagonists/hypnotized/hypnotized.dm index 6ee82f01b69f..1634b2b84a72 100644 --- a/code/modules/antagonists/hypnotized/hypnotized.dm +++ b/code/modules/antagonists/hypnotized/hypnotized.dm @@ -1,7 +1,7 @@ /// Antag datum associated with the hypnosis brain trauma, used for displaying objectives and antag hud /datum/antagonist/hypnotized name = "\improper Hypnotized Victim" - stinger_sound = 'sound/music/antag/hypnotized.ogg' + stinger_sound = 'sound/ambience/antag/hypnotized.ogg' pref_flag = ROLE_HYPNOTIZED roundend_category = "hypnotized victims" antag_hud_name = "brainwashed" diff --git a/code/modules/antagonists/nukeop/datums/operative.dm b/code/modules/antagonists/nukeop/datums/operative.dm index 0279a12939dd..649c1351174f 100644 --- a/code/modules/antagonists/nukeop/datums/operative.dm +++ b/code/modules/antagonists/nukeop/datums/operative.dm @@ -8,7 +8,7 @@ show_to_ghosts = TRUE hijack_speed = 2 //If you can't take out the station, take the shuttle instead. suicide_cry = "FOR THE SYNDICATE!!" - stinger_sound = 'sound/music/antag/ops.ogg' + stinger_sound = 'sound/ambience/antag/ops.ogg' /// Which nukie team are we on? var/datum/team/nuclear/nuke_team diff --git a/code/modules/antagonists/nukeop/datums/operative_leader.dm b/code/modules/antagonists/nukeop/datums/operative_leader.dm index a15a0dec31a2..0b4a5b0ea0e5 100644 --- a/code/modules/antagonists/nukeop/datums/operative_leader.dm +++ b/code/modules/antagonists/nukeop/datums/operative_leader.dm @@ -3,8 +3,6 @@ nukeop_outfit = /datum/outfit/syndicate/leader /// Randomly chosen honorific, for distinction var/title - /// The nuclear challenge remote we will spawn this player with. - var/challengeitem = /obj/item/nuclear_challenge /datum/antagonist/nukeop/leader/memorize_code() . = ..() @@ -32,11 +30,6 @@ /datum/antagonist/nukeop/leader/on_gain() . = ..() - if(!CONFIG_GET(flag/disable_warops)) - var/mob/living/carbon/human/leader = owner.current - var/obj/item/war_declaration = new challengeitem(leader.drop_location()) - leader.put_in_hands(war_declaration) - nuke_team.war_button_ref = WEAKREF(war_declaration) addtimer(CALLBACK(src, PROC_REF(nuketeam_name_assign)), 0.1 SECONDS) /datum/antagonist/nukeop/leader/proc/nuketeam_name_assign() diff --git a/code/modules/antagonists/nukeop/datums/operative_team.dm b/code/modules/antagonists/nukeop/datums/operative_team.dm index 4eab9341d48c..1604ac65f6ab 100644 --- a/code/modules/antagonists/nukeop/datums/operative_team.dm +++ b/code/modules/antagonists/nukeop/datums/operative_team.dm @@ -76,7 +76,7 @@ text += "
" text += "(Syndicates used [TC_uses] TC) [purchases]" if(TC_uses == 0 && GLOB.station_was_nuked && !are_all_operatives_dead()) - text += "[icon2html('icons/ui/antags/badass.dmi', world, "badass")]" + text += "[icon2html('icons/ui_icons/antags/badass.dmi', world, "badass")]" parts += text @@ -106,26 +106,6 @@ disk_report += "" var/post_report - - var/war_declared = FALSE - for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards) - if(board.challenge_start_time) - war_declared = TRUE - - var/force_war_button = "" - - if(war_declared) - post_report += "War declared." - force_war_button = "\[Force war\]" - else - post_report += "War not declared." - var/obj/item/nuclear_challenge/war_button = war_button_ref?.resolve() - if(war_button) - force_war_button = "\[Force war\]" - else - force_war_button = "\[Cannot declare war, challenge button missing!\]" - - post_report += "\n[force_war_button]" post_report += "\n\[Send Reinforcement\]" var/final_report = ..() @@ -196,7 +176,7 @@ var/mob/living/carbon/human/nukie = new(spawn_loc) chosen_one.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE) - nukie.PossessByPlayer(chosen_one.key) + nukie.key = chosen_one.key var/datum/antagonist/nukeop/antag_datum = new() antag_datum.send_to_spawnpoint = FALSE @@ -309,9 +289,9 @@ return TRUE return FALSE -/datum/team/nuclear/add_member(datum/mind/new_member) - ..() - SEND_SIGNAL(src, COMSIG_NUKE_TEAM_ADDITION, new_member.current) +// /datum/team/nuclear/add_member(datum/mind/new_member) +// ..() +// SEND_SIGNAL(src, COMSIG_NUKE_TEAM_ADDITION, new_member.current) /datum/team/nuclear/proc/assign_nuke_delayed() assign_nuke() diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm deleted file mode 100644 index 734025e9a370..000000000000 --- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm +++ /dev/null @@ -1,199 +0,0 @@ -#define CHALLENGE_TELECRYSTALS 280 -#define CHALLENGE_TIME_LIMIT (5 MINUTES) -#define CHALLENGE_SHUTTLE_DELAY (25 MINUTES) // 25 minutes, so the ops have at least 5 minutes before the shuttle is callable. - -GLOBAL_LIST_EMPTY(jam_on_wardec) - -/obj/item/nuclear_challenge - name = "Declaration of War (Challenge Mode)" - icon = 'icons/obj/devices/voice.dmi' - icon_state = "nukietalkie" - inhand_icon_state = "nukietalkie" - lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - desc = "Use to send a declaration of hostilities to the target, delaying your shuttle departure for 20 minutes while they prepare for your assault. \ - Such a brazen move will attract the attention of powerful benefactors within the Syndicate, who will supply your team with a massive amount of bonus telecrystals. \ - Must be used within five minutes, or your benefactors will lose interest." - var/declaring_war = FALSE - var/uplink_type = /obj/item/uplink/nuclear - -/obj/item/nuclear_challenge/attack_self(mob/living/user) - if(!check_allowed(user)) - return - - declaring_war = TRUE - var/are_you_sure = tgui_alert(user, "Consult your team carefully before you declare war on [station_name()]. Are you sure you want to alert the enemy crew? You have [DisplayTimeText(CHALLENGE_TIME_LIMIT - world.time - SSticker.round_start_time)] to decide.", "Declare war?", list("Yes", "No")) - declaring_war = FALSE - - if(!check_allowed(user)) - return - - if(are_you_sure != "Yes") - to_chat(user, span_notice("On second thought, the element of surprise isn't so bad after all.")) - return - - var/war_declaration = "A syndicate fringe group has declared their intent to utterly destroy [station_name()] with a nuclear device, and dares the crew to try and stop them." - - declaring_war = TRUE - var/custom_threat = tgui_alert(user, "Do you want to customize your declaration?", "Customize?", list("Yes", "No")) - declaring_war = FALSE - - if(!check_allowed(user)) - return - - if(custom_threat == "Yes") - declaring_war = TRUE - war_declaration = tgui_input_text(user, "Insert your custom declaration", "Declaration", multiline = TRUE, encode = FALSE) - declaring_war = FALSE - - if(!check_allowed(user) || !war_declaration) - return - - war_was_declared(user, memo = war_declaration) - -///Admin only proc to bypass checks and force a war declaration. Button on antag panel. -/obj/item/nuclear_challenge/proc/force_war() - var/are_you_sure = tgui_alert(usr, "Are you sure you wish to force a war declaration?[GLOB.player_list.len < CHALLENGE_MIN_PLAYERS ? " Note, the player count is under the required limit." : ""]", "Declare war?", list("Yes", "No")) - - if(are_you_sure != "Yes") - return - - var/war_declaration = "A syndicate fringe group has declared their intent to utterly destroy [station_name()] with a nuclear device, and dares the crew to try and stop them." - - var/custom_threat = tgui_alert(usr, "Do you want to customize the declaration?", "Customize?", list("Yes", "No")) - - if(custom_threat == "Yes") - war_declaration = tgui_input_text(usr, "Insert your custom declaration", "Declaration", multiline = TRUE, encode = FALSE) - - if(!war_declaration) - tgui_alert(usr, "Invalid war declaration.", "Poor Choice of Words") - return - - for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards) - if(board.challenge) - tgui_alert(usr, "War has already been declared!", "War Was Declared") - return - - war_was_declared(memo = war_declaration) - -/obj/item/nuclear_challenge/proc/war_was_declared(mob/living/user, memo) - priority_announce( - text = memo, - title = "Declaration of War", - sound = 'sound/machines/alarm.ogg', - has_important_message = TRUE, - sender_override = "Nuclear Operative Outpost", - color_override = "red", - ) - if(user) - to_chat(user, "You've attracted the attention of powerful forces within the syndicate. \ - A bonus bundle of telecrystals has been granted to your team. Great things await you if you complete the mission.") - - distribute_tc() - CONFIG_SET(number/shuttle_refuel_delay, max(CONFIG_GET(number/shuttle_refuel_delay), CHALLENGE_SHUTTLE_DELAY)) - SSblackbox.record_feedback("amount", "nuclear_challenge_mode", 1) - - for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards) - board.challenge = TRUE - - for(var/obj/machinery/computer/camera_advanced/shuttle_docker/dock as anything in GLOB.jam_on_wardec) - dock.jammed = TRUE - - qdel(src) - -/obj/item/nuclear_challenge/proc/distribute_tc() - var/list/orphans = list() - var/list/uplinks = list() - - for (var/datum/mind/M in get_antag_minds(/datum/antagonist/nukeop)) - if (iscyborg(M.current)) - continue - var/datum/component/uplink/uplink = M.find_syndicate_uplink() - if (!uplink) - orphans += M.current - continue - uplinks += uplink - - var/tc_to_distribute = CHALLENGE_TELECRYSTALS - var/tc_per_nukie = round(tc_to_distribute / (length(orphans)+length(uplinks))) - - for (var/datum/component/uplink/uplink in uplinks) - uplink.add_telecrystals(tc_per_nukie) - tc_to_distribute -= tc_per_nukie - - for (var/mob/living/L in orphans) - var/TC = new /obj/item/stack/telecrystal(L.drop_location(), tc_per_nukie) - to_chat(L, span_warning("Your uplink could not be found so your share of the team's bonus telecrystals has been bluespaced to your [L.put_in_hands(TC) ? "hands" : "feet"].")) - tc_to_distribute -= tc_per_nukie - - if (tc_to_distribute > 0) // What shall we do with the remainder... - for (var/mob/living/basic/carp/pet/cayenne/C in GLOB.mob_living_list) - if (C.stat != DEAD) - var/obj/item/stack/telecrystal/TC = new(C.drop_location(), tc_to_distribute) - TC.throw_at(get_step(C, C.dir), 3, 3) - C.visible_message(span_notice("[C] coughs up a half-digested telecrystal"),span_notice("You cough up a half-digested telecrystal!")) - break - - -/obj/item/nuclear_challenge/proc/check_allowed(mob/living/user) - if(declaring_war) - to_chat(user, span_boldwarning("You are already in the process of declaring war! Make your mind up.")) - return FALSE - if(GLOB.player_list.len < CHALLENGE_MIN_PLAYERS) - to_chat(user, span_boldwarning("The enemy crew is too small to be worth declaring war on.")) - return FALSE - if(!user.onSyndieBase()) - to_chat(user, span_boldwarning("You have to be at your base to use this.")) - return FALSE - if(world.time - SSticker.round_start_time > CHALLENGE_TIME_LIMIT) - to_chat(user, span_boldwarning("It's too late to declare hostilities. Your benefactors are already busy with other schemes. You'll have to make do with what you have on hand.")) - return FALSE - for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards) - if(board.moved) - to_chat(user, span_boldwarning("The shuttle has already been moved! You have forfeit the right to declare war.")) - return FALSE - if(board.challenge) - to_chat(user, span_boldwarning("War has already been declared!")) - return FALSE - return TRUE - -/obj/item/nuclear_challenge/clownops - uplink_type = /obj/item/uplink/clownop - -/// Subtype that does nothing but plays the war op message. Intended for debugging -/obj/item/nuclear_challenge/literally_just_does_the_message - name = "\"Declaration of War\"" - desc = "It's a Syndicate Declaration of War thing-a-majig, but it only plays the loud sound and message. Nothing else." - var/admin_only = TRUE - -/obj/item/nuclear_challenge/literally_just_does_the_message/check_allowed(mob/living/user) - if(admin_only && !check_rights_for(user.client, R_SPAWN|R_FUN|R_DEBUG)) - to_chat(user, span_hypnophrase("You shouldn't have this!")) - return FALSE - - return TRUE - -/obj/item/nuclear_challenge/literally_just_does_the_message/war_was_declared(mob/living/user, memo) -#ifndef TESTING - // Reminder for our friends the admins - var/are_you_sure = tgui_alert(user, "Last second reminder that fake war declarations is a horrible idea and yes, \ - this does the whole shebang, so be careful what you're doing.", "Don't do it", list("I'm sure", "You're right")) - if(are_you_sure != "I'm sure") - return -#endif - - priority_announce( - text = memo, - title = "Declaration of War", - sound = 'sound/machines/alarm.ogg', - has_important_message = TRUE, - sender_override = "Nuclear Operative Outpost", - color_override = "red", - ) - -/obj/item/nuclear_challenge/literally_just_does_the_message/distribute_tc() - return - -#undef CHALLENGE_TELECRYSTALS -#undef CHALLENGE_TIME_LIMIT -#undef CHALLENGE_SHUTTLE_DELAY diff --git a/code/modules/antagonists/nukeop/outfits.dm b/code/modules/antagonists/nukeop/outfits.dm index e58507d31648..9aa08dcdbaa6 100644 --- a/code/modules/antagonists/nukeop/outfits.dm +++ b/code/modules/antagonists/nukeop/outfits.dm @@ -79,6 +79,10 @@ /obj/item/pen/edagger = 1, ) +/datum/outfit/syndicate/full/loneop + name = "Syndicate Operative - Full Kit (Loneop)" + uplink_type = /obj/item/uplink/loneop + /datum/outfit/syndicate/full/plasmaman name = "Syndicate Operative - Full Kit (Plasmaman)" back = /obj/item/mod/control/pre_equipped/nuclear/plasmaman @@ -163,3 +167,22 @@ shoes = /obj/item/clothing/shoes/laceup glasses = /obj/item/clothing/glasses/sunglasses/big faction = "MI13" + +/datum/outfit/nuclear_operative + name = "Nuclear Operative (Preview only)" + + back = /obj/item/mod/control/pre_equipped/empty/syndicate + uniform = /obj/item/clothing/under/syndicate + +/datum/outfit/nuclear_operative_elite + name = "Nuclear Operative (Elite, Preview only)" + + back = /obj/item/mod/control/pre_equipped/empty/elite + uniform = /obj/item/clothing/under/syndicate + l_hand = /obj/item/modular_computer/pda/nukeops + r_hand = /obj/item/shield/energy + +/datum/outfit/nuclear_operative_elite/post_equip(mob/living/carbon/human/H, visuals_only) + var/obj/item/shield/energy/shield = locate() in H.held_items + shield.icon_state = "[shield.base_icon_state]1" + H.update_held_items() diff --git a/code/modules/antagonists/obsessed/obsessed.dm b/code/modules/antagonists/obsessed/obsessed.dm index e83f96dd6065..9595463c1ebb 100644 --- a/code/modules/antagonists/obsessed/obsessed.dm +++ b/code/modules/antagonists/obsessed/obsessed.dm @@ -12,6 +12,7 @@ suicide_cry = "FOR MY LOVE!!" preview_outfit = /datum/outfit/obsessed hardcore_random_bonus = TRUE + stinger_sound = 'sound/ambience/antag/creepalert.ogg' var/datum/brain_trauma/special/obsessed/trauma /// Dummy antag datum that will show the cured obsessed to admins diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm index 6e494334552c..d70511038ab6 100644 --- a/code/modules/antagonists/revolution/revolution.dm +++ b/code/modules/antagonists/revolution/revolution.dm @@ -6,6 +6,7 @@ antag_moodlet = /datum/mood_event/revolution antag_hud_name = "rev" suicide_cry = "VIVA LA REVOLUTION!!" + stinger_sound = 'sound/ambience/antag/revolutionary_tide.ogg' var/datum/team/revolution/rev_team /// When this antagonist is being de-antagged, this is the source. Can be a mob (for mindshield/blunt force trauma) or a #define string. diff --git a/code/modules/antagonists/revolution/revolution_handler.dm b/code/modules/antagonists/revolution/revolution_handler.dm index d3bdbf972693..1b68205e36ea 100644 --- a/code/modules/antagonists/revolution/revolution_handler.dm +++ b/code/modules/antagonists/revolution/revolution_handler.dm @@ -154,6 +154,6 @@ GLOBAL_DATUM(revolution_handler, /datum/revolution_handler) return FALSE if(candidate.assigned_role.job_flags & JOB_HEAD_OF_STAFF) return FALSE - if(HAS_MIND_TRAIT(candidate.current, TRAIT_UNCONVERTABLE)) - return FALSE + // if(HAS_MIND_TRAIT(candidate.current, TRAIT_UNCONVERTABLE)) + // return FALSE return TRUE diff --git a/code/modules/antagonists/traitor/contractor/contract_teammate.dm b/code/modules/antagonists/traitor/contractor/contract_teammate.dm index d48d86269ecb..c62e8aee7942 100644 --- a/code/modules/antagonists/traitor/contractor/contract_teammate.dm +++ b/code/modules/antagonists/traitor/contractor/contract_teammate.dm @@ -1,32 +1,3 @@ -///Spawns a contractor partner to a spawning user, with a given key to assign to the new player. -/proc/spawn_contractor_partner(mob/living/user, key) - var/mob/living/carbon/human/partner = new() - var/datum/outfit/contractor_partner/partner_outfit = new() - - partner_outfit.equip(partner) - - var/obj/structure/closet/supplypod/arrival_pod = new(null, /datum/pod_style/syndicate) // Non-module change : removed upstream - arrival_pod.explosionSize = list(0,0,0,1) - arrival_pod.bluespace = TRUE - - var/turf/free_location = find_obstruction_free_location(2, user) - - // We really want to send them - if we can't find a nice location just land it on top of them. - if (!free_location) - free_location = get_turf(user) - - partner.forceMove(arrival_pod) - partner.ckey = key - - /// We give a reference to the mind that'll be the support unit - var/datum/antagonist/traitor/contractor_support/new_datum = partner.mind.add_antag_datum(/datum/antagonist/traitor/contractor_support) - - to_chat(partner, "\n[span_alertwarning("[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.")]") - to_chat(partner, "[span_alertwarning("Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.")]") - - new /obj/effect/pod_landingzone(free_location, arrival_pod) - return new_datum - /// Support unit gets it's own very basic antag datum for admin logging. /datum/antagonist/traitor/contractor_support name = "Contractor Support Unit" diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index df36231c572a..9a71a5c3e51e 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -17,7 +17,7 @@ can_assign_self_objectives = TRUE default_custom_objective = "Perform an overcomplicated heist on valuable Nanotrasen assets." hardcore_random_bonus = TRUE - stinger_sound = 'sound/music/antag/traitor/tatoralert.ogg' + stinger_sound = 'sound/ambience/antag/tatoralert.ogg' ///The flag of uplink that this traitor is supposed to have. var/uplink_flag_given = UPLINK_TRAITORS @@ -222,7 +222,7 @@ . = ..() if (!.) return - owner.current.playsound_local(get_turf(owner.current), 'sound/music/antag/traitor/final_objective.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) + owner.current.playsound_local(get_turf(owner.current), 'sound/traitor/final_objective.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) /datum/antagonist/traitor/ui_static_data(mob/user) var/datum/component/uplink/uplink = uplink_ref?.resolve() @@ -278,7 +278,7 @@ if(uplink_owned) var/uplink_text = "(used [used_telecrystals] TC) [purchases]" if((used_telecrystals == 0) && traitor_won) - var/static/icon/badass = icon('icons/ui/antags/badass.dmi', "badass") + var/static/icon/badass = icon('icons/ui_icons/antags/badass.dmi', "badass") uplink_text += "[icon2html(badass, world)]" result += uplink_text @@ -293,7 +293,7 @@ result += span_greentext("The [special_role_text] was successful!") else result += span_redtext("The [special_role_text] has failed!") - SEND_SOUND(owner.current, 'sound/ambience/misc/ambifailure.ogg') + SEND_SOUND(owner.current, 'sound/ambience/ambifailure.ogg') return result.Join("
") diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm index cbbf82e9c8ae..1f3445fd12d0 100644 --- a/code/modules/antagonists/wizard/wizard.dm +++ b/code/modules/antagonists/wizard/wizard.dm @@ -123,7 +123,7 @@ GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key) if(!owner.current) return if(!GLOB.wizardstart.len) - SSjob.SendToLateJoin(owner.current) + SSjob.send_to_late_join(owner.current) to_chat(owner, "HOT INSERTION, GO GO GO") owner.current.forceMove(pick(GLOB.wizardstart)) diff --git a/code/modules/client/preferences/sounds.dm b/code/modules/client/preferences/sounds.dm index 61161db90b7e..5f3721a2fbe1 100644 --- a/code/modules/client/preferences/sounds.dm +++ b/code/modules/client/preferences/sounds.dm @@ -53,11 +53,17 @@ /datum/preference/choiced/sound_tts/create_default_value() return TTS_SOUND_ENABLED +/datum/preference/choiced/sound_tts/is_accessible(datum/preferences/preferences) + return ..() && SStts.tts_enabled + /datum/preference/numeric/volume/sound_tts_volume category = PREFERENCE_CATEGORY_GAME_PREFERENCES savefile_key = "sound_tts_volume" savefile_identifier = PREFERENCE_PLAYER +/datum/preference/numeric/volume/sound_tts_volume/is_accessible(datum/preferences/preferences) + return ..() && SStts.tts_enabled + /datum/preference/choiced/sound_achievement category = PREFERENCE_CATEGORY_GAME_PREFERENCES savefile_key = "sound_achievement" diff --git a/code/modules/clothing/head/mind_monkey_helmet.dm b/code/modules/clothing/head/mind_monkey_helmet.dm index 2ffdc035198f..c169e7fc74d2 100644 --- a/code/modules/clothing/head/mind_monkey_helmet.dm +++ b/code/modules/clothing/head/mind_monkey_helmet.dm @@ -49,19 +49,18 @@ playsound(src, 'sound/machines/ping.ogg', 30, TRUE) RegisterSignal(magnification, COMSIG_SPECIES_LOSS, PROC_REF(make_fall_off)) polling = TRUE - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a mind magnified monkey?", check_jobban = ROLE_MONKEY_HELMET, poll_time = 5 SECONDS, target_mob = magnification, ignore_category = POLL_IGNORE_MONKEY_HELMET, pic_source = magnification, role_name_text = "mind-magnified monkey") + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_MONKEY_HELMET, poll_time = 5 SECONDS, checked_target = magnification, ignore_category = POLL_IGNORE_MONKEY_HELMET, alert_pic = magnification, role_name_text = "mind-magnified monkey") polling = FALSE if(!magnification) return - if(!candidates.len) + if(isnull(chosen_one)) UnregisterSignal(magnification, COMSIG_SPECIES_LOSS) magnification = null visible_message(span_notice("[src] falls silent and drops on the floor. Maybe you should try again later?")) playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) user.dropItemToGround(src) return - var/mob/picked = pick(candidates) - magnification.key = picked.key + magnification.key = chosen_one.key playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE) to_chat(magnification, span_notice("You're a mind magnified monkey! Protect your helmet with your life- if you lose it, your sentience goes with it!")) var/policy = get_policy(ROLE_MONKEY_HELMET) diff --git a/code/modules/events/ghost_role/operative.dm b/code/modules/events/ghost_role/operative.dm index db84410a1c67..5ea7b39f872d 100644 --- a/code/modules/events/ghost_role/operative.dm +++ b/code/modules/events/ghost_role/operative.dm @@ -15,16 +15,12 @@ fakeable = FALSE /datum/round_event/ghost_role/operative/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_OPERATIVE, role = ROLE_LONE_OPERATIVE, pic_source = /obj/machinery/nuclearbomb) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_OPERATIVE, role = ROLE_LONE_OPERATIVE, alert_pic = /obj/machinery/nuclearbomb, amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick_n_take(candidates) - var/spawn_location = find_space_spawn() if(isnull(spawn_location)) return MAP_ERROR - var/mob/living/carbon/human/operative = new(spawn_location) operative.randomize_human_appearance(~RANDOMIZE_SPECIES) operative.dna.update_dna_identity() diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index 8ebd30ad7b3e..53062aa48f88 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -49,14 +49,14 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( priority_announce(sentience_report,"[command_name()] Medium-Priority Update") /datum/round_event/ghost_role/sentience/spawn_role() - var/list/mob/dead/observer/candidates - candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, pic_source = /obj/item/slimepotion/slime/sentience, role_name_text = role_name) + var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, alert_pic = /obj/item/slimepotion/slime/sentience, role_name_text = role_name) + if(!length(candidates)) + return NOT_ENOUGH_PLAYERS // find our chosen mob to breathe life into // Mobs have to be simple animals, mindless, on station, and NOT holograms. // prioritize starter animals that people will recognise - var/list/potential = list() var/list/hi_pri = list() diff --git a/code/modules/events/ghost_role/sentient_disease.dm b/code/modules/events/ghost_role/sentient_disease.dm index 44f78d7c08cc..1e8c5832ed90 100644 --- a/code/modules/events/ghost_role/sentient_disease.dm +++ b/code/modules/events/ghost_role/sentient_disease.dm @@ -13,14 +13,19 @@ role_name = "sentient disease" /datum/round_event/ghost_role/sentient_disease/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, pic_source = /obj/structure/sign/warning/biohazard, role_name_text = role_name) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates( + poll_time = 15 SECONDS, + check_jobban = ROLE_ALIEN, + role = ROLE_ALIEN, + alert_pic = /obj/structure/sign/warning/biohazard, + role_name_text = role_name, + amount_to_pick = 1, + ) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - var/mob/dead/observer/selected = pick_n_take(candidates) - var/mob/camera/disease/virus = new /mob/camera/disease(SSmapping.get_station_center()) - virus.key = selected.key + virus.key = chosen_one.key INVOKE_ASYNC(virus, TYPE_PROC_REF(/mob/camera/disease, pick_name)) message_admins("[ADMIN_LOOKUPFLW(virus)] has been made into a sentient disease by an event.") virus.log_message("was spawned as a sentient disease by an event.", LOG_GAME) diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm index 7c89ff101752..f3a98984a970 100644 --- a/code/modules/events/holiday/vday.dm +++ b/code/modules/events/holiday/vday.dm @@ -77,7 +77,7 @@ poll_time = 30 SECONDS, flash_window = FALSE, start_signed_up = TRUE, - pic_source = /obj/item/storage/fancy/heart_box, + alert_pic = /obj/item/storage/fancy/heart_box, custom_response_messages = list( POLL_RESPONSE_SIGNUP = "You have signed up for a date!", POLL_RESPONSE_ALREADY_SIGNED = "You are already signed up for a date.", diff --git a/code/modules/events/holiday/xmas.dm b/code/modules/events/holiday/xmas.dm index cad343f7debd..35ae6a7b3b97 100644 --- a/code/modules/events/holiday/xmas.dm +++ b/code/modules/events/holiday/xmas.dm @@ -78,17 +78,14 @@ description = "Spawns santa, who shall roam the station, handing out gifts." /datum/round_event/santa - var/mob/living/carbon/human/santa //who is our santa? /datum/round_event/santa/announce(fake) priority_announce("Santa is coming to town!", "Unknown Transmission") -/datum/round_event/santa/start() - var/list/candidates = SSpolling.poll_ghost_candidates("Santa is coming to town! Do you want to be Santa?", poll_time = 15 SECONDS, pic_source = /obj/item/clothing/head/costume/santa, role_name_text = "santa") - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - santa = new /mob/living/carbon/human(pick(GLOB.blobstart)) - santa.key = C.key - - var/datum/antagonist/santa/A = new - santa.mind.add_antag_datum(A) +/datum/round_event/ghost_role/santa/start() + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Santa is coming to town! Do you want to be [span_notice("Santa")]?", poll_time = 15 SECONDS, alert_pic = /obj/item/clothing/head/costume/santa, role_name_text = "Santa", amount_to_pick = 1) + if(isnull(chosen_one)) + return NOT_ENOUGH_PLAYERS + var/mob/living/carbon/human/santa = new(pick(GLOB.blobstart)) + santa.key = chosen_one.key + santa.mind.add_antag_datum(/datum/antagonist/santa) diff --git a/code/modules/events/wizard/imposter.dm b/code/modules/events/wizard/imposter.dm index 6c6f3f84b3e7..914780d6d787 100644 --- a/code/modules/events/wizard/imposter.dm +++ b/code/modules/events/wizard/imposter.dm @@ -13,20 +13,18 @@ if(!ishuman(M.current)) continue var/mob/living/carbon/human/W = M.current - var/list/candidates = SSpolling.poll_ghost_candidates("Would you like to be an imposter wizard?", check_jobban = ROLE_WIZARD, pic_source = /obj/item/clothing/head/wizard, role_name_text = "imposter wizard") - if(!length(candidates)) + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Would you like to be an [span_notice("imposter wizard")]?", check_jobban = ROLE_WIZARD, alert_pic = /obj/item/clothing/head/wizard, jump_target = W, role_name_text = "imposter wizard", amount_to_pick = 1) + if(isnull(chosen_one)) return //Sad Trombone - var/mob/dead/observer/C = pick(candidates) new /obj/effect/particle_effect/fluid/smoke(W.loc) - var/mob/living/carbon/human/I = new /mob/living/carbon/human(W.loc) W.dna.transfer_identity(I, transfer_SE=1) I.real_name = I.dna.real_name I.name = I.dna.real_name I.updateappearance(mutcolor_update=1) I.domutcheck() - I.key = C.key + I.key = chosen_one.key var/datum/antagonist/wizard/master = M.has_antag_datum(/datum/antagonist/wizard) if(!master.wiz_team) master.create_wiz_team() diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 2b4c274ff442..7c4ee0ed3ca6 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -658,3 +658,7 @@ /// Called when a mob that has this job is admin respawned /datum/job/proc/on_respawn(mob/new_character) SSjob.equip_rank(new_character, new_character.mind.assigned_role, new_character.client) + +/// This proc may be called when someone of this job is made into a traitor to create custom objectives related to the job. +/datum/job/proc/generate_traitor_objective() + return null diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm index 964cf1109c59..1b6de1f11ff1 100644 --- a/code/modules/jobs/job_types/head_of_personnel.dm +++ b/code/modules/jobs/job_types/head_of_personnel.dm @@ -49,6 +49,45 @@ /datum/job/head_of_personnel/get_captaincy_announcement(mob/living/captain) return "Due to staffing shortages, newly promoted Acting Captain [captain.real_name] on deck!" +/datum/job/head_of_personnel/generate_traitor_objective() + var/datum/objective/assassinate/captain_replacement/promotion = new() + promotion.target = promotion.find_target() + if(isnull(promotion.target)) + qdel(promotion) + return null + + promotion.update_explanation_text() + return promotion + +/// Special assassination objective to kill the cap, take their id, and become the new captain +/datum/objective/assassinate/captain_replacement + name = "replace the captain" + admin_grantable = FALSE + +/datum/objective/assassinate/captain_replacement/update_explanation_text() + . = ..() + explanation_text = "Assassinate [target.name], the Captain, and steal [target.p_their()] ID card." + +/datum/objective/assassinate/captain_replacement/check_completion() + if(completed) + return TRUE + if(!..()) + return FALSE + + for(var/datum/mind/hop as anything in get_owners()) + if(!isliving(hop.current)) + continue + + if(locate(/obj/item/card/id/advanced/gold) in hop.current.get_all_contents()) + return TRUE + + return FALSE + +/datum/objective/assassinate/captain_replacement/find_target(dupe_search_range, list/blacklist) + for(var/datum/mind/fellow_head as anything in SSjob.get_all_heads() - blacklist) + if(is_captain_job(fellow_head.assigned_role)) + return fellow_head + return null /datum/outfit/job/hop name = "Head of Personnel" diff --git a/code/modules/logging/categories/log_category_game.dm b/code/modules/logging/categories/log_category_game.dm index 1d29f288984a..f337d5f821c7 100644 --- a/code/modules/logging/categories/log_category_game.dm +++ b/code/modules/logging/categories/log_category_game.dm @@ -12,6 +12,11 @@ config_flag = /datum/config_entry/flag/log_emote master_category = /datum/log_category/game +/datum/log_category/game_ghost_polls + category = LOG_CATEGORY_GAME_GHOST_POLLS + config_flag = /datum/config_entry/flag/log_ghost_poll + master_category = /datum/log_category/game + /datum/log_category/game_internet_request category = LOG_CATEGORY_GAME_INTERNET_REQUEST config_flag = /datum/config_entry/flag/log_internet_request diff --git a/code/modules/mapfluff/ruins/spaceruin_code/caravanambush.dm b/code/modules/mapfluff/ruins/spaceruin_code/caravanambush.dm index 7884b6852264..68b95a4e8473 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/caravanambush.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/caravanambush.dm @@ -56,14 +56,6 @@ shuttleId = "caravantrade1" possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_lavaland;caravantrade1_custom;caravantrade1_ambush" -/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/Initialize(mapload) - . = ..() - GLOB.jam_on_wardec += src - -/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/Destroy() - GLOB.jam_on_wardec -= src - return ..() - /obj/machinery/computer/camera_advanced/shuttle_docker/caravan/trade1 name = "Small Freighter Navigation Computer" desc = "Used to designate a precise transit location for the Small Freighter." diff --git a/code/modules/mapfluff/ruins/spaceruin_code/cyborgmothership.dm b/code/modules/mapfluff/ruins/spaceruin_code/cyborgmothership.dm index cad1c8488f91..463eda50f611 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/cyborgmothership.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/cyborgmothership.dm @@ -56,14 +56,6 @@ designate_time = 100 y_offset = -11 -/obj/machinery/computer/camera_advanced/shuttle_docker/cyborg_mothership/Initialize(mapload) - . = ..() - GLOB.jam_on_wardec += src - -/obj/machinery/computer/camera_advanced/shuttle_docker/cyborg_mothership/Destroy() - GLOB.jam_on_wardec -= src - return ..() - /obj/item/disk/holodisk/ruin/cyborg_mothership name = "Blackbox Print-out #101011" desc = "A rusty holodisk containing the last moments of #101011." diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 7fab5fd6a6fb..4f480074178c 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -186,7 +186,7 @@ CRASH("Failed to create a character for latejoin.") transfer_character() - SSjob.EquipRank(character, job, character.client) + SSjob.equip_rank(character, job, character.client) job.after_latejoin_spawn(character) #define IS_NOT_CAPTAIN 0 diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index f6e4967cf4e8..60ec939ffe62 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -52,7 +52,7 @@ GLOBAL_LIST_INIT(command_strings, list( ///All initial access this bot started with. var/list/initial_access = list() ///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION - var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION + var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT ///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_MAINTS_PANEL_OPEN | BOT_CONTROL_PANEL_OPEN | BOT_COVER_EMAGGED | BOT_COVER_HACKED var/bot_access_flags = NONE ///Small name of what the bot gets messed with when getting hacked/emagged. diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index 56bd95e178ed..6abee565b95e 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -348,7 +348,7 @@ ai_controller.set_blackboard_key(BB_CLEANBOT_EMAGGED_PHRASES, emagged_phrases) /mob/living/basic/bot/cleanbot/autopatrol - bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION + bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT /mob/living/basic/bot/cleanbot/medbay name = "Scrubs, MD" diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index edde350dc1ec..3f9b7c2e18aa 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -374,7 +374,7 @@ INVOKE_ASYNC(src, PROC_REF(speak), "Error! Surgical efficacy decreased to [round(heal_multiplier * 100)]%!") /mob/living/basic/bot/medbot/autopatrol - bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION + bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT /mob/living/basic/bot/medbot/stationary medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE diff --git a/code/modules/mob/living/basic/drone/extra_drone_types.dm b/code/modules/mob/living/basic/drone/extra_drone_types.dm index 927d28f0ca24..08c9278b7533 100644 --- a/code/modules/mob/living/basic/drone/extra_drone_types.dm +++ b/code/modules/mob/living/basic/drone/extra_drone_types.dm @@ -33,7 +33,7 @@ /mob/living/basic/drone/syndrone/Initialize(mapload) . = ..() var/datum/component/uplink/hidden_uplink = internal_storage.GetComponent(/datum/component/uplink) - hidden_uplink.set_telecrystals(telecrystal_count) + hidden_uplink.uplink_handler.set_telecrystals(telecrystal_count) /obj/effect/mob_spawn/ghost_role/drone/syndrone name = "syndrone shell" diff --git a/code/modules/mob/living/basic/guardian/guardian_creator.dm b/code/modules/mob/living/basic/guardian/guardian_creator.dm index 3f1f09275221..b88f3708ee82 100644 --- a/code/modules/mob/living/basic/guardian/guardian_creator.dm +++ b/code/modules/mob/living/basic/guardian/guardian_creator.dm @@ -52,11 +52,15 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial()) /mob/living/basic/guardian/standard, /mob/living/basic/guardian/support, ) + /// Have we been refunded? Used to prevent guardians from being created after we've been refunded + /// while avoiding scamming people if they use and then destroy us + var/was_refunded = FALSE /obj/item/guardian_creator/Initialize(mapload) . = ..() var/datum/guardian_fluff/using_theme = GLOB.guardian_themes[theme] mob_name = using_theme.name + RegisterSignal(src, COMSIG_ITEM_TC_REIMBURSED, PROC_REF(on_reimbursed)) /obj/item/guardian_creator/attack_self(mob/living/user) if(isguardian(user) && !allow_guardian) @@ -87,23 +91,32 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial()) used = TRUE to_chat(user, use_message) var/guardian_type_name = random ? "Random" : capitalize(initial(guardian_path.creator_name)) - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates( - "Do you want to play as [user.real_name]'s [guardian_type_name] [mob_name]?", + var/mob/chosen_one = SSpolling.poll_ghost_candidates( + "Do you want to play as [span_danger("[user.real_name]'s")] [span_notice("[guardian_type_name] [mob_name]")]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HOLOPARASITE, - pic_source = src, - role_name_text = "guardian spirit", + alert_pic = guardian_path, + jump_target = src, + role_name_text = guardian_type_name, + amount_to_pick = 1, ) - if(LAZYLEN(candidates)) - var/mob/dead/observer/candidate = pick(candidates) - spawn_guardian(user, candidate, guardian_path) + + if(was_refunded) + return + + if(chosen_one) + spawn_guardian(user, chosen_one, guardian_path) used = TRUE SEND_SIGNAL(src, COMSIG_TRAITOR_ITEM_USED(type)) else to_chat(user, failure_message) used = FALSE +/obj/item/guardian_creator/proc/on_reimbursed(datum/source) + SIGNAL_HANDLER + was_refunded = TRUE + /// Actually create our guy /obj/item/guardian_creator/proc/spawn_guardian(mob/living/user, mob/dead/candidate, guardian_path) if(QDELETED(user) || user.stat == DEAD) diff --git a/code/modules/mob/living/basic/guardian/guardian_verbs.dm b/code/modules/mob/living/basic/guardian/guardian_verbs.dm index 2f40da369f82..7c1a89132fef 100644 --- a/code/modules/mob/living/basic/guardian/guardian_verbs.dm +++ b/code/modules/mob/living/basic/guardian/guardian_verbs.dm @@ -169,20 +169,19 @@ return FALSE to_chat(owner, span_holoparasite("You attempt to reset [span_bold(chosen_guardian.real_name)]'s personality...")) - var/list/mob/dead/observer/ghost_candidates = SSpolling.poll_ghost_candidates("Do you want to play as [owner.real_name]'s [chosen_guardian.theme.name]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, pic_source = chosen_guardian, role_name_text = chosen_guardian.theme.name) - if (!LAZYLEN(ghost_candidates)) + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as [span_danger("[owner.real_name]'s")] [span_notice(chosen_guardian.theme.name)]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, alert_pic = chosen_guardian, jump_target = owner, role_name_text = chosen_guardian.theme.name, amount_to_pick = 1) + if(isnull(chosen_one)) to_chat(owner, span_holoparasite("Your attempt to reset the personality of \ [span_bold(chosen_guardian.real_name)] appears to have failed... \ Looks like you're stuck with it for now.")) StartCooldown() return FALSE - var/mob/dead/observer/candidate = pick(ghost_candidates) to_chat(chosen_guardian, span_holoparasite("Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")) to_chat(owner, span_boldholoparasite("The personality of [chosen_guardian.theme.name] has been successfully reset.")) - message_admins("[key_name_admin(candidate)] has taken control of ([ADMIN_LOOKUPFLW(chosen_guardian)])") + message_admins("[key_name_admin(chosen_one)] has taken control of ([ADMIN_LOOKUPFLW(chosen_guardian)])") chosen_guardian.ghostize(FALSE) - chosen_guardian.key = candidate.key + chosen_guardian.key = chosen_one.key COOLDOWN_START(chosen_guardian, resetting_cooldown, 5 MINUTES) chosen_guardian.guardian_rename() //give it a new color and name, to show it's a new person chosen_guardian.guardian_recolour() diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm index ea153b03c063..f8a3db0202a2 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm @@ -86,21 +86,11 @@ /// Handles giving the revenant a new client to control it /obj/item/ectoplasm/revenant/proc/get_new_user() message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to be [revenant.name] (reforming)?", check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, poll_time = 5 SECONDS, target_mob = revenant, pic_source = revenant) - - if(!LAZYLEN(candidates)) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to be [span_notice(revenant.name)] (reforming)?", check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, poll_time = 5 SECONDS, checked_target = revenant, alert_pic = revenant, role_name_text = "reforming revenant", chat_text_border_icon = revenant) + if(isnull(chosen_one)) message_admins("No candidates were found for the new revenant.") inert = TRUE visible_message(span_revenwarning("[src] settles down and seems lifeless.")) qdel(revenant) return null - - var/mob/dead/observer/potential_client = pick(candidates) - if(isnull(potential_client)) - qdel(revenant) - message_admins("No candidate was found for the new revenant. Oh well!") - inert = TRUE - visible_message(span_revenwarning("[src] settles down and seems lifeless.")) - return null - - return potential_client + return chosen_one diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index e26b182a4db6..588bd64958c7 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -2761,10 +2761,9 @@ GLOBAL_LIST_EMPTY(fire_appearances) if(isnull(guardian_client)) return else if(guardian_client == "Poll Ghosts") - var/list/candidates = SSpolling.poll_ghost_candidates("Do you want to play as an admin created Guardian Spirit of [real_name]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HOLOPARASITE, pic_source = src, role_name_text = "guardian spirit") - if(LAZYLEN(candidates)) - var/mob/dead/observer/candidate = pick(candidates) - guardian_client = candidate.client + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as an admin created [span_notice("Guardian Spirit")] of [span_danger(real_name)]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HOLOPARASITE, alert_pic = mutable_appearance('icons/mob/nonhuman-player/guardian.dmi', "magicexample"), jump_target = src, role_name_text = "guardian spirit", amount_to_pick = 1) + if(chosen_one) + guardian_client = chosen_one.client else tgui_alert(admin, "No ghost candidates.", "Guardian Controller") return diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index fa93f7b02978..700121ae3d3f 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -49,7 +49,7 @@ var/list/prev_access = list() ///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION - var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION + var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT ///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_COVER_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED var/bot_cover_flags = BOT_COVER_LOCKED diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 28ecc29c93e3..1a3799e250d2 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -11,7 +11,7 @@ radio_key = /obj/item/encryptionkey/headset_service //doesn't have security key radio_channel = RADIO_CHANNEL_SERVICE //Doesn't even use the radio anyway. bot_type = HONK_BOT - bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_AUTOPATROL | BOT_MODE_ROUNDSTART_POSSESSION + bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_AUTOPATROL hackables = "sound control systems" path_image_color = "#FF69B4" data_hud_type = TRAIT_SECURITY_HUD_ID_ONLY //show jobs diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index a5818ebb20c3..81144a20ab47 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -26,7 +26,6 @@ buckle_lying = 0 mob_size = MOB_SIZE_LARGE buckle_prevents_pull = TRUE // No pulling loaded shit - bot_mode_flags = ~BOT_MODE_ROUNDSTART_POSSESSION maints_access_required = list(ACCESS_ROBOTICS, ACCESS_CARGO) radio_key = /obj/item/encryptionkey/headset_cargo radio_channel = RADIO_CHANNEL_SUPPLY diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm index 4b9fe6a65827..e347ef0cf672 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm @@ -184,10 +184,10 @@ While using this makes the system rely on OnFire, it still gives options for tim addtimer(CALLBACK(src, PROC_REF(spawn_elite)), 3 SECONDS) return visible_message(span_boldwarning("Something within [src] stirs...")) - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a lavaland elite?", check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, poll_time = 5 SECONDS, target_mob = src, ignore_category = POLL_IGNORE_LAVALAND_ELITE, pic_source = src, role_name_text = "lavaland elite") - if(candidates.len) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, poll_time = 5 SECONDS, checked_target = src, ignore_category = POLL_IGNORE_LAVALAND_ELITE, alert_pic = src, role_name_text = "lavaland elite") + if(chosen_one) audible_message(span_boldwarning("The stirring sounds increase in volume!")) - elitemind = pick(candidates) + elitemind = chosen_one elitemind.playsound_local(get_turf(elitemind), 'sound/effects/magic.ogg', 40, 0) to_chat(elitemind, "You have been chosen to play as a Lavaland Elite.\nIn a few seconds, you will be summoned on Lavaland as a monster to fight your activator, in a fight to the death.\n\ Your attacks can be switched using the buttons on the top left of the HUD, and used by clicking on targets or tiles similar to a gun.\n\ diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 29cadca9861e..6c206813de7f 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -352,12 +352,11 @@ whomst += "Status: [span_boldnotice(english_list(M.mind?.get_special_roles()))]." var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [whomst]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, checked_target = M, alert_pic = M, role_name_text = "ghost control") - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + if(chosen_one) to_chat(M, "Your mob has been taken over by a ghost!") - message_admins("[key_name_admin(C)] has taken control of ([ADMIN_LOOKUPFLW(M)])") + message_admins("[key_name_admin(chosen_one)] has taken control of ([ADMIN_LOOKUPFLW(M)])") M.ghostize(FALSE) - M.key = C.key + M.key = chosen_one.key M.client?.init_verbs() return TRUE else diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 657056a288ee..2b888b81fee1 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -191,11 +191,10 @@ to_chat(src, "You are job banned from cyborg! Appeal your job ban if you want to avoid this in the future!") ghostize(FALSE) - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as [src]?", check_jobban = JOB_CYBORG, poll_time = 5 SECONDS, target_mob = src, pic_source = src, role_name_text = "cyborg") - if(LAZYLEN(candidates)) - var/mob/dead/observer/chosen_candidate = pick(candidates) - message_admins("[key_name_admin(chosen_candidate)] has taken control of ([key_name_admin(src)]) to replace a jobbanned player.") - key = chosen_candidate.key + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_notice(name)]?", check_jobban = JOB_CYBORG, poll_time = 5 SECONDS, checked_target = src, alert_pic = src, role_name_text = "cyborg") + if(chosen_one) + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(src)]) to replace a jobbanned player.") + key = chosen_one.key //human -> alien /mob/living/carbon/human/proc/Alienize() diff --git a/code/modules/modular_computers/computers/item/disks/virus_disk.dm b/code/modules/modular_computers/computers/item/disks/virus_disk.dm index 8ddb3c4fca39..28ceb2da54a1 100644 --- a/code/modules/modular_computers/computers/item/disks/virus_disk.dm +++ b/code/modules/modular_computers/computers/item/disks/virus_disk.dm @@ -151,7 +151,7 @@ hidden_uplink.uplink_handler.progression_points = min(SStraitor.current_global_progression, current_progression) SStraitor.register_uplink_handler(hidden_uplink.uplink_handler) else - hidden_uplink.add_telecrystals(telecrystals) + hidden_uplink.uplink_handler.add_telecrystals(telecrystals) telecrystals = 0 hidden_uplink.locked = FALSE hidden_uplink.active = TRUE diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index cfbfc81b3a56..73e0f3dc8f38 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -371,16 +371,11 @@ var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [whomst]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, checked_target = target, alert_pic = target, role_name_text = "bolt of possession") if(target.stat == DEAD)//boo. return - if(LAZYLEN(candidates)) - var/mob/dead/observer/ghost = pick(candidates) + if(chosen_one) to_chat(target, span_boldnotice("You have been noticed by a ghost and it has possessed you!")) - var/oldkey = target.key - target.ghostize(FALSE) - target.key = ghost.key - trauma.friend.key = oldkey - trauma.friend.reset_perspective(null) - trauma.friend.Show() - trauma.friend_initialized = TRUE + var/mob/dead/observer/ghosted_target = target.ghostize(FALSE) + target.key = chosen_one.key + trauma.add_friend(ghosted_target) else to_chat(target, span_notice("Your mind has managed to go unnoticed in the spirit world.")) qdel(trauma) diff --git a/code/modules/shuttle/battlecruiser_starfury.dm b/code/modules/shuttle/battlecruiser_starfury.dm index e95ff4243f5d..a27cadacad2e 100644 --- a/code/modules/shuttle/battlecruiser_starfury.dm +++ b/code/modules/shuttle/battlecruiser_starfury.dm @@ -135,7 +135,7 @@ */ /proc/summon_battlecruiser(datum/team/battlecruiser/team) - var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for battlecruiser crew?", check_jobban = ROLE_TRAITOR, pic_source = /obj/machinery/sleeper/syndie, role_name_text = "battlecruiser crew") + var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for [span_notice("battlecruiser crew")]?", check_jobban = ROLE_TRAITOR, alert_pic = /obj/machinery/sleeper/syndie, role_name_text = "battlecruiser crew") shuffle_inplace(candidates) var/datum/map_template/ship = SSmapping.map_templates["battlecruiser_starfury.dmm"] diff --git a/code/modules/shuttle/shuttle_events/player_controlled.dm b/code/modules/shuttle/shuttle_events/player_controlled.dm deleted file mode 100644 index 77fee390a687..000000000000 --- a/code/modules/shuttle/shuttle_events/player_controlled.dm +++ /dev/null @@ -1,75 +0,0 @@ -///Mobs spawned with this one are automatically player controlled, if possible -/datum/shuttle_event/simple_spawner/player_controlled - spawning_list = list(/mob/living/basic/carp) - - ///If we cant find a ghost, do we spawn them anyway? Otherwise they go in the garbage bin - var/spawn_anyway_if_no_player = FALSE - - var/ghost_alert_string = "Would you like to be shot at the shuttle?" - - var/role_type = ROLE_SENTIENCE - -/datum/shuttle_event/simple_spawner/player_controlled/spawn_movable(spawn_type) - if(ispath(spawn_type, /mob/living)) - INVOKE_ASYNC(src, PROC_REF(try_grant_ghost_control), spawn_type) - else - ..() - -/// Attempt to grant control of a mob to ghosts before spawning it in. if spawn_anyway_if_no_player = TRUE, we spawn the mob even if there's no ghosts -/datum/shuttle_event/simple_spawner/player_controlled/proc/try_grant_ghost_control(spawn_type) - var/list/candidates = SSpolling.poll_ghost_candidates(ghost_alert_string + " (Warning: you will not be able to return to your body!)", check_jobban = role_type, poll_time = 10 SECONDS, pic_source = spawn_type, role_name_text = "shot at shuttle") - - if(!candidates.len && !spawn_anyway_if_no_player) - return - - var/mob/living/new_mob = new spawn_type (get_turf(get_spawn_turf())) - - if(candidates.len) - var/mob/dead/observer/candidate = pick(candidates) - new_mob.ckey = candidate.ckey - post_spawn(new_mob) - -///BACK FOR REVENGE!!! -/datum/shuttle_event/simple_spawner/player_controlled/alien_queen - name = "ALIEN QUEEN! (Kinda dangerous!)" - spawning_list = list(/mob/living/carbon/alien/adult/royal/queen = 1, /obj/vehicle/sealed/mecha/ripley = 1) - spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE - - event_probability = 0.2 - spawn_probability_per_process = 10 - activation_fraction = 0.5 - - spawn_anyway_if_no_player = FALSE - ghost_alert_string = "Would you like to be an alien queen shot at the shuttle?" - remove_from_list_when_spawned = TRUE - self_destruct_when_empty = TRUE - - role_type = ROLE_ALIEN - -///Spawns three player controlled carp!! Deadchats final chance to wreak havoc, probably really not that dangerous if even one person has a laser gun -/datum/shuttle_event/simple_spawner/player_controlled/carp - name = "Three player controlled carp! (Little dangerous!)" - spawning_list = list(/mob/living/basic/carp = 10, /mob/living/basic/carp/mega = 2, /mob/living/basic/carp/magic = 2, /mob/living/basic/carp/magic/chaos = 1) - spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE - - event_probability = 1 - spawn_probability_per_process = 10 - activation_fraction = 0.4 - - spawn_anyway_if_no_player = TRUE - ghost_alert_string = "Would you like to be a space carp to pester the emergency shuttle?" - remove_from_list_when_spawned = TRUE - self_destruct_when_empty = TRUE - - role_type = ROLE_SENTIENCE - - ///how many carp can we spawn max? - var/max_carp_spawns = 3 - -/datum/shuttle_event/simple_spawner/player_controlled/carp/New(obj/docking_port/mobile/port) - . = ..() - - var/list/spawning_list_copy = spawning_list.Copy() - spawning_list.Cut() - for(var/i in 1 to max_carp_spawns) - spawning_list[pick_weight(spawning_list_copy)] += 1 diff --git a/code/modules/shuttle/white_ship.dm b/code/modules/shuttle/white_ship.dm index b222fde787b0..a19492e43aa1 100644 --- a/code/modules/shuttle/white_ship.dm +++ b/code/modules/shuttle/white_ship.dm @@ -26,11 +26,3 @@ x_offset = -6 y_offset = -10 designate_time = 100 - -/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/Initialize(mapload) - . = ..() - GLOB.jam_on_wardec += src - -/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/Destroy() - GLOB.jam_on_wardec -= src - return ..() diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm index d50766a56a95..cad4e5569eab 100644 --- a/code/modules/uplink/uplink_devices.dm +++ b/code/modules/uplink/uplink_devices.dm @@ -68,6 +68,10 @@ var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) hidden_uplink.allow_restricted = FALSE +///A subtype used for lone ops, with some of the stuff they shouldn't/can't access removed from purchase. +/obj/item/uplink/loneop + uplink_flag = UPLINK_LONE_OP + /obj/item/uplink/clownop uplink_flag = UPLINK_CLOWN_OPS diff --git a/code/modules/uplink/uplink_items/contractor.dm b/code/modules/uplink/uplink_items/contractor.dm index 7bf37f1b777e..68229d66c8cf 100644 --- a/code/modules/uplink/uplink_items/contractor.dm +++ b/code/modules/uplink/uplink_items/contractor.dm @@ -82,28 +82,9 @@ cost = 1 /datum/uplink_item/contractor/partner - name = "Reinforcements" - desc = "Upon purchase we'll contact available units in the area. Should there be an agent free, \ - we'll send them down to assist you immediately. If no units are free, we give a full refund." - item = /obj/effect/gibspawner/generic + name = "Contractor Reinforcement" + desc = "A reinforcement operative will be sent to aid you in your goals, \ + they are paid separately, and will not take a cut from your profits." + item = /obj/item/antag_spawner/loadout/contractor limited_stock = 1 cost = 2 - -/datum/uplink_item/contractor/partner/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) - to_chat(user, span_notice("The uplink vibrates quietly, connecting to nearby agents...")) - var/list/candidates = SSpolling.poll_ghost_candidates( - question = "Do you want to play as the Contractor Support Unit for [user.real_name]?", - check_jobban = ROLE_TRAITOR, - role = ROLE_TRAITOR, - poll_time = 10 SECONDS, - ignore_category = POLL_IGNORE_CONTRACTOR_SUPPORT, - pic_source = /obj/item/modular_computer/pda/syndicate_contract_uplink, - role_name_text = "contractor support unit", - ) - if(!LAZYLEN(candidates)) - to_chat(user, span_notice("No available agents at this time, please try again later.")) - limited_stock++ - return //bobux no icon - var/mob/dead/observer/selected_player = pick(candidates) - uplink_handler.contractor_hub.contractor_teammate = spawn_contractor_partner(user, selected_player.key) - return source //for log icon diff --git a/config/logging.txt b/config/logging.txt index 2635197a7966..28dc901bb3d9 100644 --- a/config/logging.txt +++ b/config/logging.txt @@ -29,6 +29,9 @@ LOG_ECON ## log emotes LOG_EMOTE +## log ghost polling +LOG_GHOST_POLL + ## log game actions (start of round, results, etc.) LOG_GAME diff --git a/maplestation.dme b/maplestation.dme index fd017907871d..fb72ce27c06c 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -88,7 +88,6 @@ #include "code\__DEFINES\door.dm" #include "code\__DEFINES\drone.dm" #include "code\__DEFINES\dye_keys.dm" -#include "code\__DEFINES\dynamic.dm" #include "code\__DEFINES\economy.dm" #include "code\__DEFINES\electrified_buckle.dm" #include "code\__DEFINES\events.dm" @@ -718,16 +717,15 @@ #include "code\controllers\subsystem\wardrobe.dm" #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\wiremod_composite.dm" +#include "code\controllers\subsystem\dynamic\__dynamic_defines.dm" +#include "code\controllers\subsystem\dynamic\_dynamic_ruleset.dm" +#include "code\controllers\subsystem\dynamic\_dynamic_tier.dm" #include "code\controllers\subsystem\dynamic\dynamic.dm" -#include "code\controllers\subsystem\dynamic\dynamic_hijacking.dm" -#include "code\controllers\subsystem\dynamic\dynamic_logging.dm" -#include "code\controllers\subsystem\dynamic\dynamic_midround_rolling.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_latejoin.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_midround.dm" -#include "code\controllers\subsystem\dynamic\dynamic_rulesets_roundstart.dm" -#include "code\controllers\subsystem\dynamic\dynamic_unfavorable_situation.dm" -#include "code\controllers\subsystem\dynamic\ruleset_picking.dm" +#include "code\controllers\subsystem\dynamic\dynamic_admin.dm" +#include "code\controllers\subsystem\dynamic\dynamic_ruleset_latejoin.dm" +#include "code\controllers\subsystem\dynamic\dynamic_ruleset_midround.dm" +#include "code\controllers\subsystem\dynamic\dynamic_ruleset_roundstart.dm" +#include "code\controllers\subsystem\dynamic\dynamic_testing.dm" #include "code\controllers\subsystem\movement\ai_movement.dm" #include "code\controllers\subsystem\movement\cliff_falling.dm" #include "code\controllers\subsystem\movement\hyperspace_drift.dm" @@ -780,6 +778,7 @@ #include "code\datums\chat_payload.dm" #include "code\datums\chatmessage.dm" #include "code\datums\cogbar.dm" +#include "code\datums\communications.dm" #include "code\datums\dash_weapon.dm" #include "code\datums\datum.dm" #include "code\datums\datumvars.dm" @@ -2710,7 +2709,6 @@ #include "code\game\objects\structures\spawner.dm" #include "code\game\objects\structures\spirit_board.dm" #include "code\game\objects\structures\stairs.dm" -#include "code\game\objects\structures\syndicate_uplink_beacon.dm" #include "code\game\objects\structures\table_frames.dm" #include "code\game\objects\structures\tables_racks.dm" #include "code\game\objects\structures\tank_dispenser.dm" @@ -3233,7 +3231,6 @@ #include "code\modules\antagonists\nukeop\datums\operative_team.dm" #include "code\modules\antagonists\nukeop\equipment\borgchameleon.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_authentication_disk.dm" -#include "code\modules\antagonists\nukeop\equipment\nuclear_challenge.dm" #include "code\modules\antagonists\nukeop\equipment\pinpointer.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_bomb\_nuclear_bomb.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_bomb\beer_nuke.dm" @@ -3242,7 +3239,6 @@ #include "code\modules\antagonists\obsessed\obsessed.dm" #include "code\modules\antagonists\paradox_clone\paradox_clone.dm" #include "code\modules\antagonists\pirate\pirate.dm" -#include "code\modules\antagonists\pirate\pirate_event.dm" #include "code\modules\antagonists\pirate\pirate_gangs.dm" #include "code\modules\antagonists\pirate\pirate_outfits.dm" #include "code\modules\antagonists\pirate\pirate_roles.dm" @@ -3253,6 +3249,7 @@ #include "code\modules\antagonists\revenant\revenant_blight.dm" #include "code\modules\antagonists\revolution\enemy_of_the_state.dm" #include "code\modules\antagonists\revolution\revolution.dm" +#include "code\modules\antagonists\revolution\revolution_handler.dm" #include "code\modules\antagonists\santa\santa.dm" #include "code\modules\antagonists\sentient_creature\sentient_creature.dm" #include "code\modules\antagonists\separatist\nation_creation.dm" @@ -3266,39 +3263,13 @@ #include "code\modules\antagonists\spiders\spiders.dm" #include "code\modules\antagonists\survivalist\survivalist.dm" #include "code\modules\antagonists\syndicate_monkey\syndicate_monkey.dm" -#include "code\modules\antagonists\traitor\balance_helper.dm" #include "code\modules\antagonists\traitor\datum_traitor.dm" -#include "code\modules\antagonists\traitor\objective_category.dm" -#include "code\modules\antagonists\traitor\traitor_objective.dm" #include "code\modules\antagonists\traitor\uplink_handler.dm" #include "code\modules\antagonists\traitor\components\demoraliser.dm" -#include "code\modules\antagonists\traitor\components\traitor_objective_helpers.dm" -#include "code\modules\antagonists\traitor\components\traitor_objective_limit_per_time.dm" -#include "code\modules\antagonists\traitor\components\traitor_objective_mind_tracker.dm" #include "code\modules\antagonists\traitor\contractor\contract_teammate.dm" #include "code\modules\antagonists\traitor\contractor\contractor_hub.dm" #include "code\modules\antagonists\traitor\contractor\contractor_items.dm" #include "code\modules\antagonists\traitor\contractor\syndicate_contract.dm" -#include "code\modules\antagonists\traitor\objectives\assassination.dm" -#include "code\modules\antagonists\traitor\objectives\demoralise_assault.dm" -#include "code\modules\antagonists\traitor\objectives\destroy_heirloom.dm" -#include "code\modules\antagonists\traitor\objectives\destroy_item.dm" -#include "code\modules\antagonists\traitor\objectives\eyesnatching.dm" -#include "code\modules\antagonists\traitor\objectives\hack_comm_console.dm" -#include "code\modules\antagonists\traitor\objectives\infect.dm" -#include "code\modules\antagonists\traitor\objectives\kidnapping.dm" -#include "code\modules\antagonists\traitor\objectives\kill_pet.dm" -#include "code\modules\antagonists\traitor\objectives\locate_weakpoint.dm" -#include "code\modules\antagonists\traitor\objectives\sabotage_machinery.dm" -#include "code\modules\antagonists\traitor\objectives\steal.dm" -#include "code\modules\antagonists\traitor\objectives\abstract\target_player.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\battlecruiser.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\final_objective.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\infect_ai.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\objective_dark_matteor.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\romerol.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\space_dragon.dm" -#include "code\modules\antagonists\traitor\objectives\final_objective\supermatter_cascade.dm" #include "code\modules\antagonists\valentines\heartbreaker.dm" #include "code\modules\antagonists\valentines\valentine.dm" #include "code\modules\antagonists\venus_human_trap\venus_human_trap.dm" @@ -3980,7 +3951,6 @@ #include "code\modules\events\camerafailure.dm" #include "code\modules\events\carp_migration.dm" #include "code\modules\events\communications_blackout.dm" -#include "code\modules\events\creep_awakening.dm" #include "code\modules\events\disease_outbreak.dm" #include "code\modules\events\dust.dm" #include "code\modules\events\earthquake.dm" @@ -4003,7 +3973,6 @@ #include "code\modules\events\scrubber_overflow.dm" #include "code\modules\events\shuttle_catastrophe.dm" #include "code\modules\events\shuttle_insurance.dm" -#include "code\modules\events\spider_infestation.dm" #include "code\modules\events\stray_cargo.dm" #include "code\modules\events\supermatter_surge.dm" #include "code\modules\events\tram_malfunction.dm" @@ -4022,20 +3991,9 @@ #include "code\modules\events\anomaly\anomaly_pyro.dm" #include "code\modules\events\anomaly\anomaly_vortex.dm" #include "code\modules\events\ghost_role\_ghost_role.dm" -#include "code\modules\events\ghost_role\abductor.dm" -#include "code\modules\events\ghost_role\alien_infestation.dm" -#include "code\modules\events\ghost_role\blob.dm" -#include "code\modules\events\ghost_role\changeling_event.dm" -#include "code\modules\events\ghost_role\fugitive_event.dm" -#include "code\modules\events\ghost_role\morph_event.dm" -#include "code\modules\events\ghost_role\nightmare.dm" #include "code\modules\events\ghost_role\operative.dm" -#include "code\modules\events\ghost_role\revenant_event.dm" #include "code\modules\events\ghost_role\sentience.dm" #include "code\modules\events\ghost_role\sentient_disease.dm" -#include "code\modules\events\ghost_role\slaughter_event.dm" -#include "code\modules\events\ghost_role\space_dragon.dm" -#include "code\modules\events\ghost_role\space_ninja.dm" #include "code\modules\events\holiday\easter.dm" #include "code\modules\events\holiday\halloween.dm" #include "code\modules\events\holiday\vday.dm" @@ -4346,7 +4304,6 @@ #include "code\modules\jobs\job_types\antagonists\abductor_agent.dm" #include "code\modules\jobs\job_types\antagonists\abductor_scientist.dm" #include "code\modules\jobs\job_types\antagonists\abductor_solo.dm" -#include "code\modules\jobs\job_types\antagonists\clown_operative.dm" #include "code\modules\jobs\job_types\antagonists\lone_operative.dm" #include "code\modules\jobs\job_types\antagonists\morph.dm" #include "code\modules\jobs\job_types\antagonists\nightmare.dm" @@ -4583,6 +4540,7 @@ #include "code\modules\mapping\space_management\zlevel_manager.dm" #include "code\modules\meteors\meteor_changeling.dm" #include "code\modules\meteors\meteor_dark_matteor.dm" +#include "code\modules\meteors\meteor_mode_controller.dm" #include "code\modules\meteors\meteor_spawning.dm" #include "code\modules\meteors\meteor_types.dm" #include "code\modules\meteors\meteor_waves.dm" @@ -5776,7 +5734,6 @@ #include "code\modules\shuttle\shuttle_events\carp.dm" #include "code\modules\shuttle\shuttle_events\meteors.dm" #include "code\modules\shuttle\shuttle_events\misc.dm" -#include "code\modules\shuttle\shuttle_events\player_controlled.dm" #include "code\modules\shuttle\shuttle_events\turbulence.dm" #include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" diff --git a/maplestation_modules/code/modules/antagonists/advanced_cult/advanced_cult.dm b/maplestation_modules/code/modules/antagonists/advanced_cult/advanced_cult.dm index 296e2c5d0897..a41eb9816cba 100644 --- a/maplestation_modules/code/modules/antagonists/advanced_cult/advanced_cult.dm +++ b/maplestation_modules/code/modules/antagonists/advanced_cult/advanced_cult.dm @@ -2,7 +2,7 @@ /datum/antagonist/advanced_cult ui_name = null show_in_antagpanel = FALSE - job_rank = ROLE_CULTIST + pref_flag = ROLE_CULTIST roundend_category = "cultists" antagpanel_category = "Cult" suicide_cry = "FOR MY GOD!!" @@ -41,7 +41,6 @@ /datum/antagonist/advanced_cult/finalize_antag() cultist_style.on_cultist_made(src, owner.current) - owner.special_role = ROLE_CULTIST /datum/antagonist/advanced_cult/on_removal() cultist_style.on_cultist_lost(src, owner.current) diff --git a/sound/ambience/antag/abductee.ogg b/sound/ambience/antag/abductee.ogg new file mode 100644 index 0000000000000000000000000000000000000000..eb6c2f564b188257c627f7b391d28c41b40c5802 GIT binary patch literal 169081 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℘~GA0-({6M@0JI+mpy`h@aqcr@2_4a;VG|cHZ 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

mco%_1!`sK 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%0DRrH!eRuk4?gv3t%=?Eh-|Bx>-$bo-Y(0`)a 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 z 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


" + . += "

[command_name()], TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]


" + . += command_report_main_content || pick_list_replacements("flavor_reports.json", "reports") + if(CONFIG_GET(flag/no_dynamic_report)) + if(isnull(greenshift)) + greenshift = SSdynamic.current_tier.tier == 0 + else + var/dynamic_report = SSdynamic.get_advisory_report() + if(isnull(greenshift)) // if we're not forced to be greenshift or not - check if we are an actual greenshift + greenshift = SSdynamic.current_tier.tier == 0 && dynamic_report == /datum/dynamic_tier/greenshift::advisory_report - . = "Nanotrasen Department of Intelligence Threat Advisory, Spinward Sector, TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]:
" - . += dynamic_report + . += "

Nanotrasen Department of Intelligence Threat Advisory, Spinward Sector

" + . += dynamic_report SSstation.generate_station_goals(greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget)) - var/list/datum/station_goal/goals = SSstation.get_station_goals() - if(length(goals)) - var/list/texts = list("
Special Orders for [station_name()]:
") - for(var/datum/station_goal/station_goal as anything in goals) + var/list/station_goal_strings = list() + if(greenshift) + station_goal_strings += "All special orders have been authorized for the shift. \ + Feel free to pick one your crew wishes to specialize in - you are not expected to complete them all." + + else + for(var/datum/station_goal/station_goal as anything in SSstation.get_station_goals()) station_goal.on_report() - texts += station_goal.get_report() - . += texts.Join("
") + station_goal_strings += station_goal.get_report() + + if(length(station_goal_strings) > 0) // if we have any special orders to report, add them in + . += "

Special Orders for [station_name()]:

" + . += station_goal_strings.Join("
") var/list/trait_list_strings = list() for(var/datum/station_trait/station_trait as anything in SSstation.station_traits) @@ -107,7 +125,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n . += "
Additional Notes:

" + footnote_pile #ifndef MAP_TEST - print_command_report(., "[command_name()] Status Summary", announce=FALSE) + print_command_report(., "[command_name()] Status Summary", announce = FALSE, contains_advanced_html = TRUE) if(greenshift) priority_announce( "Thanks to the tireless efforts of our security and intelligence divisions, \ diff --git a/code/datums/components/transforming.dm b/code/datums/components/transforming.dm index d800ee041f7b..ab2528ea73da 100644 --- a/code/datums/components/transforming.dm +++ b/code/datums/components/transforming.dm @@ -154,7 +154,7 @@ toggle_active(source) if(!(SEND_SIGNAL(source, COMSIG_TRANSFORMING_ON_TRANSFORM, user, active) & COMPONENT_NO_DEFAULT_MESSAGE)) default_transform_message(source, user) - + SEND_SIGNAL(user, COMSIG_MOB_TRANSFORMING_ITEM, source, active) if(isnum(transform_cooldown_time)) COOLDOWN_START(src, transform_cooldown, transform_cooldown_time) if(user) diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm index f96d67a198b3..8caea05f7b3d 100644 --- a/code/modules/admin/verbs/adminevents.dm +++ b/code/modules/admin/verbs/adminevents.dm @@ -261,11 +261,12 @@ ADMIN_VERB(run_weather, R_FUN, "Run Weather", "Triggers specific weather on the log_admin("[key_name(user)] started weather of type [weather_type] on the z-level [z_level].") BLACKBOX_LOG_ADMIN_VERB("Run Weather") -ADMIN_VERB(command_report_footnote, R_ADMIN, "Command Report Footnote", "Adds a footnote to the roundstart command report.", ADMIN_CATEGORY_EVENTS) +ADMIN_VERB(command_report_footnote, R_FUN, "Command Report Footnote", "Adds a footnote to the roundstart command report.", ADMIN_CATEGORY_EVENTS) var/datum/command_footnote/command_report_footnote = new /datum/command_footnote() GLOB.communications_controller.block_command_report += 1 //Add a blocking condition to the counter until the inputs are done. - command_report_footnote.message = tgui_input_text(user, "This message will be attached to the bottom of the roundstart threat report. Be sure to delay the roundstart report if you need extra time.", "P.S.") + command_report_footnote.message = tgui_input_text(user, "This message will be attached to the bottom of the roundstart threat report. \ + Be sure to delay the roundstart report if you need extra time.", "P.S.") if(!command_report_footnote.message) GLOB.communications_controller.block_command_report -= 1 qdel(command_report_footnote) @@ -285,6 +286,12 @@ ADMIN_VERB(command_report_footnote, R_ADMIN, "Command Report Footnote", "Adds a var/message var/signature +ADMIN_VERB(command_report_content, R_FUN, "Command Report Content", "Sets the main content of the roundstart command report", ADMIN_CATEGORY_EVENTS) + var/content = tgui_input_text(user, "This message will be the main content of the roundstart command report, above the threat report (if enabled). \ + Be sure to delay the roundstart report if you need extra time to compose this message.", "To Whom It May Concern") + GLOB.communications_controller.command_report_main_content = content + message_admins("[key_name_admin(user)] has [content ? "set" : "cleared"] the main content of the roundstart command report.") + ADMIN_VERB(delay_command_report, R_FUN, "Delay Command Report", "Prevents the roundstart command report from being sent; or forces it to send it delayed.", ADMIN_CATEGORY_EVENTS) GLOB.communications_controller.block_command_report = !GLOB.communications_controller.block_command_report message_admins("[key_name_admin(user)] has [(GLOB.communications_controller.block_command_report ? "delayed" : "sent")] the roundstart command report.") diff --git a/code/modules/admin/verbs/commandreport.dm b/code/modules/admin/verbs/commandreport.dm index f714d430ab00..d05206994fd2 100644 --- a/code/modules/admin/verbs/commandreport.dm +++ b/code/modules/admin/verbs/commandreport.dm @@ -147,7 +147,7 @@ ADMIN_VERB(create_command_report, R_ADMIN, "Create Command Report", "Create a co priority_announce(command_report_content, subheader == ""? null : subheader, report_sound, has_important_message = TRUE, color_override = chosen_color) if(!announce_contents || print_report) - print_command_report(command_report_content, "[announce_contents ? "" : "Classified "][command_name] Update", !announce_contents) + print_command_report(command_report_content, "[announce_contents ? "" : "Classified "][command_name] Update", !announce_contents, contains_advanced_html = TRUE) change_command_name(original_command_name) diff --git a/code/modules/asset_cache/assets/paper.dm b/code/modules/asset_cache/assets/paper.dm index f97d25e2d5cc..ee88f8062476 100644 --- a/code/modules/asset_cache/assets/paper.dm +++ b/code/modules/asset_cache/assets/paper.dm @@ -1,5 +1,5 @@ -/datum/asset/spritesheet/simple/paper - name = "paper" +/datum/asset/spritesheet/simple/stamps + name = "stamps" assets = list( "stamp-clown" = 'icons/stamp_icons/large_stamp-clown.png', "stamp-deny" = 'icons/stamp_icons/large_stamp-deny.png', @@ -21,3 +21,9 @@ "stamp-bo" = 'maplestation_modules/icons/stamp_icons/large_stamp-bo.png', "stamp-ap" = 'maplestation_modules/icons/stamp_icons/large_stamp-ap.png', ) + +// Contains 256x128 versions of various in game company logos +/datum/asset/simple/logos + assets = list( + "nanotrasen-logo" = 'maplestation_modules/icons/nanotrasen-logo.png', + ) diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 99558d5ac500..62f4b8581089 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -66,6 +66,9 @@ ///If this paper can be selected as a candidate for a future message in a bottle when spawned outside of mapload. Doesn't affect manually doing that. var/can_become_message_in_bottle = TRUE + /// Assoc Lazylist of REF()s to mobs that are viewing the paper while holding a writing tool to what that tool's writing implement details are + VAR_FINAL/list/writers + /obj/item/paper/Initialize(mapload) . = ..() pixel_x = base_pixel_x + rand(-9, 9) @@ -503,7 +506,8 @@ /obj/item/paper/ui_assets(mob/user) return list( - get_asset_datum(/datum/asset/spritesheet/simple/paper), + get_asset_datum(/datum/asset/spritesheet/simple/stamps), + get_asset_datum(/datum/asset/simple/logos), ) /obj/item/paper/ui_interact(mob/user, datum/tgui/ui) @@ -511,8 +515,93 @@ return ui = SStgui.try_update_ui(user, src, ui) if(!ui) + /** + * these signals are for checking whether the ui viewer is holding a writing tool. + * (whether they are holding a writing tool matters for the state of the ui) + * + * we have to do this rigamarole, rather than just checking on ui_data calls, + * because if we set this ui to autoupdate, it causes weird rendering issues. + * rather than figure out why those are happening, it was easier to just turn off autoupdate. + */ + RegisterSignals(user, list( + COMSIG_MOB_UNEQUIPPED_ITEM, + COMSIG_MOB_EQUIPPED_ITEM, + COMSIG_MOB_SWAP_HANDS, + COMSIG_MOB_TRANSFORMING_ITEM, // specifically for pens + ), PROC_REF(viewer_writing_state_change)) + var/list/writing_info = get_viewer_writing_implement_details(user) + if(writing_info) + add_writer(user, writing_info, update = FALSE) + ui = new(user, src, "PaperSheet", name) ui.open() + // please see the above comment if you want to re-enable autoupdate + ui.set_autoupdate(FALSE) + +/obj/item/paper/ui_close(mob/user) + . = ..() + if(LAZYACCESS(writers, REF(user))) + remove_writer(user, update = FALSE) + UnregisterSignal(user, list( + COMSIG_MOB_UNEQUIPPED_ITEM, + COMSIG_MOB_EQUIPPED_ITEM, + COMSIG_MOB_SWAP_HANDS, + COMSIG_MOB_TRANSFORMING_ITEM, + )) + +/// Generically check if we are holding a writing tool to update our writer status +/obj/item/paper/proc/viewer_writing_state_change(mob/living/source) + SIGNAL_HANDLER + + var/list/writing_info = get_viewer_writing_implement_details(source) + if(writing_info) + if(!LAZYACCESS(writers, REF(source))) + add_writer(source, writing_info) + + else + if(LAZYACCESS(writers, REF(source))) + remove_writer(source) + +/// Add passed mob with passed writing info to the list of writers, then updates their ui +/obj/item/paper/proc/add_writer(mob/living/user, list/writing_info, update = TRUE) + PRIVATE_PROC(TRUE) + set waitfor = FALSE + + LAZYSET(writers, REF(user), writing_info) + if(update) + ui_interact(user) + +/// Remove passed mob from the list of writers, then updates their ui +/obj/item/paper/proc/remove_writer(mob/living/user, update = TRUE) + PRIVATE_PROC(TRUE) + set waitfor = FALSE + + LAZYREMOVE(writers, REF(user)) + if(update) + ui_interact(user) + +/obj/item/paper/proc/get_viewer_writing_implement_details(mob/living/user) + if(istype(loc, /obj/structure/noticeboard)) + var/obj/structure/noticeboard/noticeboard = loc + if(!noticeboard.allowed(user)) + return null + + var/obj/item/holding = user.get_active_held_item() + . = holding?.get_writing_implement_details() + + // Use a clipboard's pen, if applicable + if(istype(loc, /obj/item/clipboard)) + var/obj/item/clipboard/clipboard = loc + . ||= clipboard.pen?.get_writing_implement_details() + + return . + +/obj/item/paper/ui_data(mob/user) + var/list/data = list() + + data["held_item_details"] = LAZYACCESS(writers, REF(user)) + + return data /obj/item/paper/ui_static_data(mob/user) var/list/static_data = list() @@ -528,7 +617,7 @@ static_data["default_pen_color"] = COLOR_BLACK static_data["signature_font"] = FOUNTAIN_PEN_FONT - return static_data; + return static_data /obj/item/paper/proc/convert_to_data() var/list/data = list() @@ -567,28 +656,6 @@ name = data[LIST_PAPER_NAME] -/obj/item/paper/ui_data(mob/user) - var/list/data = list() - - var/obj/item/holding = user.get_active_held_item() - // Use a clipboard's pen, if applicable - if(istype(loc, /obj/item/clipboard)) - var/obj/item/clipboard/clipboard = loc - // This is just so you can still use a stamp if you're holding one. Otherwise, it'll - // use the clipboard's pen, if applicable. - if(!istype(holding, /obj/item/stamp) && clipboard.pen) - holding = clipboard.pen - - data["held_item_details"] = holding?.get_writing_implement_details() - - // If the paper is on an unwritable noticeboard, clear the held item details so it's read-only. - if(istype(loc, /obj/structure/noticeboard)) - var/obj/structure/noticeboard/noticeboard = loc - if(!noticeboard.allowed(user)) - data["held_item_details"] = null; - - return data - /obj/item/paper/ui_act(action, params, datum/tgui/ui) . = ..() if(.) diff --git a/code/modules/paperwork/stamps.dm b/code/modules/paperwork/stamps.dm index 9548fb04c477..55585a65e10e 100644 --- a/code/modules/paperwork/stamps.dm +++ b/code/modules/paperwork/stamps.dm @@ -22,7 +22,7 @@ return OXYLOSS /obj/item/stamp/get_writing_implement_details() - var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet/simple/paper) + var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet/simple/stamps) return list( interaction_mode = MODE_STAMPING, stamp_icon_state = icon_state, diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm index e2feaa2bc26e..57bc857a20a1 100644 --- a/code/modules/station_goals/bsa.dm +++ b/code/modules/station_goals/bsa.dm @@ -11,11 +11,11 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) /datum/station_goal/bluespace_cannon/get_report() return list( - "
Our military presence is inadequate in your sector.", + "Our military presence is inadequate in your sector.", "We need you to construct BSA-[rand(1,99)] Artillery position aboard your station.", "", "Base parts are available for shipping via cargo.", - "-Nanotrasen Naval Command
", + "- Nanotrasen Naval Command", ).Join("\n") /datum/station_goal/bluespace_cannon/on_report() diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm index 2039fc1d64a5..12accfbd1154 100644 --- a/code/modules/station_goals/dna_vault.dm +++ b/code/modules/station_goals/dna_vault.dm @@ -33,7 +33,7 @@ /datum/station_goal/dna_vault/get_report() return list( - "
Our long term prediction systems indicate a 99% chance of system-wide cataclysm in the near future.", + "Our long term prediction systems indicate a 99% chance of system-wide cataclysm in the near future.", "We need you to construct a DNA Vault aboard your station.", "", "The DNA Vault needs to contain samples of:", @@ -41,7 +41,7 @@ "* [plant_count] unique non-standard plant data", "* [human_count] unique sapient humanoid DNA data", "", - "Base vault parts are available for shipping via cargo.
", + "Base vault parts are available for shipping via cargo.", ).Join("\n") diff --git a/code/modules/station_goals/meteor_shield.dm b/code/modules/station_goals/meteor_shield.dm index 6c3b68ba264a..790b212da9e7 100644 --- a/code/modules/station_goals/meteor_shield.dm +++ b/code/modules/station_goals/meteor_shield.dm @@ -20,10 +20,10 @@ /datum/station_goal/station_shield/get_report() return list( - "
The station is located in a zone full of space debris.", + "The station is located in a zone full of space debris.", "We have a prototype shielding system you must deploy to reduce collision-related accidents.", "", - "You can order the satellites and control systems at cargo.
", + "You can order the satellites and control systems at cargo.", ).Join("\n") diff --git a/maplestation_modules/icons/nanotrasen-logo.png b/maplestation_modules/icons/nanotrasen-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..015bf21f4e35a12f4f251a42a8bbc24cecc910c3 GIT binary patch literal 4813 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 literal 0 HcmV?d00001 diff --git a/strings/flavor_reports.json b/strings/flavor_reports.json new file mode 100644 index 000000000000..e92ce0475207 --- /dev/null +++ b/strings/flavor_reports.json @@ -0,0 +1,76 @@ +{ + "reports": [ + "The station's communications systems are operating within @pick(adjectives) parameters. Good luck on your shift today, and remember to report any issues with comms to your station's Engineering team.", + "All systems are operational and functioning within @pick(adjectives) parameters. Remember to report any issues with your station's systems to your station's Engineering team.", + "Station diagnostics indicate that all systems are @pick(adjectives). Enjoy your shift today, and remember to report any issues with your station's systems to your station's Engineering team.", + "Your station's AI is functioning within @pick(adjectives) parameters. Remember to report any issues with your station's AI to the Research Director.", + "The Syndicate has issued a statement claiming they are not responsible for any recent incidents within the Spinward Sector. Whether you believe them or not is up to you.", + "Rumors that Nanotrasen are planning on widespread pay cuts in the Spinward Sector are unsubstantiated at this time. Please prevent the spread of misinformation amongst the crew in the meanwhile.", + "The company would like to inform the crew that the recent expansion of Bluespace Artillery platforms in the Spinward Sector is not a cause for concern - these batteries exist to protect the the inner station and colonies from larger threats, and are not authorized for offensive use.", + "Cybersun Industries has announced that they have successfully raided the high-security Library of the Manse. The library is known to contain several books on the occult, which occasionally demonstrate anomalous or paranormal properties. We assure the crew that there is no cause for concern, and that the company is taking all necessary precautions to ensure the safety of the station and its inhabitants - however, if you notice any strange activity from your station's library, report it to your station's security team.", + "Due to recent events, the company has decided to implement a new policy regarding the handling of corpses - particularly those of simian test subjects - aboard the station. All corpses must now be brought to the morgue for proper storage and handling, and may not be left unattended in hallways or other public areas. The company thanks you for your cooperation in this matter.", + "Due to recent events, the company would like to remind the crew to wash their hands regularly, and to avoid contact with any bodily fluids or hazardous materials. Especially for the exploration teams, and especially after contact with any alien flora or fauna. The company thanks you for your cooperation in this matter.", + "Due to recent events, the company would like to remind the crew that the Supermatter is not a toy, and the Engineering staff should avoid any unnecessary contact with it - yes, lighting a cigarette on it is considered unnecessary contact. The company thanks you for your cooperation in this matter.", + "Nanotrasen would like to remind the crew that their soul is owned by the company, and that any attempts to sell or trade your soul for personal gain will be met with swift and severe consequences, as it is considered a breach of contract and theft of company property.", + "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their research staffs have resorted to superstitious beliefs and rituals, including blood sacrifice to appease 'the Geometer' and strange invocations. The Wizard Federation denies any involment in these events, and the company has no reason to doubt this statement - signs point towards the Cult of Nar'Sie as the likely culprit, but there is no concrete evidence at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their maintenance crews have reported hearing strange noises and seeing strange apparitions during these times, and some have even gone missing. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Some members of their crews have reported feelings of intense paranoia and dread during these times, and some have even resorted to violence against others. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. On rare occasions, the AI systems of these stations have reported malfunctions and strange behavior during these times, including erratic speech patterns and unprovoked aggression. Little information is known at this time. The company advises the crew to monitor the station's AI closely, and to report any strange activity to the Research Director immediately.", + "The Wizard Federation were reported to be holding a convention in the Spinward Sector recently, though the exact location is unknown. The company has no reason to doubt the Federation's claim that the convention was purely social, though as Wizards lack a sense of right and wrong, the company advises the crew to stay on the lookout for any robed individuals appearing around the station.", + "Reports that a strange @pick(dangerous_virus) have been spreading across Spinward Sector colonies have been circulating amongst news agencies recently. All major organizations have released statements denying any involvement in the spread of this virus, and no cure is currently known. Cases of it spreading to orbital stations have been few and far between, but the company advises the medical staff to be on the lookout for any strange symptoms in patients, and to report any suspected cases to the Chief Medical Officer immediately.", + "One of the company's trading routes was recently @pick(attack) by the Gorlex Marauders. High casualties were reported, but amidst recovery operations, it has been noted that little was stolen outside of the contents of one ship - a high security transport ship containing a nuclear fission explosive device. Fortunately, the device cannot be armed without nuclear authorization code from Central Command. At the time we are unsure of their plans for it, but the company advises the Captain and Security team keep the station's self destruct codes secure, as there is a possibility that they could be reverse engineered to bypass security measures on the device.", + "One of the company's containment facilities was recently @pick(attack) by the Gorlex Marauders. The facility was being used to store several dangerous and exotic specimens, including codename Gamma - a highly adapted and dangerous alien creature. It is now believed that the Tiger Collective has inducted Gamma into their ranks, and will likely be using it to infiltrate and sabotage stations and colonies across the sector.", + "Employee unrest has spiked in recent weeks, with several stations and outposts reporting attempted mutinies and riots. The Syndicate denies involment, but recent reports from the Internal Affairs division state that they have been actively researching mind suggestion and mass hypnosis techniques, so the company has not ruled out their involvement. In the event that the crew start to demand higher wages or better working conditions, the company advises the Captain to remain calm and to avoid any rash decisions, and to report any signs of unrest to Central Command immediately.", + "The Internal Affairs division have released a statement condemning the recent actions of the Syndicate, and have accused them of being responsible for several recent incidents in the Spinward Sector, including the spread of a dangerous @pick(dangerous_virus) and the recent employee unrest. The Syndicate has denied these allegations, and there is currently no concrete evidence to support either claim.", + "The Internal Affairs division have released a classified statement suggesting that the Syndicate have achieved a breakthrough in their sleeper agent program, and that despite corporate background checks, there is a non-zero chance that any member of the crew could be a long term Syndicate sleeper agent, capable of being activated at any moment to undergo heinous acts of sabotage, espionage, or even assassination.", + "The Internal Affairs division would like to remind the crew that they do not have any direct involvement in station affairs. Anyone claiming to be an agent of Internal Affairs should be reported to your station's security team immediately. Any accusations that Internal Affairs, Central Command, or Nanotrasen as a whole is placing members of Internal Affairs on the station will be met with a conversation with an official Internal Affairs agent.", + "The Syndicate seem to be attempting to recruit members of the crew for their cause, though the exact details of their plans are unknown. The company would like to remind the crew that their loyalty should lie with the company, and that any attempts to recruit for the Syndicate should be reported to your station's security team immediately.", + "Recent breakthroughs in Bluespace technology have resulted in strange and unpredictable effects on spacetime, including the creation of temporary wormholes and rifts in reality. Any anomalous activity should be reported to your station's research team.", + "Bluespace technological research has been soaring to new heights recently, with several stations reporting successful Bluespace derived tests. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", + "Plasma research has been progressing steadily, with several stations reporting successful tests and breakthroughs in the field. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", + "Plasma research has seen explosive new developments recently, with several stations harnessing it for use in new weaponry. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", + "Several members of the crew have been found asleep at their desks recently. The company would like to remind the crew that while we understand that working in space can be stressful, sleeping on the job is not acceptable and may lead to disciplinary action. If you are feeling tired, please report to the station's dormitories for rest, and remember to take care of yourselves - your health and wellbeing is important to the company.", + "The recent disappearance of the clown has been acknowledged by Central Command. During their absence, we would like to make it clear that the company does not condone the actions of the missing clown, and that the crew should look into a replacement entertainer at their leisure.", + "Voyagers to Indeciphres have reported strange and unsettling dreams during their stay on the station. These dreams often involve themes of isolation, paranoia, and cosmic horror, and have been known to cause psychological distress in some individuals. The company advises the crew to report any instances of these dreams to the medical staff, and to seek help if they are experiencing any mental health issues as a result.", + "Voyagers to Indeciphres have returned with a plethora of strange and exotic souvenirs from their travels, including several items of great power. It is advised that any items returned from these voyages be handled with caution, and that any strange or anomalous items be brought to your station's research team for analysis.", + "As mining operations on Freja continue, several explorers have discovered abnormal caverns and tunnels within the moon. These tunnels share geological features with the tunnels found on Indeciphres, and have even been found to contain flora and fauna naitve to Indicephres, somehow unfrozen and thriving despite the vast difference of environments.", + "Reports have been circulating that members of mining teams assigned to Freja have been wandering on the surface of the moon, away from the station, until they are out of comms range - where they are never heard from again. The company attributes this to the harsh and unforgiving environment of Freja, and would like to remind all explorers and miners to stay within comms range at all times and to report to the Psychologist if experiencing any feelings of isolation or loneliness.", + "DeForest Medical have reported great strides in their research on the effects of Bluespace on human physiology, and have recently developed a new treatment for Bluespace Sickness that has shown promising results in early trials. The company is excited to see the results of this research, and encourages the medical staff to continue their diligent work in this field.", + "There is nothing interesting to report at this time. Please continue with your duties as normal, and report any suspicious activity to your station's security team immediately." + ], + + "adjectives": [ + "nominal", + "acceptable", + "optimal", + "normal", + "stable", + "standard" + ], + + "dangerous_virus": [ + "bacterial infection", + "blight", + "cerebral parasite", + "contagion", + "curse", + "disease", + "flesh-eating virus", + "infectious spore", + "nanomachine virus", + "plague", + "retrovirus", + "spaceborne bacterium", + "unknown pathogen", + "virus", + "xenopathogen" + ], + + "attack": [ + "assaulted", + "attacked", + "raided", + "ransacked" + ] + +} From 0cc1fc9557ccbd66c15cf7b0caf8ac714e010a78 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 00:26:12 -0600 Subject: [PATCH 09/21] Update --- code/modules/admin/verbs/adminevents.dm | 29 +++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm index 8caea05f7b3d..c21aaccc202c 100644 --- a/code/modules/admin/verbs/adminevents.dm +++ b/code/modules/admin/verbs/adminevents.dm @@ -265,20 +265,27 @@ ADMIN_VERB(command_report_footnote, R_FUN, "Command Report Footnote", "Adds a fo var/datum/command_footnote/command_report_footnote = new /datum/command_footnote() GLOB.communications_controller.block_command_report += 1 //Add a blocking condition to the counter until the inputs are done. - command_report_footnote.message = tgui_input_text(user, "This message will be attached to the bottom of the roundstart threat report. \ - Be sure to delay the roundstart report if you need extra time.", "P.S.") + command_report_footnote.message = tgui_input_text( + user, + "This message will be attached to the bottom of the roundstart threat report.", + "P.S.", + ) if(!command_report_footnote.message) GLOB.communications_controller.block_command_report -= 1 qdel(command_report_footnote) return - command_report_footnote.signature = tgui_input_text(user, "Whose signature will appear on this footnote?", "Also sign here, here, aaand here.") + command_report_footnote.signature = tgui_input_text( + user, + "Whose signature will appear on this footnote?", + "Also sign here, here, aaand here.", + ) if(!command_report_footnote.signature) command_report_footnote.signature = "Classified" GLOB.communications_controller.command_report_footnotes += command_report_footnote - GLOB.communications_controller.block_command_report-- + GLOB.communications_controller.block_command_report -= 1 message_admins("[user] has added a footnote to the command report: [command_report_footnote.message], signed [command_report_footnote.signature]") @@ -286,11 +293,15 @@ ADMIN_VERB(command_report_footnote, R_FUN, "Command Report Footnote", "Adds a fo var/message var/signature -ADMIN_VERB(command_report_content, R_FUN, "Command Report Content", "Sets the main content of the roundstart command report", ADMIN_CATEGORY_EVENTS) - var/content = tgui_input_text(user, "This message will be the main content of the roundstart command report, above the threat report (if enabled). \ - Be sure to delay the roundstart report if you need extra time to compose this message.", "To Whom It May Concern") - GLOB.communications_controller.command_report_main_content = content - message_admins("[key_name_admin(user)] has [content ? "set" : "cleared"] the main content of the roundstart command report.") +ADMIN_VERB(command_report_content, R_FUN, "Command Report Content", "Sets the main content of the roundstart command report.", ADMIN_CATEGORY_EVENTS) + GLOB.communications_controller.block_command_report += 1 + GLOB.communications_controller.command_report_main_content = tgui_input_text( + user, + "This message will be the main content of the roundstart command report, above the threat report (if enabled).", + "To Whom It May Concern", + ) + GLOB.communications_controller.block_command_report -= 1 + message_admins("[key_name_admin(user)] has [GLOB.communications_controller.command_report_main_content ? "set" : "cleared"] the main content of the roundstart command report.") ADMIN_VERB(delay_command_report, R_FUN, "Delay Command Report", "Prevents the roundstart command report from being sent; or forces it to send it delayed.", ADMIN_CATEGORY_EVENTS) GLOB.communications_controller.block_command_report = !GLOB.communications_controller.block_command_report From db42c9892057a088668c2bd0fab00e783e7faff1 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 00:29:18 -0600 Subject: [PATCH 10/21] Format tweak --- code/datums/communications.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/datums/communications.dm b/code/datums/communications.dm index a5678fcf4169..05f4cc5409f5 100644 --- a/code/datums/communications.dm +++ b/code/datums/communications.dm @@ -112,7 +112,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n continue trait_list_strings += "[station_trait.get_report()]
" if(trait_list_strings.len > 0) - . += "
Identified shift divergencies:
" + trait_list_strings.Join() + . += "

Identified shift divergencies:

" + trait_list_strings.Join() if(length(command_report_footnotes)) var/footnote_pile = "" @@ -122,7 +122,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n footnote_pile += "[footnote.signature]
" footnote_pile += "
" - . += "
Additional Notes:

" + footnote_pile + . += "

Additional Notes:

" + footnote_pile #ifndef MAP_TEST print_command_report(., "[command_name()] Status Summary", announce = FALSE, contains_advanced_html = TRUE) From 4b10255db6cddd8b6ec95d249a0b69b2640270d6 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 00:40:42 -0600 Subject: [PATCH 11/21] This --- code/datums/communications.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/datums/communications.dm b/code/datums/communications.dm index 05f4cc5409f5..d17ef4d71bba 100644 --- a/code/datums/communications.dm +++ b/code/datums/communications.dm @@ -77,7 +77,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n . = "" . += "

" - . += "

[command_name()], TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]


" + . += "

[command_name()], TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]


" . += command_report_main_content || pick_list_replacements("flavor_reports.json", "reports") if(CONFIG_GET(flag/no_dynamic_report)) if(isnull(greenshift)) @@ -87,7 +87,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n if(isnull(greenshift)) // if we're not forced to be greenshift or not - check if we are an actual greenshift greenshift = SSdynamic.current_tier.tier == 0 && dynamic_report == /datum/dynamic_tier/greenshift::advisory_report - . += "

Nanotrasen Department of Intelligence Threat Advisory, Spinward Sector

" + . += "

Nanotrasen Department of Intelligence Threat Advisory, Spinward Sector:

" . += dynamic_report SSstation.generate_station_goals(greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget)) From 9993d69a37f63a70031cc148e0b3c71522c6a14f Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 00:47:28 -0600 Subject: [PATCH 12/21] abc --- strings/flavor_reports.json | 55 +++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/strings/flavor_reports.json b/strings/flavor_reports.json index e92ce0475207..bf89c4362d85 100644 --- a/strings/flavor_reports.json +++ b/strings/flavor_reports.json @@ -1,42 +1,43 @@ { "reports": [ - "The station's communications systems are operating within @pick(adjectives) parameters. Good luck on your shift today, and remember to report any issues with comms to your station's Engineering team.", "All systems are operational and functioning within @pick(adjectives) parameters. Remember to report any issues with your station's systems to your station's Engineering team.", - "Station diagnostics indicate that all systems are @pick(adjectives). Enjoy your shift today, and remember to report any issues with your station's systems to your station's Engineering team.", - "Your station's AI is functioning within @pick(adjectives) parameters. Remember to report any issues with your station's AI to the Research Director.", - "The Syndicate has issued a statement claiming they are not responsible for any recent incidents within the Spinward Sector. Whether you believe them or not is up to you.", - "Rumors that Nanotrasen are planning on widespread pay cuts in the Spinward Sector are unsubstantiated at this time. Please prevent the spread of misinformation amongst the crew in the meanwhile.", - "The company would like to inform the crew that the recent expansion of Bluespace Artillery platforms in the Spinward Sector is not a cause for concern - these batteries exist to protect the the inner station and colonies from larger threats, and are not authorized for offensive use.", + "As mining operations on Freja continue, several explorers have discovered abnormal caverns and tunnels within the moon. These tunnels share geological features with the tunnels found on Indeciphres, and have even been found to contain flora and fauna naitve to Indicephres, somehow unfrozen and thriving despite the vast difference of environments.", + "Bluespace technological research has been soaring to new heights recently, with several stations reporting successful Bluespace derived tests. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", "Cybersun Industries has announced that they have successfully raided the high-security Library of the Manse. The library is known to contain several books on the occult, which occasionally demonstrate anomalous or paranormal properties. We assure the crew that there is no cause for concern, and that the company is taking all necessary precautions to ensure the safety of the station and its inhabitants - however, if you notice any strange activity from your station's library, report it to your station's security team.", + "DeForest Medical have reported great strides in their research on the effects of Bluespace on human physiology, and have recently developed a new treatment for Bluespace Sickness that has shown promising results in early trials. The company is excited to see the results of this research, and encourages the medical staff to continue their diligent work in this field.", "Due to recent events, the company has decided to implement a new policy regarding the handling of corpses - particularly those of simian test subjects - aboard the station. All corpses must now be brought to the morgue for proper storage and handling, and may not be left unattended in hallways or other public areas. The company thanks you for your cooperation in this matter.", - "Due to recent events, the company would like to remind the crew to wash their hands regularly, and to avoid contact with any bodily fluids or hazardous materials. Especially for the exploration teams, and especially after contact with any alien flora or fauna. The company thanks you for your cooperation in this matter.", "Due to recent events, the company would like to remind the crew that the Supermatter is not a toy, and the Engineering staff should avoid any unnecessary contact with it - yes, lighting a cigarette on it is considered unnecessary contact. The company thanks you for your cooperation in this matter.", + "Due to recent events, the company would like to remind the crew to wash their hands regularly, and to avoid contact with any bodily fluids or hazardous materials. Especially for the exploration teams, and especially after contact with any alien flora or fauna. The company thanks you for your cooperation in this matter.", + "Employee unrest has spiked in recent weeks, with several stations and outposts reporting attempted mutinies and riots. The Syndicate denies involment, but recent reports from the Internal Affairs division state that they have been actively researching mind suggestion and mass hypnosis techniques, so the company has not ruled out their involvement. In the event that the crew start to demand higher wages or better working conditions, the company advises the Captain to remain calm and to avoid any rash decisions, and to report any signs of unrest to Central Command immediately.", "Nanotrasen would like to remind the crew that their soul is owned by the company, and that any attempts to sell or trade your soul for personal gain will be met with swift and severe consequences, as it is considered a breach of contract and theft of company property.", - "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their research staffs have resorted to superstitious beliefs and rituals, including blood sacrifice to appease 'the Geometer' and strange invocations. The Wizard Federation denies any involment in these events, and the company has no reason to doubt this statement - signs point towards the Cult of Nar'Sie as the likely culprit, but there is no concrete evidence at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "One of the company's containment facilities was recently @pick(attack) by the Gorlex Marauders. The facility was being used to store several dangerous and exotic specimens, including codename Gamma - a highly adapted and dangerous alien creature. It is now believed that the Tiger Collective has inducted Gamma into their ranks, and will likely be using it to infiltrate and sabotage stations and colonies across the sector.", + "One of the company's trading routes was recently @pick(attack) by the Gorlex Marauders. High casualties were reported, but amidst recovery operations, it has been noted that little was stolen outside of the contents of one ship - a high security transport ship containing a nuclear fission explosive device. Fortunately, the device cannot be armed without nuclear authorization code from Central Command. At the time we are unsure of their plans for it, but the company advises the Captain and Security team keep the station's self destruct codes secure, as there is a possibility that they could be reverse engineered to bypass security measures on the device.", "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their maintenance crews have reported hearing strange noises and seeing strange apparitions during these times, and some have even gone missing. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", - "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Some members of their crews have reported feelings of intense paranoia and dread during these times, and some have even resorted to violence against others. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their research staffs have resorted to superstitious beliefs and rituals, including blood sacrifice to appease 'the Geometer' and strange invocations. The Wizard Federation denies any involment in these events, and the company has no reason to doubt this statement - signs point towards the Cult of Nar'Sie as the likely culprit, but there is no concrete evidence at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. On rare occasions, the AI systems of these stations have reported malfunctions and strange behavior during these times, including erratic speech patterns and unprovoked aggression. Little information is known at this time. The company advises the crew to monitor the station's AI closely, and to report any strange activity to the Research Director immediately.", - "The Wizard Federation were reported to be holding a convention in the Spinward Sector recently, though the exact location is unknown. The company has no reason to doubt the Federation's claim that the convention was purely social, though as Wizards lack a sense of right and wrong, the company advises the crew to stay on the lookout for any robed individuals appearing around the station.", + "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Some members of their crews have reported feelings of intense paranoia and dread during these times, and some have even resorted to violence against others. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Plasma research has been progressing steadily, with several stations reporting successful tests and breakthroughs in the field. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", + "Plasma research has seen explosive new developments recently, with several stations harnessing it for use in new weaponry. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", + "Recent breakthroughs in Bluespace technology have resulted in strange and unpredictable effects on spacetime, including the creation of temporary wormholes and rifts in reality. Any anomalous activity should be reported to your station's research team.", + "Recently, a shuttle full of evacuees fleeing a @pick(attack) Nanotrasen research station saw a strange event upon landing at their designated Central Command dropoff point - immediately upon landing, the evacuees all snapped and beat each other to death in a gruesome and violent frenzy. The company has no information on what caused this event, and suggests that all personnel keep up to date with their Psychological evaluations.", + "Reports have been circulating that members of mining teams assigned to Freja have been wandering on the surface of the moon, away from the station, until they are out of comms range - where they are never heard from again. The company attributes this to the harsh and unforgiving environment of Freja, and would like to remind all explorers and miners to stay within comms range at all times and to report to the Psychologist if experiencing any feelings of isolation or loneliness.", "Reports that a strange @pick(dangerous_virus) have been spreading across Spinward Sector colonies have been circulating amongst news agencies recently. All major organizations have released statements denying any involvement in the spread of this virus, and no cure is currently known. Cases of it spreading to orbital stations have been few and far between, but the company advises the medical staff to be on the lookout for any strange symptoms in patients, and to report any suspected cases to the Chief Medical Officer immediately.", - "One of the company's trading routes was recently @pick(attack) by the Gorlex Marauders. High casualties were reported, but amidst recovery operations, it has been noted that little was stolen outside of the contents of one ship - a high security transport ship containing a nuclear fission explosive device. Fortunately, the device cannot be armed without nuclear authorization code from Central Command. At the time we are unsure of their plans for it, but the company advises the Captain and Security team keep the station's self destruct codes secure, as there is a possibility that they could be reverse engineered to bypass security measures on the device.", - "One of the company's containment facilities was recently @pick(attack) by the Gorlex Marauders. The facility was being used to store several dangerous and exotic specimens, including codename Gamma - a highly adapted and dangerous alien creature. It is now believed that the Tiger Collective has inducted Gamma into their ranks, and will likely be using it to infiltrate and sabotage stations and colonies across the sector.", - "Employee unrest has spiked in recent weeks, with several stations and outposts reporting attempted mutinies and riots. The Syndicate denies involment, but recent reports from the Internal Affairs division state that they have been actively researching mind suggestion and mass hypnosis techniques, so the company has not ruled out their involvement. In the event that the crew start to demand higher wages or better working conditions, the company advises the Captain to remain calm and to avoid any rash decisions, and to report any signs of unrest to Central Command immediately.", - "The Internal Affairs division have released a statement condemning the recent actions of the Syndicate, and have accused them of being responsible for several recent incidents in the Spinward Sector, including the spread of a dangerous @pick(dangerous_virus) and the recent employee unrest. The Syndicate has denied these allegations, and there is currently no concrete evidence to support either claim.", + "Rumors that Nanotrasen are planning on widespread pay cuts in the Spinward Sector are unsubstantiated at this time. Please prevent the spread of misinformation amongst the crew in the meanwhile.", + "Several members of the crew have been found asleep at their desks recently. The company would like to remind the crew that while we understand that working in space can be stressful, sleeping on the job is not acceptable and may lead to disciplinary action. If you are feeling tired, please report to the station's dormitories for rest, and remember to take care of yourselves - your health and wellbeing is important to the company.", + "Station diagnostics indicate that all systems are @pick(adjectives). Enjoy your shift today, and remember to report any issues with your station's systems to your station's Engineering team.", "The Internal Affairs division have released a classified statement suggesting that the Syndicate have achieved a breakthrough in their sleeper agent program, and that despite corporate background checks, there is a non-zero chance that any member of the crew could be a long term Syndicate sleeper agent, capable of being activated at any moment to undergo heinous acts of sabotage, espionage, or even assassination.", + "The Internal Affairs division have released a statement condemning the recent actions of the Syndicate, and have accused them of being responsible for several recent incidents in the Spinward Sector, including the spread of a dangerous @pick(dangerous_virus) and the recent employee unrest. The Syndicate has denied these allegations, and there is currently no concrete evidence to support either claim.", "The Internal Affairs division would like to remind the crew that they do not have any direct involvement in station affairs. Anyone claiming to be an agent of Internal Affairs should be reported to your station's security team immediately. Any accusations that Internal Affairs, Central Command, or Nanotrasen as a whole is placing members of Internal Affairs on the station will be met with a conversation with an official Internal Affairs agent.", + "The Syndicate has issued a statement claiming they are not responsible for any recent incidents within the Spinward Sector. Whether you believe them or not is up to you.", "The Syndicate seem to be attempting to recruit members of the crew for their cause, though the exact details of their plans are unknown. The company would like to remind the crew that their loyalty should lie with the company, and that any attempts to recruit for the Syndicate should be reported to your station's security team immediately.", - "Recent breakthroughs in Bluespace technology have resulted in strange and unpredictable effects on spacetime, including the creation of temporary wormholes and rifts in reality. Any anomalous activity should be reported to your station's research team.", - "Bluespace technological research has been soaring to new heights recently, with several stations reporting successful Bluespace derived tests. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", - "Plasma research has been progressing steadily, with several stations reporting successful tests and breakthroughs in the field. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", - "Plasma research has seen explosive new developments recently, with several stations harnessing it for use in new weaponry. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", - "Several members of the crew have been found asleep at their desks recently. The company would like to remind the crew that while we understand that working in space can be stressful, sleeping on the job is not acceptable and may lead to disciplinary action. If you are feeling tired, please report to the station's dormitories for rest, and remember to take care of yourselves - your health and wellbeing is important to the company.", + "The Wizard Federation were reported to be holding a convention in the Spinward Sector recently, though the exact location is unknown. The company has no reason to doubt the Federation's claim that the convention was purely social, though as Wizards lack a sense of right and wrong, the company advises the crew to stay on the lookout for any robed individuals appearing around the station.", + "The company would like to inform the crew that the recent expansion of Bluespace Artillery platforms in the Spinward Sector is not a cause for concern - these batteries exist to protect the the inner station and colonies from larger threats, and are not authorized for offensive use.", "The recent disappearance of the clown has been acknowledged by Central Command. During their absence, we would like to make it clear that the company does not condone the actions of the missing clown, and that the crew should look into a replacement entertainer at their leisure.", + "The station's communications systems are operating within @pick(adjectives) parameters. Good luck on your shift today, and remember to report any issues with comms to your station's Engineering team.", + "There is nothing interesting to report at this time. Please continue with your duties as normal, and report any suspicious activity to your station's security team immediately.", "Voyagers to Indeciphres have reported strange and unsettling dreams during their stay on the station. These dreams often involve themes of isolation, paranoia, and cosmic horror, and have been known to cause psychological distress in some individuals. The company advises the crew to report any instances of these dreams to the medical staff, and to seek help if they are experiencing any mental health issues as a result.", "Voyagers to Indeciphres have returned with a plethora of strange and exotic souvenirs from their travels, including several items of great power. It is advised that any items returned from these voyages be handled with caution, and that any strange or anomalous items be brought to your station's research team for analysis.", - "As mining operations on Freja continue, several explorers have discovered abnormal caverns and tunnels within the moon. These tunnels share geological features with the tunnels found on Indeciphres, and have even been found to contain flora and fauna naitve to Indicephres, somehow unfrozen and thriving despite the vast difference of environments.", - "Reports have been circulating that members of mining teams assigned to Freja have been wandering on the surface of the moon, away from the station, until they are out of comms range - where they are never heard from again. The company attributes this to the harsh and unforgiving environment of Freja, and would like to remind all explorers and miners to stay within comms range at all times and to report to the Psychologist if experiencing any feelings of isolation or loneliness.", - "DeForest Medical have reported great strides in their research on the effects of Bluespace on human physiology, and have recently developed a new treatment for Bluespace Sickness that has shown promising results in early trials. The company is excited to see the results of this research, and encourages the medical staff to continue their diligent work in this field.", - "There is nothing interesting to report at this time. Please continue with your duties as normal, and report any suspicious activity to your station's security team immediately." + "Your station's AI is functioning within @pick(adjectives) parameters. Remember to report any issues with it to the Research Director." ], "adjectives": [ @@ -66,11 +67,5 @@ "xenopathogen" ], - "attack": [ - "assaulted", - "attacked", - "raided", - "ransacked" - ] - + "attack": ["assaulted", "attacked", "raided", "ransacked"] } From 21587d153f62c6cdb3065e1d491b91d8be6a1856 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 01:00:55 -0600 Subject: [PATCH 13/21] Tweaks --- code/datums/components/transforming.dm | 3 +- strings/flavor_reports.json | 43 +++++++++++++++++++++----- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/code/datums/components/transforming.dm b/code/datums/components/transforming.dm index ab2528ea73da..9f006ddb0f1b 100644 --- a/code/datums/components/transforming.dm +++ b/code/datums/components/transforming.dm @@ -154,7 +154,8 @@ toggle_active(source) if(!(SEND_SIGNAL(source, COMSIG_TRANSFORMING_ON_TRANSFORM, user, active) & COMPONENT_NO_DEFAULT_MESSAGE)) default_transform_message(source, user) - SEND_SIGNAL(user, COMSIG_MOB_TRANSFORMING_ITEM, source, active) + if(!isnull(user)) + SEND_SIGNAL(user, COMSIG_MOB_TRANSFORMING_ITEM, source, active) if(isnum(transform_cooldown_time)) COOLDOWN_START(src, transform_cooldown, transform_cooldown_time) if(user) diff --git a/strings/flavor_reports.json b/strings/flavor_reports.json index bf89c4362d85..2a976a96fda2 100644 --- a/strings/flavor_reports.json +++ b/strings/flavor_reports.json @@ -8,12 +8,12 @@ "Due to recent events, the company has decided to implement a new policy regarding the handling of corpses - particularly those of simian test subjects - aboard the station. All corpses must now be brought to the morgue for proper storage and handling, and may not be left unattended in hallways or other public areas. The company thanks you for your cooperation in this matter.", "Due to recent events, the company would like to remind the crew that the Supermatter is not a toy, and the Engineering staff should avoid any unnecessary contact with it - yes, lighting a cigarette on it is considered unnecessary contact. The company thanks you for your cooperation in this matter.", "Due to recent events, the company would like to remind the crew to wash their hands regularly, and to avoid contact with any bodily fluids or hazardous materials. Especially for the exploration teams, and especially after contact with any alien flora or fauna. The company thanks you for your cooperation in this matter.", - "Employee unrest has spiked in recent weeks, with several stations and outposts reporting attempted mutinies and riots. The Syndicate denies involment, but recent reports from the Internal Affairs division state that they have been actively researching mind suggestion and mass hypnosis techniques, so the company has not ruled out their involvement. In the event that the crew start to demand higher wages or better working conditions, the company advises the Captain to remain calm and to avoid any rash decisions, and to report any signs of unrest to Central Command immediately.", + "Employee unrest has spiked in recent weeks, with several stations and outposts reporting attempted mutinies and riots. The Syndicate denies involvement, but recent reports from the Internal Affairs division state that they have been actively researching mind suggestion and mass hypnosis techniques, so the company has not ruled out their involvement. In the event that the crew start to demand higher wages or better working conditions, the company advises the Captain to remain calm and to avoid any rash decisions, and to report any signs of unrest to Central Command immediately.", "Nanotrasen would like to remind the crew that their soul is owned by the company, and that any attempts to sell or trade your soul for personal gain will be met with swift and severe consequences, as it is considered a breach of contract and theft of company property.", - "One of the company's containment facilities was recently @pick(attack) by the Gorlex Marauders. The facility was being used to store several dangerous and exotic specimens, including codename Gamma - a highly adapted and dangerous alien creature. It is now believed that the Tiger Collective has inducted Gamma into their ranks, and will likely be using it to infiltrate and sabotage stations and colonies across the sector.", - "One of the company's trading routes was recently @pick(attack) by the Gorlex Marauders. High casualties were reported, but amidst recovery operations, it has been noted that little was stolen outside of the contents of one ship - a high security transport ship containing a nuclear fission explosive device. Fortunately, the device cannot be armed without nuclear authorization code from Central Command. At the time we are unsure of their plans for it, but the company advises the Captain and Security team keep the station's self destruct codes secure, as there is a possibility that they could be reverse engineered to bypass security measures on the device.", + "One of the company's containment facilities was recently @pick(attack) by the Gorlex Marauders. The facility was being used to store several dangerous and exotic specimens, including codename @pick(codenames) - a highly adapted and dangerous alien creature. It is now believed that the Tiger Collective has inducted the subject into their ranks, and will likely be using it to infiltrate and sabotage stations and colonies across the sector.", + "One of the company's trading routes was recently @pick(attack) by the Gorlex Marauders. High casualties were reported, but amidst recovery operations, it has been noted that little was stolen outside of the contents of one ship - a high-security transport ship containing a nuclear fission explosive device. Fortunately, the device cannot be armed without a nuclear authorization code from Central Command. At the time we are unsure of their plans for it, but the company advises the Captain and Security team keep the station's self destruct codes secure, as there is a possibility that they could be reverse engineered to bypass security measures on the device.", "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their maintenance crews have reported hearing strange noises and seeing strange apparitions during these times, and some have even gone missing. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", - "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their research staffs have resorted to superstitious beliefs and rituals, including blood sacrifice to appease 'the Geometer' and strange invocations. The Wizard Federation denies any involment in these events, and the company has no reason to doubt this statement - signs point towards the Cult of Nar'Sie as the likely culprit, but there is no concrete evidence at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their research staffs have resorted to superstitious beliefs and rituals, including blood sacrifice to appease 'the Geometer' and strange invocations. The Wizard Federation denies any involvement in these events, and the company has no reason to doubt this statement - signs point towards the Cult of Nar'Sie as the likely culprit, but there is no concrete evidence at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. On rare occasions, the AI systems of these stations have reported malfunctions and strange behavior during these times, including erratic speech patterns and unprovoked aggression. Little information is known at this time. The company advises the crew to monitor the station's AI closely, and to report any strange activity to the Research Director immediately.", "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Some members of their crews have reported feelings of intense paranoia and dread during these times, and some have even resorted to violence against others. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", "Plasma research has been progressing steadily, with several stations reporting successful tests and breakthroughs in the field. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", @@ -21,12 +21,13 @@ "Recent breakthroughs in Bluespace technology have resulted in strange and unpredictable effects on spacetime, including the creation of temporary wormholes and rifts in reality. Any anomalous activity should be reported to your station's research team.", "Recently, a shuttle full of evacuees fleeing a @pick(attack) Nanotrasen research station saw a strange event upon landing at their designated Central Command dropoff point - immediately upon landing, the evacuees all snapped and beat each other to death in a gruesome and violent frenzy. The company has no information on what caused this event, and suggests that all personnel keep up to date with their Psychological evaluations.", "Reports have been circulating that members of mining teams assigned to Freja have been wandering on the surface of the moon, away from the station, until they are out of comms range - where they are never heard from again. The company attributes this to the harsh and unforgiving environment of Freja, and would like to remind all explorers and miners to stay within comms range at all times and to report to the Psychologist if experiencing any feelings of isolation or loneliness.", - "Reports that a strange @pick(dangerous_virus) have been spreading across Spinward Sector colonies have been circulating amongst news agencies recently. All major organizations have released statements denying any involvement in the spread of this virus, and no cure is currently known. Cases of it spreading to orbital stations have been few and far between, but the company advises the medical staff to be on the lookout for any strange symptoms in patients, and to report any suspected cases to the Chief Medical Officer immediately.", + "Reports that a strange @pick(virus) have been spreading across Spinward Sector colonies have been circulating amongst news agencies recently. All major organizations have released statements denying any involvement in the spread of this virus, and no cure is currently known. Cases of it spreading to orbital stations have been few and far between, but the company advises the medical staff to be on the lookout for any strange symptoms in patients, and to report any suspected cases to the Chief Medical Officer immediately.", "Rumors that Nanotrasen are planning on widespread pay cuts in the Spinward Sector are unsubstantiated at this time. Please prevent the spread of misinformation amongst the crew in the meanwhile.", + "Rumors have circulated that a small army of pre-civilized humanoids managed to appropriate a transport vessel that landed on their planet. Allegedly, they overpowered the crew with their primitive swords, spears, and poleaxes, and have since discovered how to activate the ship's autopilot systems - allowing them to continue their conquest to the vessel's home station. The company assures the crew that there is no cause for concern, for even if the story is true, Nanotrasen laser weaponry could easily disintegrate their simple weapons and armor.", "Several members of the crew have been found asleep at their desks recently. The company would like to remind the crew that while we understand that working in space can be stressful, sleeping on the job is not acceptable and may lead to disciplinary action. If you are feeling tired, please report to the station's dormitories for rest, and remember to take care of yourselves - your health and wellbeing is important to the company.", "Station diagnostics indicate that all systems are @pick(adjectives). Enjoy your shift today, and remember to report any issues with your station's systems to your station's Engineering team.", "The Internal Affairs division have released a classified statement suggesting that the Syndicate have achieved a breakthrough in their sleeper agent program, and that despite corporate background checks, there is a non-zero chance that any member of the crew could be a long term Syndicate sleeper agent, capable of being activated at any moment to undergo heinous acts of sabotage, espionage, or even assassination.", - "The Internal Affairs division have released a statement condemning the recent actions of the Syndicate, and have accused them of being responsible for several recent incidents in the Spinward Sector, including the spread of a dangerous @pick(dangerous_virus) and the recent employee unrest. The Syndicate has denied these allegations, and there is currently no concrete evidence to support either claim.", + "The Internal Affairs division have released a statement condemning the recent actions of the Syndicate, and have accused them of being responsible for several recent incidents in the Spinward Sector, including the spread of a dangerous @pick(virus) and the recent employee unrest. The Syndicate has denied these allegations, and there is currently no concrete evidence to support either claim.", "The Internal Affairs division would like to remind the crew that they do not have any direct involvement in station affairs. Anyone claiming to be an agent of Internal Affairs should be reported to your station's security team immediately. Any accusations that Internal Affairs, Central Command, or Nanotrasen as a whole is placing members of Internal Affairs on the station will be met with a conversation with an official Internal Affairs agent.", "The Syndicate has issued a statement claiming they are not responsible for any recent incidents within the Spinward Sector. Whether you believe them or not is up to you.", "The Syndicate seem to be attempting to recruit members of the crew for their cause, though the exact details of their plans are unknown. The company would like to remind the crew that their loyalty should lie with the company, and that any attempts to recruit for the Syndicate should be reported to your station's security team immediately.", @@ -49,7 +50,34 @@ "standard" ], - "dangerous_virus": [ + "codenames": [ + "Alpha", + "Beta", + "Gamma", + "Delta", + "Epsilon", + "Zeta", + "Eta", + "Theta", + "Iota", + "Kappa", + "Lambda", + "Mu", + "Nu", + "Xi", + "Omicron", + "Pi", + "Rho", + "Sigma", + "Tau", + "Upsilon", + "Phi", + "Chi", + "Psi", + "Omega" + ], + + "virus": [ "bacterial infection", "blight", "cerebral parasite", @@ -57,6 +85,7 @@ "curse", "disease", "flesh-eating virus", + "grey goo", "infectious spore", "nanomachine virus", "plague", From 366818aac1bb613908a53d6c43a8d055c523f323 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 01:20:57 -0600 Subject: [PATCH 14/21] Updates --- code/datums/communications.dm | 8 +++++++- strings/flavor_reports.json | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/code/datums/communications.dm b/code/datums/communications.dm index d17ef4d71bba..91ccc8369374 100644 --- a/code/datums/communications.dm +++ b/code/datums/communications.dm @@ -78,7 +78,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n . = "" . += "

" . += "

[command_name()], TCD [time2text(world.realtime, "DDD, MMM DD")], [CURRENT_STATION_YEAR]


" - . += command_report_main_content || pick_list_replacements("flavor_reports.json", "reports") + . += command_report_main_content || get_main_report_content() if(CONFIG_GET(flag/no_dynamic_report)) if(isnull(greenshift)) greenshift = SSdynamic.current_tier.tier == 0 @@ -156,5 +156,11 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n return . +/// Return a random flavor/meme report to use in the command report +/datum/communciations_controller/proc/get_main_report_content() + if(istype(SSstation.announcer, /datum/centcom_announcer/intern)) + return pick_list_replacements("flavor_reports.json", "intern_reports") + return pick_list_replacements("flavor_reports.json", "reports") + #undef COMMUNICATION_COOLDOWN #undef COMMUNICATION_COOLDOWN_AI diff --git a/strings/flavor_reports.json b/strings/flavor_reports.json index 2a976a96fda2..f365e67cddf9 100644 --- a/strings/flavor_reports.json +++ b/strings/flavor_reports.json @@ -3,7 +3,7 @@ "All systems are operational and functioning within @pick(adjectives) parameters. Remember to report any issues with your station's systems to your station's Engineering team.", "As mining operations on Freja continue, several explorers have discovered abnormal caverns and tunnels within the moon. These tunnels share geological features with the tunnels found on Indeciphres, and have even been found to contain flora and fauna naitve to Indicephres, somehow unfrozen and thriving despite the vast difference of environments.", "Bluespace technological research has been soaring to new heights recently, with several stations reporting successful Bluespace derived tests. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", - "Cybersun Industries has announced that they have successfully raided the high-security Library of the Manse. The library is known to contain several books on the occult, which occasionally demonstrate anomalous or paranormal properties. We assure the crew that there is no cause for concern, and that the company is taking all necessary precautions to ensure the safety of the station and its inhabitants - however, if you notice any strange activity from your station's library, report it to your station's security team.", + "Cybersun Industries has announced that they have successfully raided the high-security Library of the Manse. The library was known to contain several books on the occult, which occasionally demonstrate anomalous or paranormal properties. We assure the crew that there is no cause for concern, and that the company is taking all necessary precautions to ensure the safety of the station and its inhabitants - however, if you notice any strange activity from your station's library, report it to your station's security team.", "DeForest Medical have reported great strides in their research on the effects of Bluespace on human physiology, and have recently developed a new treatment for Bluespace Sickness that has shown promising results in early trials. The company is excited to see the results of this research, and encourages the medical staff to continue their diligent work in this field.", "Due to recent events, the company has decided to implement a new policy regarding the handling of corpses - particularly those of simian test subjects - aboard the station. All corpses must now be brought to the morgue for proper storage and handling, and may not be left unattended in hallways or other public areas. The company thanks you for your cooperation in this matter.", "Due to recent events, the company would like to remind the crew that the Supermatter is not a toy, and the Engineering staff should avoid any unnecessary contact with it - yes, lighting a cigarette on it is considered unnecessary contact. The company thanks you for your cooperation in this matter.", @@ -22,8 +22,8 @@ "Recently, a shuttle full of evacuees fleeing a @pick(attack) Nanotrasen research station saw a strange event upon landing at their designated Central Command dropoff point - immediately upon landing, the evacuees all snapped and beat each other to death in a gruesome and violent frenzy. The company has no information on what caused this event, and suggests that all personnel keep up to date with their Psychological evaluations.", "Reports have been circulating that members of mining teams assigned to Freja have been wandering on the surface of the moon, away from the station, until they are out of comms range - where they are never heard from again. The company attributes this to the harsh and unforgiving environment of Freja, and would like to remind all explorers and miners to stay within comms range at all times and to report to the Psychologist if experiencing any feelings of isolation or loneliness.", "Reports that a strange @pick(virus) have been spreading across Spinward Sector colonies have been circulating amongst news agencies recently. All major organizations have released statements denying any involvement in the spread of this virus, and no cure is currently known. Cases of it spreading to orbital stations have been few and far between, but the company advises the medical staff to be on the lookout for any strange symptoms in patients, and to report any suspected cases to the Chief Medical Officer immediately.", - "Rumors that Nanotrasen are planning on widespread pay cuts in the Spinward Sector are unsubstantiated at this time. Please prevent the spread of misinformation amongst the crew in the meanwhile.", "Rumors have circulated that a small army of pre-civilized humanoids managed to appropriate a transport vessel that landed on their planet. Allegedly, they overpowered the crew with their primitive swords, spears, and poleaxes, and have since discovered how to activate the ship's autopilot systems - allowing them to continue their conquest to the vessel's home station. The company assures the crew that there is no cause for concern, for even if the story is true, Nanotrasen laser weaponry could easily disintegrate their simple weapons and armor.", + "Rumors that Nanotrasen are planning on widespread pay cuts in the Spinward Sector are unsubstantiated at this time. Please prevent the spread of misinformation amongst the crew in the meanwhile.", "Several members of the crew have been found asleep at their desks recently. The company would like to remind the crew that while we understand that working in space can be stressful, sleeping on the job is not acceptable and may lead to disciplinary action. If you are feeling tired, please report to the station's dormitories for rest, and remember to take care of yourselves - your health and wellbeing is important to the company.", "Station diagnostics indicate that all systems are @pick(adjectives). Enjoy your shift today, and remember to report any issues with your station's systems to your station's Engineering team.", "The Internal Affairs division have released a classified statement suggesting that the Syndicate have achieved a breakthrough in their sleeper agent program, and that despite corporate background checks, there is a non-zero chance that any member of the crew could be a long term Syndicate sleeper agent, capable of being activated at any moment to undergo heinous acts of sabotage, espionage, or even assassination.", @@ -41,6 +41,10 @@ "Your station's AI is functioning within @pick(adjectives) parameters. Remember to report any issues with it to the Research Director." ], + "intern_reports": [ + "Please replace this text with the report provided by the Communications team." + ], + "adjectives": [ "nominal", "acceptable", From ac38a92fc003d8c92071ce6d654448831f4bccd3 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 14:44:28 -0600 Subject: [PATCH 15/21] Goodbye son --- code/modules/antagonists/malf_ai/malf_ai.dm | 9 +- maplestation.dme | 1 - .../advanced_traitor/advanced_malf.dm | 113 ------------------ 3 files changed, 7 insertions(+), 116 deletions(-) delete mode 100644 maplestation_modules/code/modules/antagonists/advanced_traitor/advanced_malf.dm diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm index c1ff66152a6a..bbb8d7a77ee5 100644 --- a/code/modules/antagonists/malf_ai/malf_ai.dm +++ b/code/modules/antagonists/malf_ai/malf_ai.dm @@ -33,9 +33,13 @@ if(give_objectives) forge_ai_objectives() + if(!employer) + employer = pick(GLOB.ai_employers) - if(finalize_antag) // NON-MODULE CHANGE - finalize_antag() + malfunction_flavor = strings(MALFUNCTION_FLAVOR_FILE, employer) + + add_law_zero() + owner.current.grant_language(/datum/language/codespeak, source = LANGUAGE_MALF) return ..() @@ -260,6 +264,7 @@ /datum/antagonist/malf_ai/infected name = "Infected AI" employer = "Infected AI" + stinger_sound = null ///The player, to who is this AI slaved var/datum/mind/boss diff --git a/maplestation.dme b/maplestation.dme index df03a042e6b1..22dbf24681d8 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -6445,7 +6445,6 @@ #include "maplestation_modules\code\modules\antagonists\advanced_ling\changeling_rebalancing.dm" #include "maplestation_modules\code\modules\antagonists\advanced_ling\changeling_stings.dm" #include "maplestation_modules\code\modules\antagonists\advanced_ling\neutered_ling.dm" -#include "maplestation_modules\code\modules\antagonists\advanced_traitor\advanced_malf.dm" #include "maplestation_modules\code\modules\art\statuettes.dm" #include "maplestation_modules\code\modules\cargo\costumes_toys.dm" #include "maplestation_modules\code\modules\cargo\goodies.dm" diff --git a/maplestation_modules/code/modules/antagonists/advanced_traitor/advanced_malf.dm b/maplestation_modules/code/modules/antagonists/advanced_traitor/advanced_malf.dm deleted file mode 100644 index 577b7f64db47..000000000000 --- a/maplestation_modules/code/modules/antagonists/advanced_traitor/advanced_malf.dm +++ /dev/null @@ -1,113 +0,0 @@ -/// -- Advanced Antag for Malf AIs. -- -/// Proc to give the malf their hacked module. -/datum/antagonist/malf_ai/finalize_antag() - if(give_objectives) - if(!employer) - employer = pick(GLOB.ai_employers) - malfunction_flavor = strings(MALFUNCTION_FLAVOR_FILE, employer) - - add_law_zero() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) - owner.current.grant_language(/datum/language/codespeak, source = LANGUAGE_MALF) - -/datum/antagonist/malf_ai/apply_innate_effects(mob/living/mob_override) - . = ..() - var/datum/atom_hud/data/hackyhud = GLOB.huds[DATA_HUD_MALF_APC] - hackyhud.show_to(mob_override || owner.current) - -/datum/antagonist/malf_ai/remove_innate_effects(mob/living/mob_override) - . = ..() - var/datum/atom_hud/data/hackyhud = GLOB.huds[DATA_HUD_MALF_APC] - hackyhud.hide_from(mob_override || owner.current) - -/// The Advanced Malf datum. -/datum/antagonist/malf_ai/advanced - name = "Advanced Malfunctioning AI" - ui_name = null - employer = "The Syndicate" - give_objectives = FALSE - should_give_codewords = FALSE - finalize_antag = FALSE - /// List of objectives AIs can get in addition to the base ones - var/static/list/ai_objectives = list( - "no organics on shuttle" = /datum/objective/block, - "no mutants on shuttle" = /datum/objective/purge, - "robot army" = /datum/objective/robot_army, - "survive AI" = /datum/objective/survive/malf, - ) - /// Goals that advanced malf AIs shouldn't be able to pick - var/static/list/blacklisted_ai_objectives = list( - "survive", - "destroy AI", - "download", - "steal", - "escape", - "debrain" - ) - -/datum/antagonist/malf_ai/advanced/on_gain() - if(!GLOB.admin_objective_list) - generate_admin_objective_list() - - var/list/objectives_to_choose = GLOB.admin_objective_list.Copy() - objectives_to_choose -= blacklisted_similar_objectives - objectives_to_choose -= blacklisted_ai_objectives - objectives_to_choose += ai_objectives - name = "Malfunctioning AI" - - linked_advanced_datum = new /datum/advanced_antag_datum/malf_ai(src) - linked_advanced_datum.setup_advanced_antag() - linked_advanced_datum.possible_objectives = objectives_to_choose - return ..() - -/datum/antagonist/malf_ai/advanced/greet() - linked_advanced_datum.greet_message(owner.current) - -/datum/antagonist/malf_ai/advanced/roundend_report() - var/list/result = list() - - result += printplayer(owner) - result += "[owner] was a/an [linked_advanced_datum.name][employer? " hacked by [employer]":""]." - if(linked_advanced_datum.backstory) - result += "[owner]'s backstory was the following:
[linked_advanced_datum.backstory]" - - if(LAZYLEN(linked_advanced_datum.our_goals)) - result += "[owner]'s objectives:" - var/count = 1 - for(var/datum/advanced_antag_goal/goal as anything in linked_advanced_datum.our_goals) - result += goal.get_roundend_text(count++) - - return result.Join("
") - -/datum/antagonist/malf_ai/advanced/roundend_report_footer() - return "
And thus ends another security breach on board [station_name()]." - -/// The advanced antag datum itself for malf AIs. -/datum/advanced_antag_datum/malf_ai - name = "Advanced Malfunctioning AI" - employer = "The Syndicate" - starting_points = 20 - -/datum/advanced_antag_datum/malf_ai/modify_antag_points() - var/mob/living/silicon/ai/traitor_ai = linked_antagonist.owner.current - if(!istype(traitor_ai)) - CRASH("Advanced Malf AI datum on a mob that isn't an AI!") - - var/datum/module_picker/traitor_ai_uplink = traitor_ai.malf_picker - starting_points = get_antag_points_from_goals() - traitor_ai_uplink.processing_time = starting_points - -/datum/advanced_antag_datum/malf_ai/get_antag_points_from_goals() - var/finalized_starting_points = ADV_TRAITOR_INITIAL_MALF_POINTS - for(var/datum/advanced_antag_goal/goal as anything in our_goals) - finalized_starting_points += (goal.intensity * ADV_TRAITOR_MALF_POINTS_PER_INTENSITY) - - return min(finalized_starting_points, ADV_TRAITOR_MAX_MALF_POINTS) - -/datum/advanced_antag_datum/malf_ai/get_finalize_text() - return "Finalizing will begin installlation of your malfunction module with [get_antag_points_from_goals()] processing power. You can still edit your goals after finalizing!" - -/datum/advanced_antag_datum/malf_ai/set_employer(employer) - . = ..() - var/datum/antagonist/malf_ai/our_ai = linked_antagonist - our_ai.employer = src.employer From fe7ed71a2efd7c1a7eab46d68769074c8d5dff2c Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 15:47:30 -0600 Subject: [PATCH 16/21] Lints --- code/game/objects/items/dna_probe.dm | 4 ++ .../final_objective/space_dragon.dm | 49 ------------------- maplestation.dme | 2 + tgstation.dme | 8 +++ 4 files changed, 14 insertions(+), 49 deletions(-) delete mode 100644 code/modules/antagonists/traitor/objectives/final_objective/space_dragon.dm diff --git a/code/game/objects/items/dna_probe.dm b/code/game/objects/items/dna_probe.dm index 77abe7c005ed..6df7a3bcfd34 100644 --- a/code/game/objects/items/dna_probe.dm +++ b/code/game/objects/items/dna_probe.dm @@ -145,3 +145,7 @@ if(isanimal_or_basicmob(target) || is_type_in_typecache(target, non_simple_animals) || ismonkey(target)) return TRUE return FALSE + +#undef DNA_PROBE_SCAN_PLANTS +#undef DNA_PROBE_SCAN_ANIMALS +#undef DNA_PROBE_SCAN_HUMANS diff --git a/code/modules/antagonists/traitor/objectives/final_objective/space_dragon.dm b/code/modules/antagonists/traitor/objectives/final_objective/space_dragon.dm deleted file mode 100644 index 4d39412653ed..000000000000 --- a/code/modules/antagonists/traitor/objectives/final_objective/space_dragon.dm +++ /dev/null @@ -1,49 +0,0 @@ -/datum/traitor_objective/ultimate/space_dragon - name = "Find a Space Carp and mutate their DNA with your own using a DNA harvester we will drop pod at %AREA%" - description = "Go to %AREA%, and receive the Carp DNA scanner. Use it on any Space Carp to harvest its DNA. \ - From there, use it on yourself, to mutate your own DNA with it and become a Space Dragon. \ - Don't worry about finding one, I'm sure there'll have a wave of carp right when you need it." - - ///Area type the objective owner must be in to receive the DNA extractor. - var/area/dna_scanner_spawnarea_type - ///Whether the DNA extraction kit was sent already. - var/received_dna_scanner = FALSE - -/datum/traitor_objective/ultimate/space_dragon/on_objective_taken(mob/user) - . = ..() - force_event(/datum/round_event_control/carp_migration, "[handler.owner]'s final objective") - -/datum/traitor_objective/ultimate/space_dragon/generate_objective(datum/mind/generating_for, list/possible_duplicates) - var/list/possible_areas = GLOB.the_station_areas.Copy() - for(var/area/possible_area as anything in possible_areas) - //remove areas too close to the destination, too obvious for our poor shmuck, or just unfair - if(ispath(possible_area, /area/station/hallway) || ispath(possible_area, /area/station/security)) - possible_areas -= possible_area - if(length(possible_areas) == 0) - return FALSE - dna_scanner_spawnarea_type = pick(possible_areas) - replace_in_name("%AREA%", initial(dna_scanner_spawnarea_type.name)) - return TRUE - -/datum/traitor_objective/ultimate/space_dragon/generate_ui_buttons(mob/user) - var/list/buttons = list() - if(!received_dna_scanner) - buttons += add_ui_button("", "Pressing this will call down a pod with the DNA extraction kit.", "biohazard", "carp_dna") - return buttons - -/datum/traitor_objective/ultimate/space_dragon/ui_perform_action(mob/living/user, action) - . = ..() - switch(action) - if("carp_dna") - if(received_dna_scanner) - return - var/area/delivery_area = get_area(user) - if(delivery_area.type != dna_scanner_spawnarea_type) - to_chat(user, span_warning("You must be in [initial(dna_scanner_spawnarea_type.name)] to receive the DNA extraction kit.")) - return - received_dna_scanner = TRUE - podspawn(list( - "target" = get_turf(user), - "style" = /datum/pod_style/syndicate, // Non-module change : upstream killed this lol - "spawn" = /obj/item/storage/box/syndie_kit/space_dragon, - )) diff --git a/maplestation.dme b/maplestation.dme index 22dbf24681d8..dd6ba6ae1201 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -3106,6 +3106,7 @@ #include "code\modules\antagonists\disease\disease_disease.dm" #include "code\modules\antagonists\disease\disease_mob.dm" #include "code\modules\antagonists\ert\ert.dm" +#include "code\modules\antagonists\evil_clone\evil_clone.dm" #include "code\modules\antagonists\fugitive\fugitive.dm" #include "code\modules\antagonists\fugitive\fugitive_equipment.dm" #include "code\modules\antagonists\fugitive\fugitive_outfits.dm" @@ -4502,6 +4503,7 @@ #include "code\modules\mapfluff\ruins\spaceruin_code\derelict_sulaco.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\DJstation.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\forgottenship.dm" +#include "code\modules\mapfluff\ruins\spaceruin_code\garbagetruck.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hellfactory.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hilbertshotel.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\interdyne.dm" diff --git a/tgstation.dme b/tgstation.dme index 381575432f1e..b1c8ef22b36d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -779,6 +779,7 @@ #include "code\datums\chat_payload.dm" #include "code\datums\chatmessage.dm" #include "code\datums\cogbar.dm" +#include "code\datums\communications.dm" #include "code\datums\dash_weapon.dm" #include "code\datums\datum.dm" #include "code\datums\datumvars.dm" @@ -3109,6 +3110,7 @@ #include "code\modules\antagonists\disease\disease_disease.dm" #include "code\modules\antagonists\disease\disease_mob.dm" #include "code\modules\antagonists\ert\ert.dm" +#include "code\modules\antagonists\evil_clone\evil_clone.dm" #include "code\modules\antagonists\fugitive\fugitive.dm" #include "code\modules\antagonists\fugitive\fugitive_equipment.dm" #include "code\modules\antagonists\fugitive\fugitive_outfits.dm" @@ -3228,6 +3230,10 @@ #include "code\modules\antagonists\ninja\ninjaDrainAct.dm" #include "code\modules\antagonists\ninja\outfit.dm" #include "code\modules\antagonists\nukeop\nukeop.dm" +#include "code\modules\antagonists\nukeop\datums\operative.dm" +#include "code\modules\antagonists\nukeop\datums\operative_leader.dm" +#include "code\modules\antagonists\nukeop\datums\operative_lone.dm" +#include "code\modules\antagonists\nukeop\datums\operative_team.dm" #include "code\modules\antagonists\nukeop\outfits.dm" #include "code\modules\antagonists\nukeop\equipment\borgchameleon.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_authentication_disk.dm" @@ -3997,6 +4003,7 @@ #include "code\modules\events\ghost_role\_ghost_role.dm" #include "code\modules\events\ghost_role\operative.dm" #include "code\modules\events\ghost_role\sentience.dm" +#include "code\modules\events\ghost_role\sentient_disease.dm" #include "code\modules\events\holiday\easter.dm" #include "code\modules\events\holiday\halloween.dm" #include "code\modules\events\holiday\vday.dm" @@ -4509,6 +4516,7 @@ #include "code\modules\mapfluff\ruins\spaceruin_code\derelict_sulaco.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\DJstation.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\forgottenship.dm" +#include "code\modules\mapfluff\ruins\spaceruin_code\garbagetruck.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hellfactory.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hilbertshotel.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\interdyne.dm" From e37a65515f4e81e080ac31c84c29d993c256bc9e Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 25 Feb 2026 18:11:20 -0600 Subject: [PATCH 17/21] Update --- strings/flavor_reports.json | 58 ++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/strings/flavor_reports.json b/strings/flavor_reports.json index f365e67cddf9..37034ce977fa 100644 --- a/strings/flavor_reports.json +++ b/strings/flavor_reports.json @@ -1,48 +1,64 @@ { "reports": [ "All systems are operational and functioning within @pick(adjectives) parameters. Remember to report any issues with your station's systems to your station's Engineering team.", - "As mining operations on Freja continue, several explorers have discovered abnormal caverns and tunnels within the moon. These tunnels share geological features with the tunnels found on Indeciphres, and have even been found to contain flora and fauna naitve to Indicephres, somehow unfrozen and thriving despite the vast difference of environments.", + "As mining operations on Freyja continue, several explorers have discovered abnormal caverns and tunnels within the moon. These tunnels share geological features with the tunnels found on Indecipheres, and have even been found to contain flora and fauna native to Indecipheres, somehow unfrozen and thriving despite the vast difference in environments.", "Bluespace technological research has been soaring to new heights recently, with several stations reporting successful Bluespace derived tests. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", - "Cybersun Industries has announced that they have successfully raided the high-security Library of the Manse. The library was known to contain several books on the occult, which occasionally demonstrate anomalous or paranormal properties. We assure the crew that there is no cause for concern, and that the company is taking all necessary precautions to ensure the safety of the station and its inhabitants - however, if you notice any strange activity from your station's library, report it to your station's security team.", + "Cybersun Industries has announced that they have successfully raided a high-security library. The library was known to contain several books on the occult, which occasionally demonstrate anomalous or paranormal properties. We assure the crew that there is no cause for concern, and that we are taking all necessary precautions to ensure the safety of the station and its inhabitants - however, if you notice any strange activity from your station's library, report it to your station's security team.", "DeForest Medical have reported great strides in their research on the effects of Bluespace on human physiology, and have recently developed a new treatment for Bluespace Sickness that has shown promising results in early trials. The company is excited to see the results of this research, and encourages the medical staff to continue their diligent work in this field.", - "Due to recent events, the company has decided to implement a new policy regarding the handling of corpses - particularly those of simian test subjects - aboard the station. All corpses must now be brought to the morgue for proper storage and handling, and may not be left unattended in hallways or other public areas. The company thanks you for your cooperation in this matter.", - "Due to recent events, the company would like to remind the crew that the Supermatter is not a toy, and the Engineering staff should avoid any unnecessary contact with it - yes, lighting a cigarette on it is considered unnecessary contact. The company thanks you for your cooperation in this matter.", - "Due to recent events, the company would like to remind the crew to wash their hands regularly, and to avoid contact with any bodily fluids or hazardous materials. Especially for the exploration teams, and especially after contact with any alien flora or fauna. The company thanks you for your cooperation in this matter.", - "Employee unrest has spiked in recent weeks, with several stations and outposts reporting attempted mutinies and riots. The Syndicate denies involvement, but recent reports from the Internal Affairs division state that they have been actively researching mind suggestion and mass hypnosis techniques, so the company has not ruled out their involvement. In the event that the crew start to demand higher wages or better working conditions, the company advises the Captain to remain calm and to avoid any rash decisions, and to report any signs of unrest to Central Command immediately.", + "Due to recent events, the company has decided to implement a new policy regarding the handling of corpses - particularly those of simian test subjects - aboard the station. All corpses must now be brought to the morgue for proper storage and handling, and may not be left unattended in hallways or other public areas. We thank you for your cooperation in this matter.", + "Due to recent events, the company would like to remind the crew that the Supermatter is not a toy, and the Engineering staff should avoid any unnecessary contact with it - yes, lighting a cigarette on it is considered unnecessary contact. We thank you for your cooperation in this matter.", + "Due to recent events, the company would like to remind the crew that the disposal chutes are not toys, and that cargo technicians should not be attempting to 'ride' them for faster travel around the station. We thank you for your cooperation in this matter.", + "Due to recent events, the company would like to remind the crew to wash their hands regularly, and to avoid contact with any bodily fluids or hazardous materials. Especially for the exploration teams, and especially after contact with any alien flora or fauna. We thank you for your cooperation in this matter.", + "Employee unrest has spiked in recent weeks, with several stations and outposts reporting attempted mutinies and riots. The Syndicate denies involvement, but recent reports from the Internal Affairs division state that they have been actively researching mind suggestion and mass hypnosis techniques, so the company has not ruled them out yet. In the event that the crew start to demand higher wages or better working conditions, we advise the Captain to remain calm and to avoid any rash decisions, and to report any signs of unrest to Central Command immediately.", "Nanotrasen would like to remind the crew that their soul is owned by the company, and that any attempts to sell or trade your soul for personal gain will be met with swift and severe consequences, as it is considered a breach of contract and theft of company property.", "One of the company's containment facilities was recently @pick(attack) by the Gorlex Marauders. The facility was being used to store several dangerous and exotic specimens, including codename @pick(codenames) - a highly adapted and dangerous alien creature. It is now believed that the Tiger Collective has inducted the subject into their ranks, and will likely be using it to infiltrate and sabotage stations and colonies across the sector.", - "One of the company's trading routes was recently @pick(attack) by the Gorlex Marauders. High casualties were reported, but amidst recovery operations, it has been noted that little was stolen outside of the contents of one ship - a high-security transport ship containing a nuclear fission explosive device. Fortunately, the device cannot be armed without a nuclear authorization code from Central Command. At the time we are unsure of their plans for it, but the company advises the Captain and Security team keep the station's self destruct codes secure, as there is a possibility that they could be reverse engineered to bypass security measures on the device.", - "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their maintenance crews have reported hearing strange noises and seeing strange apparitions during these times, and some have even gone missing. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", - "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Members of their research staffs have resorted to superstitious beliefs and rituals, including blood sacrifice to appease 'the Geometer' and strange invocations. The Wizard Federation denies any involvement in these events, and the company has no reason to doubt this statement - signs point towards the Cult of Nar'Sie as the likely culprit, but there is no concrete evidence at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", - "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. On rare occasions, the AI systems of these stations have reported malfunctions and strange behavior during these times, including erratic speech patterns and unprovoked aggression. Little information is known at this time. The company advises the crew to monitor the station's AI closely, and to report any strange activity to the Research Director immediately.", - "Other stations in orbit around Indeciphres have reported strange occurances as their orbit passes behind the moon of Freja. Some members of their crews have reported feelings of intense paranoia and dread during these times, and some have even resorted to violence against others. Little information is known at this time. The company advises all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "One of the company's trading routes was recently @pick(attack) by the Gorlex Marauders. High casualties were reported, but amidst recovery operations, it has been noted that little was stolen outside of the contents of one ship - a high-security transport ship containing a nuclear fission explosive device. Fortunately, the device cannot be armed without a nuclear authorization code from Central Command. At the time we are unsure of their plans for it, but we advise the Captain and security team keep the station's self destruct codes secure, as there is a possibility that they could be reverse engineered to bypass security measures on the device.", + "Other stations in orbit around Indecipheres have reported strange occurances as their orbit passes behind the moon of Freyja. Some members of their crews have reported feelings of intense paranoia and dread during these times, and some have even resorted to violence against others. Little information is known at this time. We advise all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Other stations in orbit around Indecipheres have reported strange occurrences as their orbit passes behind the moon of Freyja. Members of their maintenance crews have reported hearing strange noises and seeing strange apparitions during these times, and some have even gone missing. Little information is known at this time. We advise all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Other stations in orbit around Indecipheres have reported strange occurrences as their orbit passes behind the moon of Freyja. Members of their research staffs have resorted to superstitious beliefs and rituals, including blood sacrifice to appease 'the Geometer' and strange invocations. The Wizard Federation denies any involvement in these events, and the company has no reason to doubt this statement - signs point towards the Cult of Nar'Sie as the likely culprit, but there is no concrete evidence at this time. We advise all crew members to remain vigilant, and to report any strange activity to your station's security team immediately.", + "Other stations in orbit around Indecipheres have reported strange occurrences as their orbit passes behind the moon of Freyja. The AI systems of these stations have exhibited malfunctions and strange behavior during these times, including erratic speech patterns and unprovoked aggression. Little information is known at this time. We advise the crew to monitor the station's AI closely, and to report any strange activity to the Research Director immediately.", "Plasma research has been progressing steadily, with several stations reporting successful tests and breakthroughs in the field. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", "Plasma research has seen explosive new developments recently, with several stations harnessing it for use in new weaponry. The company is excited to see what new innovations will come from this research, and encourages the crew to continue their diligent work in this field.", "Recent breakthroughs in Bluespace technology have resulted in strange and unpredictable effects on spacetime, including the creation of temporary wormholes and rifts in reality. Any anomalous activity should be reported to your station's research team.", + "Recent investigations into a crashed Syndicate cargo ship found advanced chameleon cloaking technology in the form of clothing marked as property of the organization known as 'MI13', able to imitate the designs of official Nanotrasen uniforms. Be aware of possible stolen valor in high-ranking personnel visiting your station and make sure to properly check for mindshield implants in high ranking staff.", "Recently, a shuttle full of evacuees fleeing a @pick(attack) Nanotrasen research station saw a strange event upon landing at their designated Central Command dropoff point - immediately upon landing, the evacuees all snapped and beat each other to death in a gruesome and violent frenzy. The company has no information on what caused this event, and suggests that all personnel keep up to date with their Psychological evaluations.", - "Reports have been circulating that members of mining teams assigned to Freja have been wandering on the surface of the moon, away from the station, until they are out of comms range - where they are never heard from again. The company attributes this to the harsh and unforgiving environment of Freja, and would like to remind all explorers and miners to stay within comms range at all times and to report to the Psychologist if experiencing any feelings of isolation or loneliness.", - "Reports that a strange @pick(virus) have been spreading across Spinward Sector colonies have been circulating amongst news agencies recently. All major organizations have released statements denying any involvement in the spread of this virus, and no cure is currently known. Cases of it spreading to orbital stations have been few and far between, but the company advises the medical staff to be on the lookout for any strange symptoms in patients, and to report any suspected cases to the Chief Medical Officer immediately.", - "Rumors have circulated that a small army of pre-civilized humanoids managed to appropriate a transport vessel that landed on their planet. Allegedly, they overpowered the crew with their primitive swords, spears, and poleaxes, and have since discovered how to activate the ship's autopilot systems - allowing them to continue their conquest to the vessel's home station. The company assures the crew that there is no cause for concern, for even if the story is true, Nanotrasen laser weaponry could easily disintegrate their simple weapons and armor.", - "Rumors that Nanotrasen are planning on widespread pay cuts in the Spinward Sector are unsubstantiated at this time. Please prevent the spread of misinformation amongst the crew in the meanwhile.", + "Reports have been circulating that members of mining teams assigned to Freyja have been wandering on the surface of the moon, away from the station, until they are out of comms range - where they are never heard from again. The company attributes this to the harsh and unforgiving environment of Freyja, and would like to remind all explorers and miners to stay within comms range at all times and to report to the Psychologist if experiencing any feelings of isolation or loneliness.", + "Reports have been circulating that some crew members on other stations have deliberately lingered in the proximity of unstable bioscrambler anomalies. It is rumored these individuals were attempting to mutate their organs, limbs, and other body parts into superior forms. DeForest Medical does not condone such unorthodox and often unreliable methods of biological augmentation.", + "Reports that a strange @pick(virus) have been spreading across Spinward Sector Coalition have been circulating amongst news agencies recently. All major organizations have released statements denying any involvement in the spread of this virus, and no cure is currently known. Cases of it spreading to orbital stations have been few and far between, but we advise medical staff to be on the lookout for any strange symptoms in patients, and to report any suspected cases to the Chief Medical Officer immediately.", + "Rumors have circulated that a small army of pre-civilized humanoids managed to appropriate a transport vessel that landed on their planet. Allegedly, they overpowered the crew with their primitive swords, spears, and poleaxes, and have since discovered how to activate the ship's autopilot systems - allowing them to continue their conquest to the vessel's home station. We assure the crew that there is no cause for concern, for even if the story is true, Nanotrasen laser weaponry could easily disintegrate their simple weapons and armor.", + "Rumors that Interdyne Pharmaceuticals have managed to synthesize a highly deadly and contagious @pick(virus) are plausible, though we currently have no concrete evidence to support this claim. In the event of contact with Syndiacte personnel, we advise the medical staff to be on the lookout for strange and unusual symptoms in patients, and to report any suspected cases to the Chief Medical Officer.", + "Rumors that Nanotrasen are planning on widespread pay cuts in the Spinward Sector are unsubstantiated at this time. We ask that you do your part in preventing the spread of misinformation amongst the crew in the meanwhile.", + "Rumors that the Syndicate have made inroads in Tiziran markets via the shipbuilding conglomerate 'Azik Interstellar' have been brewing forsome time, and seems more likely than ever with the recent discovery of Cybersun Industry material shipping into Tiziran space. It is plausible that the Lizardfolk may be supplying the Syndicate with advanced shuttlecraft - we advise the crew to be cautious of any vessels of Tiziran design approaching the station.", + "Rumors that the company outsources their clown and mime hirees from the clown and mime planets respectively are unsubstantiated at this time. All clown and mime employees staffed on Nanotrasen stations and colonies are vetted and hired through the company's standard hiring process, and are subject to the same training and background checks as all other employees.", + "Rumors that unmarked @pick(floor_things) found on the floor of maintenance tunnels may grant you 'superpowers' are entirely unsubstantiated. DeForest Medical does not condone the use of unmarked pills or syringes, and advises all crew members to bring any unmarked pills or syringes to the medical staff for proper identification and disposal.", + "Satellite photoimagery of both Freyja and Indecipheres have unveiled a number of strange ruins. While some are reminiscent of crash-landed space station segments, others appear to have far stranger, possibly alien origins. As a legal precaution, we request that any active mining operations leave these ruins for later preservation and archaeological endeavors. With that in mind, it is still scientifically important to analyze anything picked up along the surface.", "Several members of the crew have been found asleep at their desks recently. The company would like to remind the crew that while we understand that working in space can be stressful, sleeping on the job is not acceptable and may lead to disciplinary action. If you are feeling tired, please report to the station's dormitories for rest, and remember to take care of yourselves - your health and wellbeing is important to the company.", "Station diagnostics indicate that all systems are @pick(adjectives). Enjoy your shift today, and remember to report any issues with your station's systems to your station's Engineering team.", + "TerraGov recently announced that they will continue to maintain their 'peacekeeping' operations in the Spinward Sector. The TerraGov Marine Corps have already been deployed en masse to the sector under the pretenses of 'protecting joint SSC-TerraGov trade routes from piracy', and have swiftly began construction of military outposts. While the company holds doubts about their purpose, the Spinward Stellar Coalition - wishing to keep close ties with TerraGov - has released a statement supporting this move, citing the rising tensions between the Syndicate and Nanotrasen as a cause for concern.", "The Internal Affairs division have released a classified statement suggesting that the Syndicate have achieved a breakthrough in their sleeper agent program, and that despite corporate background checks, there is a non-zero chance that any member of the crew could be a long term Syndicate sleeper agent, capable of being activated at any moment to undergo heinous acts of sabotage, espionage, or even assassination.", "The Internal Affairs division have released a statement condemning the recent actions of the Syndicate, and have accused them of being responsible for several recent incidents in the Spinward Sector, including the spread of a dangerous @pick(virus) and the recent employee unrest. The Syndicate has denied these allegations, and there is currently no concrete evidence to support either claim.", "The Internal Affairs division would like to remind the crew that they do not have any direct involvement in station affairs. Anyone claiming to be an agent of Internal Affairs should be reported to your station's security team immediately. Any accusations that Internal Affairs, Central Command, or Nanotrasen as a whole is placing members of Internal Affairs on the station will be met with a conversation with an official Internal Affairs agent.", + "The Nanotrasen Department of Public Relations would like to remind all personnel that usage of 'modern' camouflage techniques to save money in the budget, while smart in theory, results in a bad look on the company's competency. When watching for petty crime, please stick to dedicated stealth technology such as cloaking modules for modular suits instead of trying to disguise yourself as the environment of a bland hallway with sticks, cloth, and patterned clothing.", + "The Nanotrasen Department of Information Technology would like to inform all crew on-duty that they have never, nor will ever, ask for your passwords. They would also like to mention that rumors of Nanotrasen installing subdermal cybernetic implants that automatically censors passwords from your vision are entirely unfounded - the company has no technology capable of such a feat, even for those of you with the password 'hunter2'. You know who you are.", + "The Spinward Stellar Coalition's president, Oleksandr Kushnirenko, has released a statement supporting the recent expansion of Nanotrasen operations in the Spinward Sector, and has praised the company for its dedication to exploration and innovation. When asked if he was concerned about the recent expansion of Bluespace Artillery platforms, Kushnirenko stated that he has full trust in the company's judgement and that he is confident that the company will use these platforms responsibly.", "The Syndicate has issued a statement claiming they are not responsible for any recent incidents within the Spinward Sector. Whether you believe them or not is up to you.", "The Syndicate seem to be attempting to recruit members of the crew for their cause, though the exact details of their plans are unknown. The company would like to remind the crew that their loyalty should lie with the company, and that any attempts to recruit for the Syndicate should be reported to your station's security team immediately.", - "The Wizard Federation were reported to be holding a convention in the Spinward Sector recently, though the exact location is unknown. The company has no reason to doubt the Federation's claim that the convention was purely social, though as Wizards lack a sense of right and wrong, the company advises the crew to stay on the lookout for any robed individuals appearing around the station.", + "The Wizard Federation were reported to be holding a symposium in the Spinward Sector recently, though the exact location is unknown. The company has no reason to doubt the Federation's claim that the meeting was purely social, though as Wizards lack a sense of right and wrong, the company advises the crew to stay on the lookout for any robed individuals suddenly appearing around the station.", + "The coffee corporation Jim Nortons recently announced a parternship with Nanotrasen, providing wayward spacers on distant stations with fresh coffee, straight from Sol. The company is excited to see the results of this partnership, and encourages the crew to enjoy their coffees responsibly.", "The company would like to inform the crew that the recent expansion of Bluespace Artillery platforms in the Spinward Sector is not a cause for concern - these batteries exist to protect the the inner station and colonies from larger threats, and are not authorized for offensive use.", "The recent disappearance of the clown has been acknowledged by Central Command. During their absence, we would like to make it clear that the company does not condone the actions of the missing clown, and that the crew should look into a replacement entertainer at their leisure.", "The station's communications systems are operating within @pick(adjectives) parameters. Good luck on your shift today, and remember to report any issues with comms to your station's Engineering team.", "There is nothing interesting to report at this time. Please continue with your duties as normal, and report any suspicious activity to your station's security team immediately.", - "Voyagers to Indeciphres have reported strange and unsettling dreams during their stay on the station. These dreams often involve themes of isolation, paranoia, and cosmic horror, and have been known to cause psychological distress in some individuals. The company advises the crew to report any instances of these dreams to the medical staff, and to seek help if they are experiencing any mental health issues as a result.", - "Voyagers to Indeciphres have returned with a plethora of strange and exotic souvenirs from their travels, including several items of great power. It is advised that any items returned from these voyages be handled with caution, and that any strange or anomalous items be brought to your station's research team for analysis.", + "Voyagers to Indecipheres have reported strange and unsettling dreams during their stay on the station. These dreams often involve themes of isolation, paranoia, and cosmic horror, and have been known to cause psychological distress in some individuals. We advise the crew to report any instances of these dreams to the medical staff, and to seek help if they are experiencing any mental health issues as a result.", + "Voyagers to Indecipheres have returned with a plethora of strange and exotic souvenirs from their travels, including several items of great power. It is advised that any items returned from these voyages be handled with caution, and that any strange or anomalous items be brought to your station's research team for analysis.", + "We have transposed Central Command's location to a new location within hyperspace. This is a variable-frequency event that the shuttles should automatically account for, with further details being confidential. Nanotrasen urges amateur space explorers against trying to find Central Command, as it is only meant to be available via scheduled shuttle services.", "Your station's AI is functioning within @pick(adjectives) parameters. Remember to report any issues with it to the Research Director." ], "intern_reports": [ - "Please replace this text with the report provided by the Communications team." + "Please replace this text with the report provided by the Communications team.", + "Please check the report for typos and grammatical errors before sending it to the station.", + "The coffee machine is currently out of order. We apologize for the inconvenience, and hope to have it back up and running soon." ], "adjectives": [ @@ -100,5 +116,7 @@ "xenopathogen" ], - "attack": ["assaulted", "attacked", "raided", "ransacked"] + "attack": ["assaulted", "attacked", "raided", "ransacked"], + + "floor_things": ["pills", "pills", "pills", "syringes"] } From 8048edf4e4d0f7ba9d3b4fd74e87837cca51ee4b Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 25 Feb 2026 18:15:33 -0600 Subject: [PATCH 18/21] Gwa --- code/__DEFINES/role_preferences.dm | 1 + code/modules/library/skill_learning/job_skillchips/_job.dm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index d0f6d4aace73..5ce4067f0492 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -65,6 +65,7 @@ #define ROLE_REV "Revolutionary" #define ROLE_REVENANT "Revenant" #define ROLE_SENTIENCE "Sentience Potion Spawn" +#define ROLE_EVIL_CLONE "Evil Clone" /// This flag specifically is used as a generic catch-all antag ban #define ROLE_SYNDICATE "Syndicate" diff --git a/code/modules/library/skill_learning/job_skillchips/_job.dm b/code/modules/library/skill_learning/job_skillchips/_job.dm index 4572baae4d6b..2be2e9a295ea 100644 --- a/code/modules/library/skill_learning/job_skillchips/_job.dm +++ b/code/modules/library/skill_learning/job_skillchips/_job.dm @@ -19,7 +19,7 @@ . = ..() if(isnull(job_type)) return INITIALIZE_HINT_QDEL - var/datum/job/job_datum = SSjob.GetJobType(job_type) + var/datum/job/job_datum = SSjob.get_job_type(job_type) if(!length(job_datum.base_skills)) stack_trace("Skill job skillchip on a job with no skills, USELESS. (job type: [job_type])") return INITIALIZE_HINT_QDEL From 823bf74079607962db672a3eead1994a1c132e5a Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 25 Feb 2026 18:20:05 -0600 Subject: [PATCH 19/21] Shoo --- .../RandomRuins/SpaceRuins/garbagetruck2.dmm | 1 - .../ruins/spaceruin_code/garbagetruck.dm | 103 ------------------ maplestation.dme | 1 - tgstation.dme | 1 - 4 files changed, 106 deletions(-) delete mode 100644 code/modules/mapfluff/ruins/spaceruin_code/garbagetruck.dm diff --git a/_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm b/_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm index 26756ca5da93..1bfbbc792af8 100644 --- a/_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm +++ b/_maps/RandomRuins/SpaceRuins/garbagetruck2.dmm @@ -102,7 +102,6 @@ "kp" = ( /obj/structure/safe, /obj/item/stamp/syndicate, -/obj/item/eyesnatcher, /obj/item/stack/spacecash/c1000, /turf/open/floor/plating, /area/ruin/space/has_grav/garbagetruck/medicalwaste) diff --git a/code/modules/mapfluff/ruins/spaceruin_code/garbagetruck.dm b/code/modules/mapfluff/ruins/spaceruin_code/garbagetruck.dm deleted file mode 100644 index e48bcfd0cebf..000000000000 --- a/code/modules/mapfluff/ruins/spaceruin_code/garbagetruck.dm +++ /dev/null @@ -1,103 +0,0 @@ -/obj/item/eyesnatcher - name = "portable eyeball extractor" - desc = "An overly complicated device that can pierce target's skull and extract their eyeballs if enough brute force is applied." - icon = 'icons/obj/medical/surgery_tools.dmi' - icon_state = "eyesnatcher" - base_icon_state = "eyesnatcher" - inhand_icon_state = "hypo" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - ///Whether it's been used to steal a pair of eyes already. - var/used = FALSE - -/obj/item/eyesnatcher/update_icon_state() - . = ..() - icon_state = "[base_icon_state][used ? "-used" : ""]" - -/obj/item/eyesnatcher/attack(mob/living/carbon/human/target, mob/living/user, params) - if(used || !istype(target) || !target.Adjacent(user)) //Works only once, no TK use - return ..() - - var/obj/item/organ/eyes/eyeballies = target.get_organ_slot(ORGAN_SLOT_EYES) - var/obj/item/bodypart/head/head = target.get_bodypart(BODY_ZONE_HEAD) - - if(!head || !eyeballies || target.is_eyes_covered()) - return ..() - var/eye_snatch_enthusiasm = 5 SECONDS - if(HAS_MIND_TRAIT(user, TRAIT_MORBID)) - eye_snatch_enthusiasm *= 0.7 - user.do_attack_animation(target, used_item = src) - target.visible_message( - span_warning("[user] presses [src] against [target]'s skull!"), - span_userdanger("[user] presses [src] against your skull!")) - if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) - return - - to_chat(target, span_userdanger("You feel something forcing its way into your skull!")) - balloon_alert(user, "applying pressure...") - if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) - return - - var/min_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = src) - var/max_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = src) - - target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(min_wound, max_wound + 10), attacking_item = src) - target.visible_message( - span_danger("[src] pierces through [target]'s skull, horribly mutilating their eyes!"), - span_userdanger("Something penetrates your skull, horribly mutilating your eyes! Holy fuck!"), - span_hear("You hear a sickening sound of metal piercing flesh!") - ) - eyeballies.apply_organ_damage(eyeballies.maxHealth) - target.emote("scream") - playsound(target, 'sound/effects/wounds/crackandbleed.ogg', 100) - log_combat(user, target, "cracked the skull of (eye snatching)", src) - - if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target))) - return - - if(!target.is_blind()) - to_chat(target, span_userdanger("You suddenly go blind!")) - if(prob(1)) - to_chat(target, span_notice("At least you got a new pirate-y look out of it...")) - var/obj/item/clothing/glasses/eyepatch/new_patch = new(target.loc) - target.equip_to_slot_if_possible(new_patch, ITEM_SLOT_EYES, disable_warning = TRUE) - - to_chat(user, span_notice("You successfully extract [target]'s eyeballs.")) - playsound(target, 'sound/items/handling/surgery/retractor2.ogg', 100, TRUE) - playsound(target, 'sound/effects/pop.ogg', 100, TRAIT_MUTE) - eyeballies.Remove(target) - eyeballies.forceMove(get_turf(target)) - notify_ghosts( - "[target] has just had their eyes snatched!", - source = target, - header = "Ouch!", - ) - target.emote("scream") - if(prob(20)) - target.emote("cry") - used = TRUE - update_appearance(UPDATE_ICON) - -/obj/item/eyesnatcher/examine(mob/user) - . = ..() - if(used) - . += span_notice("It has been used up.") - -/obj/item/eyesnatcher/proc/eyeballs_exist(obj/item/organ/eyes/eyeballies, obj/item/bodypart/head/head, mob/living/carbon/human/target) - if(!eyeballies || QDELETED(eyeballies)) - return FALSE - if(!head || QDELETED(head)) - return FALSE - - if(eyeballies.owner != target) - return FALSE - var/obj/item/organ/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) - //got different eyes or doesn't own the head... somehow - if(head.owner != target || eyes != eyeballies) - return FALSE - - return TRUE diff --git a/maplestation.dme b/maplestation.dme index 2c1138b3ff25..b1cd772c7226 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -4515,7 +4515,6 @@ #include "code\modules\mapfluff\ruins\spaceruin_code\derelict_sulaco.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\DJstation.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\forgottenship.dm" -#include "code\modules\mapfluff\ruins\spaceruin_code\garbagetruck.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hellfactory.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hilbertshotel.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\interdyne.dm" diff --git a/tgstation.dme b/tgstation.dme index 8d5497dfa734..b25a0686df5f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4529,7 +4529,6 @@ #include "code\modules\mapfluff\ruins\spaceruin_code\derelict_sulaco.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\DJstation.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\forgottenship.dm" -#include "code\modules\mapfluff\ruins\spaceruin_code\garbagetruck.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hellfactory.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hilbertshotel.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\interdyne.dm" From 92f07cb1220a1f0a0e4807da69d6396a64b6a63a Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 25 Feb 2026 18:30:08 -0600 Subject: [PATCH 20/21] Fix --- tgstation.dme | 2 -- 1 file changed, 2 deletions(-) diff --git a/tgstation.dme b/tgstation.dme index b25a0686df5f..92c6f5e61f49 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3242,7 +3242,6 @@ #include "code\modules\antagonists\ninja\ninja_stars.dm" #include "code\modules\antagonists\ninja\ninjaDrainAct.dm" #include "code\modules\antagonists\ninja\outfit.dm" -#include "code\modules\antagonists\nukeop\nukeop.dm" #include "code\modules\antagonists\nukeop\datums\operative.dm" #include "code\modules\antagonists\nukeop\datums\operative_leader.dm" #include "code\modules\antagonists\nukeop\datums\operative_lone.dm" @@ -3250,7 +3249,6 @@ #include "code\modules\antagonists\nukeop\outfits.dm" #include "code\modules\antagonists\nukeop\equipment\borgchameleon.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_authentication_disk.dm" -#include "code\modules\antagonists\nukeop\equipment\nuclear_challenge.dm" #include "code\modules\antagonists\nukeop\equipment\pinpointer.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_bomb\_nuclear_bomb.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_bomb\beer_nuke.dm" From f78ba3c327c9b383883988bf9d7bcd16ee828e1c Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 25 Feb 2026 18:33:55 -0600 Subject: [PATCH 21/21] Fu --- tgstation.dme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tgstation.dme b/tgstation.dme index 92c6f5e61f49..3b159fa675ab 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3242,11 +3242,11 @@ #include "code\modules\antagonists\ninja\ninja_stars.dm" #include "code\modules\antagonists\ninja\ninjaDrainAct.dm" #include "code\modules\antagonists\ninja\outfit.dm" +#include "code\modules\antagonists\nukeop\outfits.dm" #include "code\modules\antagonists\nukeop\datums\operative.dm" #include "code\modules\antagonists\nukeop\datums\operative_leader.dm" #include "code\modules\antagonists\nukeop\datums\operative_lone.dm" #include "code\modules\antagonists\nukeop\datums\operative_team.dm" -#include "code\modules\antagonists\nukeop\outfits.dm" #include "code\modules\antagonists\nukeop\equipment\borgchameleon.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_authentication_disk.dm" #include "code\modules\antagonists\nukeop\equipment\pinpointer.dm"

oiIGAZdsr5d65iY`& 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 literal 0 HcmV?d00001 diff --git a/sound/ambience/antag/hypnotized.ogg b/sound/ambience/antag/hypnotized.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3fc0183c4e9a08d68121338f5ab7b7d9c6bd57a4 GIT binary patch literal 52524 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 z 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 Cbs@t5 literal 0 HcmV?d00001 diff --git a/sound/misc/prompt1.ogg b/sound/misc/prompt1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b284fcaef5d310cdbfed9c4ee3aae9bb7486760e GIT binary patch literal 91247 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�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 S2elN0$py$|xXftKNcca`AcY$M literal 0 HcmV?d00001 diff --git a/strings/antagonist_flavor/traitor_flavor.json b/strings/antagonist_flavor/traitor_flavor.json index 93da8d01374e..773d2f55370f 100644 --- a/strings/antagonist_flavor/traitor_flavor.json +++ b/strings/antagonist_flavor/traitor_flavor.json @@ -111,5 +111,13 @@ "roundend_report": "was a terrorist from Waffle Corporation.", "ui_theme": "syndicate", "uplink": "You have been provided with a standard uplink to accomplish your task." + }, + "Contractor Support Unit": { + "allies": "You are being sent to help your designated agent. Their allegiences are above all others.", + "goal": "Help your designated agent to the furtest extent you can, their life is above your own.", + "introduction": "You are the Contractor Support Agent.", + "roundend_report": "was a contractor support agent.", + "ui_theme": "syndicate", + "uplink": "You do not come with your own uplink, defer to your agent." } } diff --git a/strings/traitor_flavor.json b/strings/traitor_flavor.json deleted file mode 100644 index abd0e765df57..000000000000 --- a/strings/traitor_flavor.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "Animal Rights Consortium": { - "allies": "You may cooperate with other syndicate operatives if they support our cause. Maybe you can convince the Bee Liberation Front operatives to cooperate for once?", - "goal": "The creatures of this world must be freed from the iron grasp of Nanotrasen, and you are their only hope!", - "introduction": "You are the ARC Terrorist.", - "ui_theme": "syndicate", - "uplink": "The Syndicate have graciously given one of their uplinks for your task." - }, - "Bee Liberation Front": { - "allies": "You may cooperate with other syndicate operatives if they support our cause. Maybe you can recruit an Animal Rights Consort to be useful for once?", - "goal": "We must prove ourselves to the Syndicate or we will not be able to join. Animal Rights Consort will roll us!", - "introduction": "You are the Bee Liberation Front Operative.", - "ui_theme": "syndicate", - "uplink": "The Syndicate have graciously given one of their uplinks to see if we are worthy." - }, - "Champions of Evil": { - "allies": "Anyone who sees as you see, feels as you feel, may join the Champions of Evil! That means the Syndicate, the self-serving, or even the insane, as long as it has a heart of darkness, it's cool with the Champions!", - "goal": "You've got some napkin-note-plans for some EVIL to do today. On the side, the Champions of Evil are always looking for more morally malodorous malefactors! Get some recruiting done!", - "introduction": "You are the Champion of Evil.", - "ui_theme": "neutral", - "uplink": "The Champions of Evil is well connected to the black market. Your uplink has been provided for utmost evil!" - }, - "Corporate Climber": { - "allies": "Death to the Syndicate.", - "goal": "Killing needlessly would make you some kind of traitor, or at least definitely seen as one. This is all just a means to an end.", - "introduction": "You are the Corporate Climber.", - "ui_theme": "neutral", - "uplink": "You have connections to the black market for the deeds. Knock off a few loose weights, and your climb will be so much smoother." - }, - "Cybersun Industries": { - "allies": "Fellow Cybersun operatives are to be trusted. Members of the MI13 organization can be trusted. All other syndicate operatives are not to be trusted.", - "goal": "Do not establish substantial presence on the designated facility, as larger incidents are harder to cover up.", - "introduction": "You are from Cybersun Industries.", - "ui_theme": "syndicate", - "uplink": "You have been supplied the tools for the job in the form of a standard syndicate uplink." - }, - "Donk Corporation": { - "allies": "Members of Waffle Co. are to be killed on sight; they are not allowed to be on the station while we're around.", - "goal": "We do not approve of mindless killing of innocent workers; \"get in, get done, get out\" is our motto.", - "introduction": "You are the Donk Co. Traitor.", - "ui_theme": "syndicate", - "uplink": "You have been provided with a standard uplink to accomplish your task." - }, - "Gone Postal": { - "allies": "If the syndicate learns of your plan, they're going to kill you and take your uplink. Take no chances.", - "goal": "The preparations are finally complete. Today is the day you go postal. You're going to hijack the emergency shuttle and live a new life free of Nanotrasen.", - "introduction": "You're going postal today.", - "ui_theme": "neutral", - "uplink": "You've actually managed to steal a full uplink a month ago. This should certainly help accomplish your goals." - }, - "Gorlex Marauders": { - "allies": "You may collaborate with any friends of the Syndicate coalition, but keep an eye on any of those Tiger punks if they do show up.", - "goal": "Getting noticed is not an issue, and you may use any level of ordinance to get the job done. That being said, do not make this sloppy by dragging in random slaughter.", - "introduction": "You are a Gorlex Marauder.", - "ui_theme": "syndicate", - "uplink": "You have been provided with a standard uplink to accomplish your task." - }, - "Internal Affairs Agent": { - "allies": "Death to the Syndicate. Killing infiltrators is a whole different kind of internal affair we fully approve of dealing with.", - "goal": "While you have a license to kill, unneeded property damage or loss of employee life will lead to your contract being terminated.", - "introduction": "You are the Internal Affairs Agent.", - "ui_theme": "ntos", - "uplink": "For the sake of plausible deniability, you have been equipped with an array of captured Syndicate weaponry available via uplink." - }, - "Legal Trouble": { - "allies": "Death to the Syndicate.", - "goal": "Try to finish your to-do list, and don't get caught. If they find out what you're actually doing, this scandal will go galactic.", - "introduction": "You are in legal trouble.", - "ui_theme": "neutral", - "uplink": "You've connected to the black market to clean this mess up. If there's no evidence, there's no crime." - }, - "MI13": { - "allies": "You are the only operative we are sending, any others are fake. All other syndicate operatives are not to be trusted, with the exception of Cybersun operatives.", - "goal": "Avoid killing innocent personnel at all costs. You are not here to mindlessly kill people, as that would attract too much attention and is not our goal. Avoid detection at all costs.", - "introduction": "You are the MI13 Agent.", - "ui_theme": "syndicate", - "uplink": "You have been provided with a standard uplink to accomplish your task." - }, - "Tiger Cooperative Fanatic": { - "allies": "Only the enlightened Tiger brethren can be trusted; all others must be expelled from this mortal realm!", - "goal": "Remember the teachings of Hy-lurgixon; kill first, ask questions later!", - "introduction": "You are the Tiger Cooperative Fanatic.", - "ui_theme": "abductor", - "uplink": "You have been provided with a hy-lurgixon tome to prove yourself to the changeling hive. If you accomplish your tasks, you will be assimilated.", - "uplink_name": "hy-lurgixon tome" - }, - "Waffle Corporation": { - "allies": "Members of Donk Co. are to be killed on sight; they are not allowed to be on the station while we're around. Do not trust fellow members of the Waffle.co (but try not to rat them out), as they might have been assigned opposing objectives.", - "goal": "You are not here for a stationwide demonstration. Again, other Waffle Co. Traitors may be, so watch out. Your job is to only accomplish your objectives.", - "introduction": "You are the Waffle Co. Traitor.", - "ui_theme": "syndicate", - "uplink": "You have been provided with a standard uplink to accomplish your task." - }, - "Waffle Corporation Terrorist": { - "allies": "Most other syndicate operatives are not to be trusted, except for members of the Gorlex Marauders. Do not trust fellow members of the Waffle.co (but try not to rat them out), as they might have been assigned opposing objectives.", - "goal": "Our investors need a demonstation of our pledge to destroying Nanotrasen. Let's give them a loud one!", - "introduction": "You are the Waffle Corporation Terrorist.", - "ui_theme": "syndicate", - "uplink": "You have been provided with a standard uplink to accomplish your task." - } -} From 65afb9c86c9d3094f34e48f017e28cf991de9c95 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 12 Feb 2026 22:14:14 -0600 Subject: [PATCH 07/21] Fixes --- .../configuration/entries/game_options.dm | 26 ++---- code/datums/id_trim/jobs.dm | 24 ------ code/datums/station_traits/job_traits.dm | 53 ------------ code/datums/station_traits/positive_traits.dm | 1 - code/modules/antagonists/malf_ai/malf_ai.dm | 1 + .../station_trait/bridge_assistant.dm | 82 ------------------- config/game_options.txt | 14 ++-- maplestation.dme | 1 - .../code/datums/votes/chaos.dm | 15 +++- .../code/datums/votes/transfer_vote.dm | 10 ++- .../modules/jobs/job_types/bridge_officer.dm | 6 -- .../code/modules/jobs/job_types/stowaway.dm | 6 +- tgui/packages/tgui/interfaces/VotePanel.tsx | 4 +- 13 files changed, 39 insertions(+), 204 deletions(-) delete mode 100644 code/modules/jobs/job_types/station_trait/bridge_assistant.dm diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index c15811d3360b..a2b8dc10a88c 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -52,31 +52,11 @@ /datum/config_entry/flag/disable_warops -/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors - default = 6 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/brother_scaling_coeff //how many players per brother team - default = 25 - integer = FALSE - min_val = 0 - /// Determines how fast traitors scale in general. /datum/config_entry/number/traitor_scaling_multiplier default = 1 min_val = 0.01 -/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings - default = 6 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/ecult_scaling_coeff //how much does the amount of players get divided by to determine e_cult - default = 6 - integer = FALSE - min_val = 0 - /datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions default = 8 integer = FALSE @@ -136,6 +116,10 @@ /datum/config_entry/flag/no_summon_events //Allowed /datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes. + default = FALSE + +/datum/config_entry/flag/no_dynamic_report //dynamic's status is not reported in the roundstart report + default = TRUE /datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station default = 55 @@ -165,7 +149,7 @@ default = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." /datum/config_entry/flag/roundstart_blue_alert - default = TRUE + default = FALSE /datum/config_entry/flag/revival_pod_plants diff --git a/code/datums/id_trim/jobs.dm b/code/datums/id_trim/jobs.dm index a2079a16f43e..9865ede38073 100644 --- a/code/datums/id_trim/jobs.dm +++ b/code/datums/id_trim/jobs.dm @@ -205,30 +205,6 @@ ) job = /datum/job/botanist -/datum/id_trim/job/bridge_assistant - assignment = "Bridge Assistant" - trim_state = "trim_assistant" - department_color = COLOR_COMMAND_BLUE - subdepartment_color = COLOR_COMMAND_BLUE - sechud_icon_state = SECHUD_BRIDGE_ASSISTANT - minimal_access = list( - ACCESS_COMMAND, - ACCESS_EVA, - ACCESS_GATEWAY, - ACCESS_MAINT_TUNNELS, - ACCESS_RC_ANNOUNCE, - ACCESS_TELEPORTER, - ACCESS_WEAPONS, - ) - extra_access = list() - template_access = list( - ACCESS_CAPTAIN, - ACCESS_CHANGE_IDS, - ) - job = /datum/job/bridge_assistant - honorifics = list("Underling", "Assistant", "Mate") - honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL - /datum/id_trim/job/captain assignment = "Captain" intern_alt_name = "Captain-in-Training" diff --git a/code/datums/station_traits/job_traits.dm b/code/datums/station_traits/job_traits.dm index a5d6125adeea..70fe2f443d1b 100644 --- a/code/datums/station_traits/job_traits.dm +++ b/code/datums/station_traits/job_traits.dm @@ -95,59 +95,6 @@ if(GLOB.cargo_ripley) qdel(GLOB.cargo_ripley) -/datum/station_trait/job/bridge_assistant - name = "Bridge Assistant" - button_desc = "Sign up to become the Bridge Assistant and watch over the Bridge." - weight = 2 - report_message = "We have installed a Bridge Assistant on your station." - show_in_report = TRUE - job_to_add = /datum/job/bridge_assistant - -/datum/station_trait/job/bridge_assistant/New() - . = ..() - RegisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(add_coffeemaker)) - -/datum/station_trait/job/bridge_assistant/on_lobby_button_update_overlays(atom/movable/screen/lobby/button/sign_up/lobby_button, list/overlays) - . = ..() - overlays += "bridge_assistant" - -/// Creates a coffeemaker in the bridge, if we don't have one yet. -/datum/station_trait/job/bridge_assistant/proc/add_coffeemaker(datum/source) - SIGNAL_HANDLER - var/area/bridge = GLOB.areas_by_type[/area/station/command/bridge] - if(isnull(bridge)) //no bridge, what will he assist? - return - var/list/possible_coffeemaker_positions = list(/area/station/command/bridge, /area/station/command/meeting_room) - var/list/coffeemakers = SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/coffeemaker) - for(var/obj/machinery/coffeemaker as anything in coffeemakers) //don't spawn a coffeemaker if there is already one on the bridge - if(is_type_in_list(get_area(coffeemaker), possible_coffeemaker_positions)) - return - var/list/tables = list() - for(var/turf/area_turf as anything in bridge.get_turfs_from_all_zlevels()) - var/obj/structure/table/table = locate() in area_turf - if(isnull(table)) - continue - if(area_turf.is_blocked_turf(ignore_atoms = list(table))) //don't spawn a coffeemaker on a fax machine or smth - continue - tables += table - if(!length(tables)) - return - var/picked_table = pick_n_take(tables) - var/picked_turf = get_turf(picked_table) - if(length(tables)) - var/another_table = pick(tables) - for(var/obj/thing_on_table in picked_turf) //if there's paper bins or other shit on the table, get that off - if(thing_on_table == picked_table) - continue - if(HAS_TRAIT(thing_on_table, TRAIT_WALLMOUNTED) || (thing_on_table.flags_1 & ON_BORDER_1) || thing_on_table.layer < TABLE_LAYER) - continue - if(thing_on_table.invisibility || !thing_on_table.alpha || !thing_on_table.mouse_opacity) - continue - thing_on_table.forceMove(get_turf(another_table)) - new /obj/machinery/coffeemaker/impressa(picked_turf) - new /obj/item/reagent_containers/cup/coffeepot(picked_turf) - new /obj/item/storage/box/coffeepack(picked_turf) - /datum/station_trait/job/veteran_advisor name = "Veteran Advisor" button_desc = "Sign up to become a DISABLED but hard boiled Veteran Advisor of Nanotrasen Security Force. Advise HoS and Captain, train Officers, all while fighting your PTSD." diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm index 539cdf2027ff..7d5031c28aec 100644 --- a/code/datums/station_traits/positive_traits.dm +++ b/code/datums/station_traits/positive_traits.dm @@ -234,7 +234,6 @@ /datum/job/warden = /obj/item/organ/cyberimp/eyes/hud/security, // NON-MODULE CHANGE /datum/job/asset_protection = /obj/item/organ/eyes/robotic/thermals, - /datum/job/bridge_assistant = /obj/item/organ/eyes/robotic, /datum/job/bridge_officer = /obj/item/organ/eyes/robotic, /datum/job/noble_ambassador = /obj/item/organ/cyberimp/chest/nutriment/plus, /datum/job/ordnance_tech = /obj/item/organ/cyberimp/arm/toolset, diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm index 55b770ca7416..c1ff66152a6a 100644 --- a/code/modules/antagonists/malf_ai/malf_ai.dm +++ b/code/modules/antagonists/malf_ai/malf_ai.dm @@ -10,6 +10,7 @@ ui_name = "AntagInfoMalf" can_assign_self_objectives = TRUE default_custom_objective = "Make sure your precious crew are incapable of ever, ever leaving you." + stinger_sound = 'sound/ambience/antag/malf.ogg' ///the name of the antag flavor this traitor has. var/employer ///assoc list of strings set up after employer is given diff --git a/code/modules/jobs/job_types/station_trait/bridge_assistant.dm b/code/modules/jobs/job_types/station_trait/bridge_assistant.dm deleted file mode 100644 index f7c60a1dc461..000000000000 --- a/code/modules/jobs/job_types/station_trait/bridge_assistant.dm +++ /dev/null @@ -1,82 +0,0 @@ -/datum/job/bridge_assistant - title = JOB_BRIDGE_ASSISTANT - description = "Watch over the Bridge, command its consoles, and spend your days brewing coffee for higher-ups." - auto_deadmin_role_flags = DEADMIN_POSITION_HEAD //not really a head but close enough - department_head = list(JOB_CAPTAIN) - faction = FACTION_STATION - total_positions = 0 - spawn_positions = 0 - supervisors = "the Captain, and in non-Bridge related situations the other heads" - minimal_player_age = 7 - exp_requirements = 300 - exp_required_type = EXP_TYPE_CREW - exp_granted_type = EXP_TYPE_CREW - config_tag = "BRIDGE_ASSISTANT" - - base_outfit = /datum/outfit/job/bridge_assistant - plasmaman_outfit = /datum/outfit/plasmaman/bridge_assistant - - paycheck = PAYCHECK_CREW - paycheck_department = ACCOUNT_CIV - - liver_traits = list(TRAIT_PRETENDER_ROYAL_METABOLISM) - - display_order = JOB_DISPLAY_ORDER_BRIDGE_ASSISTANT - departments_list = list(/datum/job_department/command) - - family_heirlooms = list( - // /obj/item/banner/command/mundane, - ) - - mail_goodies = list( - /obj/item/storage/fancy/cigarettes = 1, - /obj/item/pen/fountain = 1, - ) - rpg_title = "Royal Guard" - allow_bureaucratic_error = FALSE - job_flags = STATION_JOB_FLAGS | STATION_TRAIT_JOB_FLAGS | JOB_ANTAG_PROTECTED - ignore_human_authority = TRUE - -/datum/job/bridge_assistant/after_spawn(mob/living/spawned, client/player_client) - . = ..() - ADD_TRAIT(spawned, TRAIT_NO_TWOHANDING, JOB_TRAIT) - -/datum/job/bridge_assistant/get_roundstart_spawn_point() - var/list/chair_turfs = list() - var/list/possible_turfs = list() - var/area/bridge = GLOB.areas_by_type[/area/station/command/bridge] - if(isnull(bridge)) - return ..() //if no bridge, spawn on the arrivals shuttle (but also what the fuck) - for (var/list/zlevel_turfs as anything in bridge.get_zlevel_turf_lists()) - for (var/turf/possible_turf as anything in zlevel_turfs) - if(possible_turf.is_blocked_turf()) - continue - if(locate(/obj/structure/chair) in possible_turf) - chair_turfs += possible_turf - continue - possible_turfs += possible_turf - if(length(chair_turfs)) - return pick(chair_turfs) //prioritize turfs with a chair - if(length(possible_turfs)) - return pick(possible_turfs) //if none, just pick a random turf in the bridge - return ..() //if the bridge has no turfs, spawn on the arrivals shuttle - -/datum/outfit/job/bridge_assistant - name = "Bridge Assistant" - jobtype = /datum/job/bridge_assistant - - id_trim = /datum/id_trim/job/bridge_assistant - backpack_contents = list( - /obj/item/modular_computer/pda/bridge_assistant = 1, - ) - - uniform = /obj/item/clothing/under/trek/command/next - neck = /obj/item/clothing/neck/large_scarf/blue - belt = /obj/item/storage/belt/utility/full/inducer - ears = /obj/item/radio/headset/headset_com - glasses = /obj/item/clothing/glasses/sunglasses - gloves = /obj/item/clothing/gloves/fingerless - head = /obj/item/clothing/head/soft/black - shoes = /obj/item/clothing/shoes/laceup - l_pocket = /obj/item/gun/energy/e_gun/mini - r_pocket = /obj/item/assembly/flash/handheld diff --git a/config/game_options.txt b/config/game_options.txt index 8d27aa7ffcec..1ab258884828 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -84,13 +84,16 @@ STATION_GOAL_BUDGET 1 ## If TRUE / 1, station is raised to blue alert at roundstart. ## If FALSE / 0, station remains at green alert. ## Roundstart command report and greendshift announcements are unaffected. -ROUNDSTART_BLUE_ALERT 1 +ROUNDSTART_BLUE_ALERT 0 ## GAME MODES ### -## Uncomment to not send a roundstart intercept report. Gamemodes may override this. +## Uncomment to not send a roundstart intercept report. #NO_INTERCEPT_REPORT +## Uncomment to not include dynamic info in the roundstart report. +NO_DYNAMIC_REPORT + ## Percent weight reductions for three of the most recent modes REPEATED_MODE_ADJUST 45 30 10 @@ -98,13 +101,6 @@ REPEATED_MODE_ADJUST 45 30 10 ## The amount of time it takes for the emergency shuttle to be called, from round start. SHUTTLE_REFUEL_DELAY 12000 -## Variables calculate how number of antagonists will scale to population. -## Used as (Antagonists = Population / Coeff) -## Set to 0 to disable scaling and use default numbers instead. -TRAITOR_SCALING_COEFF 6 -BROTHER_SCALING_COEFF 6 -CHANGELING_SCALING_COEFF 6 - ## Variables calculate how number of open security officer positions will scale to population. ## Used as (Officers = Population / Coeff) ## Set to 0 to disable scaling and use default numbers instead. diff --git a/maplestation.dme b/maplestation.dme index fb72ce27c06c..df03a042e6b1 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -4357,7 +4357,6 @@ #include "code\modules\jobs\job_types\spawner\syndicate_cybersun_captain.dm" #include "code\modules\jobs\job_types\spawner\venus_human_trap.dm" #include "code\modules\jobs\job_types\spawner\zombie.dm" -#include "code\modules\jobs\job_types\station_trait\bridge_assistant.dm" #include "code\modules\jobs\job_types\station_trait\cargo_gorilla.dm" #include "code\modules\jobs\job_types\station_trait\veteran_advisor.dm" #include "code\modules\keybindings\bindings_atom.dm" diff --git a/maplestation_modules/code/datums/votes/chaos.dm b/maplestation_modules/code/datums/votes/chaos.dm index dcb8881565be..23009727a91e 100644 --- a/maplestation_modules/code/datums/votes/chaos.dm +++ b/maplestation_modules/code/datums/votes/chaos.dm @@ -10,6 +10,11 @@ count_method = VOTE_COUNT_METHOD_MULTI display_statistics = FALSE +/datum/vote/round_chaos/initiate_vote(initiator, duration) + . = ..() + . += "
" + . += span_slightly_smaller("This vote is used to gauge player sentiment and has no mechanical effect.") + /datum/vote/round_chaos/can_mob_vote(mob/voter) // Roundstart observers / people who DNR'd have no say if(isobserver(voter) && isnull(voter.mind)) @@ -25,7 +30,15 @@ return ..() /datum/vote/round_chaos/tiebreaker(list/winners) - return jointext(winners, "-") + switch(length(winners)) + if(2) + return jointext(winners, "-") + if(3) + return winners[2] // averaged out + if(4) + return "Any" + + return "Unknown" /datum/vote/round_chaos/finalize_vote(winning_option) SSticker.voted_round_chaos = winning_option diff --git a/maplestation_modules/code/datums/votes/transfer_vote.dm b/maplestation_modules/code/datums/votes/transfer_vote.dm index 22f251315715..382bd04e08f9 100644 --- a/maplestation_modules/code/datums/votes/transfer_vote.dm +++ b/maplestation_modules/code/datums/votes/transfer_vote.dm @@ -9,6 +9,11 @@ CHOICE_CONTINUE, ) +/datum/vote/autotransfer/initiate_vote(initiator, duration) + . = ..() + . += "
" + . += span_slightly_smaller("This vote is OOC. On a success, a transfer shuttle will be called.") + /datum/vote/autotransfer/toggle_votable() CONFIG_SET(flag/allow_vote_transfer, !CONFIG_GET(flag/allow_vote_transfer)) @@ -23,10 +28,7 @@ if(!forced && !CONFIG_GET(flag/allow_vote_transfer)) return "Transfer votes are disabled." - if(!SScrewtransfer) - return "Transfer subsystem missing. Can't really host a vote for it! This is a bug." - - if(SScrewtransfer.transfer_vote_successful) + if(SScrewtransfer?.transfer_vote_successful) return "A transfer vote has already passed." return VOTE_AVAILABLE diff --git a/maplestation_modules/code/modules/jobs/job_types/bridge_officer.dm b/maplestation_modules/code/modules/jobs/job_types/bridge_officer.dm index 531243c97f1e..029ffa9314b3 100644 --- a/maplestation_modules/code/modules/jobs/job_types/bridge_officer.dm +++ b/maplestation_modules/code/modules/jobs/job_types/bridge_officer.dm @@ -1,10 +1,4 @@ // -- Bridge Officer job & outfit datum -- -/datum/station_trait/job/bridge_assistant - weight = 0 - -/datum/job/bridge_assistant - rpg_title = "Lesser Guildperson" - /datum/job/bridge_officer title = JOB_BRIDGE_OFFICER description = "File paperwork to Central Command via your fax machine, \ diff --git a/maplestation_modules/code/modules/jobs/job_types/stowaway.dm b/maplestation_modules/code/modules/jobs/job_types/stowaway.dm index 1d6f9efd5952..ad8560fae607 100644 --- a/maplestation_modules/code/modules/jobs/job_types/stowaway.dm +++ b/maplestation_modules/code/modules/jobs/job_types/stowaway.dm @@ -7,7 +7,7 @@ paycheck = PAYCHECK_ZERO total_positions = 0 spawn_positions = 1 - supervisors = "no one" + supervisors = "no one (yet)" exp_granted_type = EXP_TYPE_CREW config_tag = "STOWAWAY" faction = FACTION_STATION @@ -28,6 +28,10 @@ /datum/job/stowaway/get_default_roundstart_spawn_point() return find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) +/datum/job/stowaway/get_spawn_message_information() + . = ..() + . += span_notice("You are free to steal and evade security as you please, but remember you are not an antagonist.") + /datum/job/stowaway/after_spawn(mob/living/spawned, client/player_client) . = ..() var/datum/status_effect/backstory/backstory = spawned.apply_status_effect(/datum/status_effect/backstory) diff --git a/tgui/packages/tgui/interfaces/VotePanel.tsx b/tgui/packages/tgui/interfaces/VotePanel.tsx index 183882cdba7c..c41c27c1b66d 100644 --- a/tgui/packages/tgui/interfaces/VotePanel.tsx +++ b/tgui/packages/tgui/interfaces/VotePanel.tsx @@ -300,7 +300,9 @@ const ChoicesPanel = (props) => { user.multiSelection[user.ckey.concat(choice.name)] === 1 ? ( ) : null} - {choice.votes} Votes + {currentVote.displayStatistics + ? `${choice.votes} Votes` + : null} From 5c19202d9a683269fdfbae6286964a736e422e68 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 13 Feb 2026 00:11:47 -0600 Subject: [PATCH 08/21] Adds omega drip to the status summary --- .../dcs/signals/signals_transform.dm | 3 + code/__HELPERS/_string_lists.dm | 8 +- code/__HELPERS/priority_announce.dm | 4 +- code/datums/communications.dm | 46 ++++--- code/datums/components/transforming.dm | 2 +- code/modules/admin/verbs/adminevents.dm | 11 +- code/modules/admin/verbs/commandreport.dm | 2 +- code/modules/asset_cache/assets/paper.dm | 10 +- code/modules/paperwork/paper.dm | 115 ++++++++++++++---- code/modules/paperwork/stamps.dm | 2 +- code/modules/station_goals/bsa.dm | 4 +- code/modules/station_goals/dna_vault.dm | 4 +- code/modules/station_goals/meteor_shield.dm | 4 +- .../icons/nanotrasen-logo.png | Bin 0 -> 4813 bytes strings/flavor_reports.json | 76 ++++++++++++ 15 files changed, 234 insertions(+), 57 deletions(-) create mode 100644 maplestation_modules/icons/nanotrasen-logo.png create mode 100644 strings/flavor_reports.json diff --git a/code/__DEFINES/dcs/signals/signals_transform.dm b/code/__DEFINES/dcs/signals/signals_transform.dm index a70e4c0b1963..68a13f824a6b 100644 --- a/code/__DEFINES/dcs/signals/signals_transform.dm +++ b/code/__DEFINES/dcs/signals/signals_transform.dm @@ -8,3 +8,6 @@ #define COMSIG_TRANSFORMING_ON_TRANSFORM "transforming_on_transform" /// Return COMPONENT_NO_DEFAULT_MESSAGE to prevent the transforming component from displaying the default transform message / sound. #define COMPONENT_NO_DEFAULT_MESSAGE (1<<0) + +/// From /datum/component/transforming/proc/on_transform_end(obj/item/source, mob/user): (mob/source, obj/item/transforming, active) +#define COMSIG_MOB_TRANSFORMING_ITEM "mob_transforming_item" diff --git a/code/__HELPERS/_string_lists.dm b/code/__HELPERS/_string_lists.dm index 4d4a7ff94c84..c8a074cd07b9 100644 --- a/code/__HELPERS/_string_lists.dm +++ b/code/__HELPERS/_string_lists.dm @@ -20,8 +20,8 @@ GLOBAL_VAR(string_filename_current_key) CRASH("strings list not found: [STRING_DIRECTORY]/[filepath], index=[key]") /proc/strings(filepath as text, key as text, directory = STRING_DIRECTORY) - if(IsAdminAdvancedProcCall()) - return + // if(IsAdminAdvancedProcCall()) + // return filepath = sanitize_filepath(filepath) load_strings_file(filepath, directory) @@ -34,8 +34,8 @@ GLOBAL_VAR(string_filename_current_key) return pick_list(GLOB.string_filename_current_key, group1) /proc/load_strings_file(filepath, directory = STRING_DIRECTORY) - if(IsAdminAdvancedProcCall()) - return + // if(IsAdminAdvancedProcCall()) + // return GLOB.string_filename_current_key = filepath if(filepath in GLOB.string_cache) diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm index 35e7898d9b4d..5e2d5f9be6e8 100644 --- a/code/__HELPERS/priority_announce.dm +++ b/code/__HELPERS/priority_announce.dm @@ -91,7 +91,7 @@ else GLOB.news_network.submit_article(text, "[command_name()] Update", NEWSCASTER_STATION_ANNOUNCEMENTS , null) -/proc/print_command_report(text = "", title = null, announce=TRUE) +/proc/print_command_report(text = "", title = null, announce = TRUE, contains_advanced_html = FALSE) if(!title) title = "Classified [command_name()] Update" @@ -107,7 +107,7 @@ message.title = title message.content = text - GLOB.communications_controller.send_message(message) + GLOB.communications_controller.send_message(message, contains_advanced_html = contains_advanced_html) /** * Sends a minor annoucement to players. diff --git a/code/datums/communications.dm b/code/datums/communications.dm index bab90027eae5..a5678fcf4169 100644 --- a/code/datums/communications.dm +++ b/code/datums/communications.dm @@ -10,6 +10,9 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n /// Are we trying to send a cross-station message that contains soft-filtered words? If so, flip to TRUE to extend the time admins have to cancel the message. var/soft_filtering = FALSE + /// The main content of the roundstart report + /// If nothing is set, it will pick a random flavor report + var/command_report_main_content = "" /// A list of footnote datums, to be added to the bottom of the roundstart command report. var/list/command_report_footnotes = list() /// A counter of conditions that are blocking the command report from printing. Counter incremements up for every blocking condition, and de-incrememnts when it is complete. @@ -48,7 +51,7 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n user.log_talk(input, LOG_SAY, tag="priority announcement") message_admins("[ADMIN_LOOKUPFLW(user)] has made a priority announcement.") -/datum/communciations_controller/proc/send_message(datum/comm_message/sending,print = TRUE,unique = FALSE) +/datum/communciations_controller/proc/send_message(datum/comm_message/sending,print = TRUE,unique = FALSE, contains_advanced_html = FALSE) for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list) if(!(C.machine_stat & (BROKEN|NOPOWER)) && is_station_level(C.z)) if(unique) @@ -59,7 +62,8 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n if(print) var/obj/item/paper/printed_paper = new /obj/item/paper(C.loc) printed_paper.name = "paper - '[sending.title]'" - printed_paper.add_raw_text(sending.content) + printed_paper.add_raw_text("[sending.content]", advanced_html = contains_advanced_html) + printed_paper.color = "#deebff" printed_paper.update_appearance() // Called AFTER everyone is equipped with their job @@ -71,22 +75,36 @@ GLOBAL_DATUM_INIT(communications_controller, /datum/communciations_controller, n addtimer(CALLBACK(src, PROC_REF(send_roundstart_report), greenshift), 10 SECONDS) return - var/dynamic_report = SSdynamic.get_advisory_report() - if(isnull(greenshift)) // if we're not forced to be greenshift or not - check if we are an actual greenshift - greenshift = SSdynamic.current_tier.tier == 0 && dynamic_report == /datum/dynamic_tier/greenshift::advisory_report + . = "" + . += "