mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-31 00:49:06 +01:00
Adds the Odyssey gamemode. (#18972)
https://forums.aurorastation.org/topic/20198-mission-briefing-auroras-gamemode-revolution To-do: - [x] Finish storyteller verbs. - [x] Storyteller landmarks. - [x] Proper storyteller spawning. Right now the gamemode system is happy with just picking one storyteller and no actors. - [x] Antagonist whitelists code. - [x] Adding the Storyteller whitelist. - [x] Mission map loading code. - [x] Map in a bunch of missions. - [ ] Storyteller adminhelps. --------- Co-authored-by: Matt Atlas <liermattia@gmail.com> Co-authored-by: DreamySkrell <>
This commit is contained in:
co-authored by
Matt Atlas
DreamySkrell <>
parent
e1f5de6298
commit
1da20ad33f
@@ -197,7 +197,7 @@ SUBSYSTEM_DEF(ghostroles)
|
||||
if("jump_to")
|
||||
var/spawner_id = params["spawner_id"]
|
||||
var/datum/ghostspawner/human/spawner = spawners[spawner_id]
|
||||
var/mob/abstract/observer/observer = usr
|
||||
var/mob/abstract/ghost/observer/observer = usr
|
||||
if(spawner && istype(observer) && spawner.can_jump_to(observer))
|
||||
var/atom/turf = spawner.select_spawnlocation(FALSE)
|
||||
if(isturf(turf))
|
||||
@@ -208,7 +208,7 @@ SUBSYSTEM_DEF(ghostroles)
|
||||
var/spawner_id = params["spawner_id"]
|
||||
var/spawned_mob_name = params["spawned_mob_name"]
|
||||
var/datum/ghostspawner/human/spawner = spawners[spawner_id]
|
||||
var/mob/abstract/observer/observer = usr
|
||||
var/mob/abstract/ghost/observer/observer = usr
|
||||
if(istype(observer) && spawner.can_jump_to(observer) && spawner && LAZYLEN(spawner.spawned_mobs))
|
||||
for(var/datum/weakref/mob_ref in spawner.spawned_mobs)
|
||||
var/mob/spawned_mob = mob_ref.resolve()
|
||||
|
||||
@@ -37,6 +37,11 @@ SUBSYSTEM_DEF(misc_late)
|
||||
if (GLOB.config.use_forumuser_api)
|
||||
update_admins_from_api(TRUE)
|
||||
|
||||
// Load outfits here so that the verb isn't laggy as balls.
|
||||
for(var/outfit_type in subtypesof(/obj/outfit))
|
||||
var/obj/outfit/new_outfit = new outfit_type()
|
||||
GLOB.outfit_cache[new_outfit.name] = new_outfit
|
||||
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/proc/sorted_add_area(area/A)
|
||||
|
||||
@@ -181,7 +181,7 @@ SUBSYSTEM_DEF(pai)
|
||||
for(var/datum/paiCandidate/c in SSpai.pai_candidates)
|
||||
if(c.ready)
|
||||
var/found = 0
|
||||
for(var/mob/abstract/observer/o in GLOB.player_list)
|
||||
for(var/mob/abstract/ghost/observer/o in GLOB.player_list)
|
||||
if(o.key == c.key && o.MayRespawn())
|
||||
found = 1
|
||||
if(found)
|
||||
@@ -293,7 +293,7 @@ SUBSYSTEM_DEF(pai)
|
||||
|
||||
/datum/controller/subsystem/pai/proc/requestRecruits(mob/user)
|
||||
inquirer = user
|
||||
for(var/mob/abstract/observer/O in GLOB.player_list)
|
||||
for(var/mob/abstract/ghost/observer/O in GLOB.player_list)
|
||||
if(!O.MayRespawn())
|
||||
continue
|
||||
if(jobban_isbanned(O, "pAI"))
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
SUBSYSTEM_DEF(odyssey)
|
||||
name = "Odyssey"
|
||||
init_order = INIT_ORDER_ODYSSEY
|
||||
runlevels = RUNLEVELS_PLAYING
|
||||
|
||||
/// 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()
|
||||
// Now that we actually have an odyssey, the subsystem can fire!
|
||||
var/list/possible_station_levels = SSmapping.levels_by_all_traits(list(ZTRAIT_STATION))
|
||||
main_map = GLOB.map_sectors["[pick(possible_station_levels)]"]
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ SUBSYSTEM_DEF(records)
|
||||
return GLOB.always_state
|
||||
|
||||
/datum/controller/subsystem/records/ui_status(mob/user, datum/ui_state/state)
|
||||
return (isnewplayer(user) || isobserver(user) || issilicon(user)) ? UI_INTERACTIVE : UI_CLOSE
|
||||
return (isnewplayer(user) || isghost(user) || issilicon(user)) ? UI_INTERACTIVE : UI_CLOSE
|
||||
|
||||
/datum/controller/subsystem/records/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
@@ -211,7 +211,7 @@ SUBSYSTEM_DEF(records)
|
||||
return
|
||||
|
||||
if(action == "follow")
|
||||
var/mob/abstract/observer/O = usr
|
||||
var/mob/abstract/ghost/O = usr
|
||||
if(istype(O))
|
||||
for(var/mob/living/M in GLOB.human_mob_list)
|
||||
if(istype(M) && M.real_name == params["name"])
|
||||
@@ -222,7 +222,7 @@ SUBSYSTEM_DEF(records)
|
||||
/datum/controller/subsystem/records/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["manifest"] = SSrecords.get_manifest_list()
|
||||
data["allow_follow"] = isobserver(user)
|
||||
data["allow_follow"] = isghost(user)
|
||||
return data
|
||||
|
||||
/datum/controller/subsystem/records/proc/open_manifest_tgui(mob/user, datum/tgui/ui)
|
||||
|
||||
@@ -41,6 +41,10 @@ SUBSYSTEM_DEF(statpanels)
|
||||
if(eta_status)
|
||||
global_data += eta_status
|
||||
|
||||
if(SSodyssey && SSodyssey.scenario)
|
||||
global_data += "Odyssey: [SSodyssey.scenario.name]"
|
||||
global_data += "Storytellers: [length(SSodyssey.storytellers)]"
|
||||
|
||||
src.currentrun = GLOB.clients.Copy()
|
||||
mc_data = null
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ var/datum/controller/subsystem/ticker/SSticker
|
||||
|
||||
var/list/roundstart_callbacks
|
||||
|
||||
/// Used to prevent players from readying or unreadying, such as for Odyssey's setup.
|
||||
var/prevent_unready = FALSE
|
||||
|
||||
// Pre-game ready menu handling
|
||||
var/total_players = 0
|
||||
var/total_players_ready = 0
|
||||
@@ -262,8 +265,8 @@ var/datum/controller/subsystem/ticker/SSticker
|
||||
else
|
||||
to_chat(Player, SPAN_NOTICE(SPAN_BOLD("You missed the crew transfer after the events on [station_name()] as [Player.real_name].")))
|
||||
else
|
||||
if(istype(Player,/mob/abstract/observer))
|
||||
var/mob/abstract/observer/O = Player
|
||||
if(isobserver(Player))
|
||||
var/mob/abstract/ghost/observer/O = Player
|
||||
if(!O.started_as_observer)
|
||||
to_chat(Player, SPAN_WARNING(SPAN_BOLD("You did not survive the events on [station_name()]...")))
|
||||
else
|
||||
@@ -533,6 +536,15 @@ var/datum/controller/subsystem/ticker/SSticker
|
||||
to_world("<span class='danger'>Serious error in mode setup!</span> Reverting to pre-game lobby.")
|
||||
return SETUP_REVOTE
|
||||
|
||||
// This proc must return TRUE on success.
|
||||
if(!mode.pre_game_setup())
|
||||
current_state = GAME_STATE_PREGAME
|
||||
to_world("<span class='danger'>Round start pre-game setup failed! Reverting to pre-game lobby.")
|
||||
prevent_unready = FALSE
|
||||
return SETUP_REVOTE
|
||||
|
||||
prevent_unready = FALSE
|
||||
|
||||
SSjobs.ResetOccupations()
|
||||
src.mode.create_antagonists()
|
||||
src.mode.pre_setup()
|
||||
|
||||
Reference in New Issue
Block a user