Files
Aurora.3/code/controllers/subsystems/processing/odyssey.dm
smellie 3699c4f3e5 Odyssey Canonicity Coinflip (#20644)
Adds a `possible_scenario_types` list to Odyssey Scenarios containing
allowed canonicity types for a scenario.
`odyssey/proc/setup_scenario_variables()` now sets `scenario_type` by
picking from the `possible_scenario_types`.

By default or if the `possible_scenario_types` list is unset, a scenario
defaults to Non-Canon.

This allows a scenario to have both Canon and Non-Canon as an option.

Additionally, the Ruined Propellant Depot scenario has been given the
option of both Non-Canon and Canon to pick from.
Supercedes https://github.com/Aurorastation/Aurora.3/pull/20264.
2025-03-27 11:21:03 +00:00

262 lines
9.7 KiB
Plaintext

SUBSYSTEM_DEF(odyssey)
name = "Odyssey"
init_order = INIT_ORDER_ODYSSEY
runlevels = RUNLEVELS_PLAYING
can_fire = FALSE //We process only if we are running an odyssey scenario, this is set to TRUE by `pick_odyssey()`
/// The selected scenario singleton.
var/singleton/scenario/scenario
/// The z-levels that the odyssey takes place in.
var/list/scenario_zlevels = list()
/// This is the site the odyssey takes place on. If null, then the mission takes place on a non-site zlevel.
/// Should only be changed through set_scenario_site().
var/datum/map_template/ruin/away_site/scenario_site
/// A list of currently spawned actors for easy access.
var/list/mob/living/carbon/human/actors
/// A list of currently spawned storytellers for easy access.
var/list/mob/abstract/ghost/storyteller/storytellers
/// Whether or not we've sent the odyssey's roundstart report yet.
var/has_sent_roundstart_announcement = FALSE
/// The current station map's overmap object. We keep it here for convenience.
var/obj/effect/overmap/visitable/ship/main_map
/// If ships can dock on the Odyssey site. True by default, edited by the scenario.
var/site_landing_restricted = TRUE
/datum/controller/subsystem/odyssey/Initialize()
return SS_INIT_SUCCESS
/datum/controller/subsystem/odyssey/Recover()
scenario = SSodyssey.scenario
scenario_site = SSodyssey.scenario_site
scenario_zlevels = SSodyssey.scenario_zlevels
actors = SSodyssey.actors
storytellers = SSodyssey.storytellers
/datum/controller/subsystem/odyssey/fire()
if(!has_sent_roundstart_announcement)
// First of all, notify the Horizon.
addtimer(CALLBACK(scenario, TYPE_PROC_REF(/singleton/scenario, send_main_map_message), main_map), rand(4 MINUTES, 6 MINUTES))
addtimer(CALLBACK(scenario, TYPE_PROC_REF(/singleton/scenario, unrestrict_landing_and_message_horizon), main_map), 40 MINUTES)
var/obj/effect/overmap/odyssey_site = get_odyssey_overmap_effect()
if(odyssey_site)
// Next, notify the offships - these announcements can happen earlier to potentially give them a bit of an edge in reaching the objective area.
addtimer(CALLBACK(scenario, TYPE_PROC_REF(/singleton/scenario, notify_offships), odyssey_site), rand(20 MINUTES, 50 MINUTES))
has_sent_roundstart_announcement = TRUE
/**
* Returns the current scenario's overmap effect. Returns null if there isn't any.
*/
/datum/controller/subsystem/odyssey/proc/get_odyssey_overmap_effect()
var/obj/effect/overmap/odyssey_site
for(var/z in scenario_zlevels)
odyssey_site = GLOB.map_sectors["[z]"]
if(!istype(odyssey_site))
continue
return odyssey_site
/**
* Picks a random odyssey while keeping in mind sector requirements.
* If successful, makes the SS start firing.
*/
/datum/controller/subsystem/odyssey/proc/pick_odyssey()
var/list/singleton/scenario/all_scenarios = GET_SINGLETON_SUBTYPE_LIST(/singleton/scenario)
var/list/possible_scenarios = list()
for(var/singleton/scenario/S as anything in all_scenarios)
if((SSatlas.current_sector.name in S.sector_whitelist) || !length(S.sector_whitelist))
possible_scenarios[S] = S.weight
if(!length(possible_scenarios))
log_subsystem_odyssey("CRITICAL ERROR: No available odyssey for sector [SSatlas.current_sector.name]!")
log_and_message_admins(SPAN_DANGER(FONT_HUGE("CRITICAL ERROR: NO SITUATIONS ARE AVAILABLE FOR THIS SECTOR!")))
return FALSE
scenario = pickweight(possible_scenarios)
setup_scenario_variables()
var/list/possible_station_levels = SSmapping.levels_by_all_traits(list(ZTRAIT_STATION))
main_map = GLOB.map_sectors["[pick(possible_station_levels)]"]
// Now that we actually have an odyssey, the subsystem can fire!
can_fire = TRUE
return TRUE
/**
* This proc overrides the gamemode variables for the amount of actors and takes care of overriding any other variables the scenario might set on external things.
* Note that Storytellers spawn through a ghost role.
*/
/datum/controller/subsystem/odyssey/proc/setup_scenario_variables()
var/datum/game_mode/odyssey/ody_gamemode = GLOB.gamemode_cache["odyssey"]
if(scenario)
ody_gamemode.required_players = scenario.min_player_amount
ody_gamemode.required_enemies = scenario.min_actor_amount
//Setting the scenario_type variable for use here in UI info and chat notices.
if(!length(scenario.possible_scenario_types))
scenario.scenario_type = SCENARIO_TYPE_NONCANON
else
scenario.scenario_type = pick(scenario.possible_scenario_types)
site_landing_restricted = scenario.site_landing_restricted
/**
* This is the proc that should be used to set the odyssey away site.
*/
/datum/controller/subsystem/odyssey/proc/set_scenario_site(datum/map_template/ruin/away_site/site)
if(!istype(site))
return
scenario_site = site
/**
* Adds an actor to the subsystem actor list.
*/
/datum/controller/subsystem/odyssey/proc/add_actor(mob/living/carbon/human/H)
LAZYDISTINCTADD(actors, H)
/**
* Adds a storyteller to the subsystem storyteller list.
*/
/datum/controller/subsystem/odyssey/proc/add_storyteller(mob/abstract/ghost/storyteller/S)
LAZYDISTINCTADD(storytellers, S)
/**
* Removes an actor from the subsystem actor list.
*/
/datum/controller/subsystem/odyssey/proc/remove_actor(mob/living/carbon/human/H)
LAZYREMOVE(actors, H)
/**
* Removes a storyteller from the subsystem storyteller list.
*/
/datum/controller/subsystem/odyssey/proc/remove_storyteller(mob/abstract/ghost/storyteller/S)
LAZYREMOVE(storytellers, S)
/datum/controller/subsystem/odyssey/ui_state()
return GLOB.always_state
/datum/controller/subsystem/odyssey/ui_interact(mob/user, datum/tgui/ui)
. = ..()
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "OdysseyPanel", "Odyssey Panel")
ui.open()
/datum/controller/subsystem/odyssey/ui_data(mob/user)
var/list/data = list()
if(scenario)
data["scenario_name"] = SSodyssey.scenario.name
data["scenario_desc"] = SSodyssey.scenario.desc
data["scenario_canonicity"] = SSodyssey.scenario.scenario_type == SCENARIO_TYPE_CANON ? "Canon" : "Non-Canon"
data["is_storyteller"] = isstoryteller(user) || check_rights(R_ADMIN, FALSE, user)
if(length(scenario.roles))
data["scenario_roles"] = list()
for(var/role_singleton in scenario.roles)
var/singleton/role/scenario_role = GET_SINGLETON(role_singleton)
data["scenario_roles"] += list(
list(
"name" = scenario_role.name,
"desc" = scenario_role.desc,
"outfit" = "[scenario_role.outfit]",
"type" = scenario_role.type
)
)
return data
/datum/controller/subsystem/odyssey/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
var/mob/odyssey_user = ui.user
if(!ismob(odyssey_user))
return
var/is_admin = check_rights(R_ADMIN, FALSE, odyssey_user)
switch(action)
if("equip_outfit")
if(!ishuman(odyssey_user))
return
var/mob/living/carbon/human/player = odyssey_user
if(player.incapacitated())
return
if(!((player.z in SSodyssey.scenario_zlevels) || (isAdminLevel(player.z))))
to_chat(player, SPAN_WARNING("You can only equip outfits on the odyssey scenario z-level, or the actor prep area!"))
return
var/outfit_type = text2path(params["outfit_type"])
if(ispath(outfit_type, /obj/outfit))
player.delete_inventory(TRUE)
player.preEquipOutfit(outfit_type, FALSE)
player.equipOutfit(outfit_type, FALSE)
return TRUE
if("edit_scenario_name")
if(!isstoryteller(odyssey_user) && !is_admin)
return
var/new_scenario_name = tgui_input_text(usr, "Insert the new name for your scenario. Remember that this will be visible for anyone in the Stat Panel.", "Odyssey Panel", max_length = MAX_NAME_LEN)
if(!new_scenario_name)
return
log_and_message_admins("has changed the scenario name from [SSodyssey.scenario.name] to [new_scenario_name]", odyssey_user)
SSodyssey.scenario.name = new_scenario_name
return TRUE
if("edit_scenario_desc")
if(!isstoryteller(odyssey_user) && !is_admin)
return
var/new_scenario_desc = tgui_input_text(odyssey_user, "Insert the new description for your scenario. This is visible only in the Odyssey Panel.", "Odyssey Panel", max_length = MAX_MESSAGE_LEN)
if(!new_scenario_desc)
return
log_and_message_admins("has changed the scenario description", odyssey_user)
SSodyssey.scenario.desc = new_scenario_desc
return TRUE
if("edit_role")
if(!isstoryteller(odyssey_user) && !is_admin)
return
var/role_path = text2path(params["role_type"])
if(!role_path || !ispath(role_path, /singleton/role))
to_chat(odyssey_user, SPAN_WARNING("Invalid or inexisting role!"))
return
var/singleton/role/role_to_edit = GET_SINGLETON(role_path)
var/editing_name = params["new_name"]
var/editing_desc = params["new_desc"]
var/editing_outfit = params["edit_outfit"]
if(editing_name)
var/new_name = tgui_input_text(odyssey_user, "Insert the new name for this role.", "Odyssey Panel", max_length = MAX_NAME_LEN)
if(new_name)
log_and_message_admins("has changed the [role_to_edit.name] role's name to [new_name]", odyssey_user)
role_to_edit.name = new_name
return TRUE
if(editing_desc)
var/new_desc = tgui_input_text(odyssey_user, "Insert the new description for this role.", "Odyssey Panel", max_length = MAX_MESSAGE_LEN)
if(new_desc)
log_and_message_admins("has changed the [role_to_edit.name] role's description", odyssey_user)
role_to_edit.desc = new_desc
return TRUE
if(editing_outfit)
var/chosen_outfit = tgui_input_list(odyssey_user, "Select the new outfit for this role.", "Odyssey Panel", GLOB.outfit_cache)
if(chosen_outfit)
var/obj/outfit/new_outfit = GLOB.outfit_cache[chosen_outfit]
if(new_outfit)
log_and_message_admins("has changed the [role_to_edit.name] role's outfit from [role_to_edit.outfit] to [new_outfit]", odyssey_user)
role_to_edit.outfit = new_outfit.type
return TRUE