Files
Aurora.3/code/controllers/subsystems/ticker.dm
Lohikar 61b5203d24 Runtime Map Loading (#3597)
changes:

Maps are no longer compiled in, instead loaded directly from the DMMs at runtime.
Z level defines have been moved from the config to map datums.
Unit tests now use typecaches.
DMMS now actually works.
DMMS has been updated slightly.
DMMS is now capable of loading simple lists of non-text types.
DMMS is now faster when loading many types without mapped in attributes and when loading area instances.
Asteroid generation is now defined on the map datum instead of being hard-coded in SSasteroid.
Holodeck presets are now defined on the map datum.
Atmos machinery now uses Initialize().
2017-10-18 23:07:34 +03:00

582 lines
19 KiB
Plaintext

#define LOBBY_TIME 180
var/datum/controller/subsystem/ticker/SSticker
/datum/controller/subsystem/ticker
// -- Subsystem stuff --
name = "Ticker"
priority = SS_PRIORITY_TICKER
flags = SS_NO_TICK_CHECK | SS_FIRE_IN_LOBBY
init_order = SS_INIT_LOBBY
wait = 1 SECOND
// -- Gameticker --
var/const/restart_timeout = 600
var/current_state = GAME_STATE_PREGAME
var/hide_mode = 0
var/datum/game_mode/mode = null
var/post_game = 0
var/login_music // music played in pregame lobby
var/list/datum/mind/minds = list()//The people in the game. Used for objective tracking.
var/Bible_icon_state // icon_state the chaplain has chosen for his bible
var/Bible_item_state // item_state the chaplain has chosen for his bible
var/Bible_name // name of the bible
var/Bible_deity_name
var/random_players = 0 // if set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders
var/pregame_timeleft = 0
var/delay_end = 0 //if set to nonzero, the round will not restart on it's own
var/triai = 0 //Global holder for Triumvirate
var/tipped = 0 //Did we broadcast the tip of the day yet?
var/selected_tip // What will be the tip of the day?
var/round_end_announced = 0 // Spam Prevention. Announce round end only once.
//station_explosion used to be a variable for every mob's hud. Which was a waste!
//Now we have a general cinematic centrally held within the gameticker....far more efficient!
var/obj/screen/cinematic = null
var/list/possible_lobby_tracks = list(
'sound/music/space.ogg',
'sound/music/traitor.ogg',
'sound/music/title2.ogg',
'sound/music/clouds.s3m',
'sound/music/space_oddity.ogg'
)
var/lobby_ready = FALSE
var/is_revote = FALSE
var/list/roundstart_callbacks
/datum/controller/subsystem/ticker/New()
NEW_SS_GLOBAL(SSticker)
/datum/controller/subsystem/ticker/Initialize(timeofday)
pregame()
/datum/controller/subsystem/ticker/stat_entry()
var/state = ""
switch (current_state)
if (GAME_STATE_PREGAME)
state = "PRE"
if (GAME_STATE_SETTING_UP)
state = "SETUP"
if (GAME_STATE_PLAYING)
state = "PLAY"
if (GAME_STATE_FINISHED)
state = "FIN"
else
state = "UNK"
..("State: [state]")
/datum/controller/subsystem/ticker/Recover()
// Copy stuff over so we don't lose any state.
current_state = SSticker.current_state
hide_mode = SSticker.hide_mode
mode = SSticker.mode
post_game = SSticker.post_game
login_music = SSticker.login_music
minds = SSticker.minds
Bible_icon_state = SSticker.Bible_icon_state
Bible_item_state = SSticker.Bible_item_state
Bible_deity_name = SSticker.Bible_deity_name
random_players = SSticker.random_players
pregame_timeleft = SSticker.pregame_timeleft
delay_end = SSticker.delay_end
triai = SSticker.triai
tipped = SSticker.tipped
selected_tip = SSticker.selected_tip
round_end_announced = SSticker.round_end_announced
cinematic = SSticker.cinematic
/datum/controller/subsystem/ticker/fire()
if (!lobby_ready)
lobby_ready = TRUE
return
switch (current_state)
if (GAME_STATE_PREGAME, GAME_STATE_SETTING_UP)
pregame_tick()
if (GAME_STATE_PLAYING)
game_tick()
/datum/controller/subsystem/ticker/proc/pregame_tick()
if (round_progressing)
pregame_timeleft--
if (current_state == GAME_STATE_PREGAME && pregame_timeleft == config.vote_autogamemode_timeleft)
if (!SSvote.time_remaining)
SSvote.autogamemode()
pregame_timeleft--
return
if (pregame_timeleft <= 10 && !tipped)
send_tip_of_the_round()
tipped = TRUE
if (pregame_timeleft <= 0 || current_state == GAME_STATE_SETTING_UP)
current_state = GAME_STATE_SETTING_UP
wait = 2 SECONDS
if (!setup())
// Something fucked up.
wait = 1 SECOND
is_revote = TRUE
pregame()
/datum/controller/subsystem/ticker/proc/game_tick()
if(current_state != GAME_STATE_PLAYING)
return 0
mode.process()
var/game_finished = 0
var/mode_finished = 0
if (config.continous_rounds)
game_finished = (emergency_shuttle.returned() || mode.station_was_nuked)
mode_finished = (!post_game && mode.check_finished())
else
game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || universe_has_ended
mode_finished = game_finished
if(!mode.explosion_in_progress && game_finished && (mode_finished || post_game))
current_state = GAME_STATE_FINISHED
declare_completion()
spawn(50)
callHook("roundend")
if (universe_has_ended)
if(mode.station_was_nuked)
feedback_set_details("end_proper","nuke")
else
feedback_set_details("end_proper","universe destroyed")
if(!delay_end)
world << "<span class='notice'><b>Rebooting due to destruction of station in [restart_timeout/10] seconds</b></span>"
else
feedback_set_details("end_proper","proper completion")
if(!delay_end)
world << "<span class='notice'><b>Restarting in [restart_timeout/10] seconds</b></span>"
if(!delay_end)
sleep(restart_timeout)
if(!delay_end)
world.Reboot()
else
world << "<span class='notice'><b>An admin has delayed the round end</b></span>"
else
world << "<span class='notice'><b>An admin has delayed the round end</b></span>"
else if (mode_finished)
post_game = 1
mode.cleanup()
//call a transfer shuttle vote
spawn(50)
if(!round_end_announced) // Spam Prevention. Now it should announce only once.
world << "<span class='danger'>The round has ended!</span>"
round_end_announced = 1
SSvote.autotransfer()
return 1
/datum/controller/subsystem/ticker/proc/declare_completion()
set waitfor = FALSE
world << "<br><br><br><H1>A round of [mode.name] has ended!</H1>"
for(var/mob/Player in player_list)
if(Player.mind && !isnewplayer(Player))
if(Player.stat != DEAD)
var/turf/playerTurf = get_turf(Player)
if(emergency_shuttle.departed && emergency_shuttle.evac)
if(isNotAdminLevel(playerTurf.z))
Player << "<font color='blue'><b>You managed to survive, but were marooned on [station_name()] as [Player.real_name]...</b></font>"
else
Player << "<font color='green'><b>You managed to survive the events on [station_name()] as [Player.real_name].</b></font>"
else if(isAdminLevel(playerTurf.z))
Player << "<font color='green'><b>You successfully underwent crew transfer after events on [station_name()] as [Player.real_name].</b></font>"
else if(issilicon(Player))
Player << "<font color='green'><b>You remain operational after the events on [station_name()] as [Player.real_name].</b></font>"
else
Player << "<font color='blue'><b>You missed the crew transfer after the events on [station_name()] as [Player.real_name].</b></font>"
else
if(istype(Player,/mob/dead/observer))
var/mob/dead/observer/O = Player
if(!O.started_as_observer)
Player << "<font color='red'><b>You did not survive the events on [station_name()]...</b></font>"
else
Player << "<font color='red'><b>You did not survive the events on [station_name()]...</b></font>"
world << "<br>"
for (var/mob/living/silicon/ai/aiPlayer in mob_list)
if (aiPlayer.stat != 2)
world << "<b>[aiPlayer.name] (Played by: [aiPlayer.key])'s laws at the end of the round were:</b>"
else
world << "<b>[aiPlayer.name] (Played by: [aiPlayer.key])'s laws when it was deactivated were:</b>"
aiPlayer.show_laws(1)
if (aiPlayer.connected_robots.len)
var/robolist = "<b>The AI's loyal minions were:</b> "
for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
robolist += "[robo.name][robo.stat?" (Deactivated) (Played by: [robo.key]), ":" (Played by: [robo.key]), "]"
world << "[robolist]"
var/dronecount = 0
for (var/mob/living/silicon/robot/robo in mob_list)
if(istype(robo,/mob/living/silicon/robot/drone))
dronecount++
continue
if (!robo.connected_ai)
if (robo.stat != 2)
world << "<b>[robo.name] (Played by: [robo.key]) survived as an AI-less borg! Its laws were:</b>"
else
world << "<b>[robo.name] (Played by: [robo.key]) was unable to survive the rigors of being a cyborg without an AI. Its laws were:</b>"
if(robo) //How the hell do we lose robo between here and the world messages directly above this?
robo.laws.show_laws(world)
if(dronecount)
world << "<b>There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round.</b>"
mode.declare_completion()//To declare normal completion.
//Ask the event manager to print round end information
SSevents.RoundEnd()
//Print a list of antagonists to the server log
var/list/total_antagonists = list()
//Look into all mobs in world, dead or alive
for(var/datum/mind/Mind in minds)
var/temprole = Mind.special_role
if(temprole) //if they are an antagonist of some sort.
if(temprole in total_antagonists) //If the role exists already, add the name to it
total_antagonists[temprole] += ", [Mind.name]([Mind.key])"
else
total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob
total_antagonists[temprole] += ": [Mind.name]([Mind.key])"
//Now print them all into the log!
log_game("Antagonists at round end were...")
for(var/i in total_antagonists)
log_game("[i]s[total_antagonists[i]].")
SSfeedback.print_round_end_message()
return 1
/datum/controller/subsystem/ticker/proc/send_tip_of_the_round()
var/m
if(selected_tip)
m = selected_tip
else
var/list/randomtips = file2list("config/tips.txt")
if(randomtips.len)
m = pick(randomtips)
if(m)
world << "<font color='purple'><b>Tip of the round: \
</b>[html_encode(m)]</font>"
/datum/controller/subsystem/ticker/proc/pregame()
set waitfor = FALSE
sleep(1) // Sleep so the MC has a chance to update its init time.
if (!login_music)
login_music = pick(possible_lobby_tracks)
if (is_revote)
pregame_timeleft = LOBBY_TIME
log_debug("SSticker: lobby reset due to game setup failure, using pregame time [LOBBY_TIME]s.")
else
var/mc_init_time = round(Master.initialization_time_taken, 1)
var/dynamic_time = LOBBY_TIME - mc_init_time
if (dynamic_time <= config.vote_autogamemode_timeleft)
pregame_timeleft = config.vote_autogamemode_timeleft + 10
log_debug("SSticker: dynamic set pregame time [dynamic_time]s was less than or equal to configured autogamemode vote time [config.vote_autogamemode_timeleft]s, clamping.")
else
pregame_timeleft = dynamic_time
log_debug("SSticker: dynamic set pregame time [dynamic_time]s was greater than configured autogamemode time, not clamping.")
world << "<B><FONT color='blue'>Welcome to the pre-game lobby!</FONT></B>"
world << "Please, setup your character and select ready. Game will start in [pregame_timeleft] seconds."
/datum/controller/subsystem/ticker/proc/setup()
//Create and announce mode
if(master_mode == ROUNDTYPE_STR_SECRET)
src.hide_mode = ROUNDTYPE_SECRET
else if (master_mode == ROUNDTYPE_STR_MIXED_SECRET)
src.hide_mode = ROUNDTYPE_MIXED_SECRET
var/list/runnable_modes = config.get_runnable_modes(master_mode)
if(master_mode in list(ROUNDTYPE_STR_RANDOM, ROUNDTYPE_STR_SECRET, ROUNDTYPE_STR_MIXED_SECRET))
if(!runnable_modes.len)
current_state = GAME_STATE_PREGAME
world << "<B>Unable to choose playable game mode.</B> Reverting to pre-game lobby."
return 0
if(secret_force_mode != ROUNDTYPE_STR_SECRET && secret_force_mode != ROUNDTYPE_STR_MIXED_SECRET)
src.mode = config.pick_mode(secret_force_mode)
if(!src.mode)
var/list/weighted_modes = list()
var/list/probabilities = list()
if (master_mode == ROUNDTYPE_STR_SECRET)
probabilities = config.probabilities_secret
else if (master_mode == ROUNDTYPE_STR_MIXED_SECRET)
probabilities = config.probabilities_mixed_secret
else
// master_mode == ROUNDTYPE_STR_RANDOM
probabilities = config.probabilities_secret.Copy()
probabilities |= config.probabilities_mixed_secret
for(var/datum/game_mode/GM in runnable_modes)
weighted_modes[GM.config_tag] = probabilities[GM.config_tag]
src.mode = gamemode_cache[pickweight(weighted_modes)]
else
src.mode = config.pick_mode(master_mode)
if(!src.mode)
current_state = GAME_STATE_PREGAME
world << "<span class='danger'>Serious error in mode setup!</span> Reverting to pre-game lobby."
return 0
SSjobs.ResetOccupations()
src.mode.create_antagonists()
src.mode.pre_setup()
SSjobs.DivideOccupations() // Apparently important for new antagonist system to register specific job antags properly.
if(!src.mode.can_start())
world << "<B>Unable to start [mode.name].</B> Not enough players, [mode.required_players] players needed. Reverting to pre-game lobby."
current_state = GAME_STATE_PREGAME
mode.fail_setup()
mode = null
SSjobs.ResetOccupations()
return 0
var/starttime = REALTIMEOFDAY
if(hide_mode)
world << "<B>The current game mode is - [hide_mode == ROUNDTYPE_SECRET ? "Secret" : "Mixed Secret"]!</B>"
if(runnable_modes.len)
var/list/tmpmodes = new
for (var/datum/game_mode/M in runnable_modes)
tmpmodes+=M.name
tmpmodes = sortList(tmpmodes)
if(tmpmodes.len)
world << "<B>Possibilities:</B> [english_list(tmpmodes)]"
else
src.mode.announce()
setup_economy()
current_state = GAME_STATE_PLAYING
create_characters() //Create player characters and transfer them
collect_minds()
equip_characters()
data_core.manifest()
Master.RoundStart()
round_start_time = world.time
callHook("roundstart")
spawn(0)//Forking here so we dont have to wait for this to finish
mode.post_setup()
//Cleanup some stuff
for(var/obj/effect/landmark/start/S in landmarks_list)
//Deleting Startpoints but we need the ai point to AI-ize people later
if (S.name != "AI")
qdel(S)
world << "<FONT color='blue'><B>Enjoy the game!</B></FONT>"
world << sound('sound/AI/welcome.ogg') // Skie
//Holiday Round-start stuff ~Carn
Holiday_Game_Start()
log_debug("SSticker: Running [LAZYLEN(roundstart_callbacks)] round-start callbacks.")
run_callback_list(roundstart_callbacks)
roundstart_callbacks = null
log_debug("SSticker: Round-start setup took [(REALTIMEOFDAY - starttime)/10] seconds.")
return 1
/datum/controller/subsystem/ticker/proc/run_callback_list(list/callbacklist)
set waitfor = FALSE
if (!callbacklist)
return
for (var/thing in callbacklist)
var/datum/callback/callback = thing
callback.Invoke()
CHECK_TICK
/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(station_missed = 0, override = null, list/affected_levels = current_map.station_levels)
if (cinematic)
return //already a cinematic in progress!
//initialise our cinematic screen object
cinematic = new /obj/screen{
icon = 'icons/effects/station_explosion.dmi';
icon_state = "station_intact";
layer = 20;
mouse_opacity = 0;
screen_loc = "1,0"
}
var/obj/structure/bed/temp_buckle = new
//Incredibly hackish. It creates a bed within the gameticker (lol) to stop mobs running around
if(station_missed)
for(var/mob/living/M in living_mob_list)
M.buckled = temp_buckle //buckles the mob so it can't do anything
if(M.client)
M.client.screen += cinematic //show every client the cinematic
else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived"
for(var/mob/living/M in living_mob_list)
M.buckled = temp_buckle
if(M.client)
M.client.screen += cinematic
var/turf/Mloc = M.loc
if (!Mloc)
continue
if (!istype(Mloc))
Mloc = get_turf(M)
if (Mloc.z in affected_levels)
M.dust()
CHECK_TICK
//Now animate the cinematic
switch(station_missed)
if(1) //nuke was nearby but (mostly) missed
if( mode && !override )
override = mode.name
switch( override )
if("mercenary") //Nuke wasn't on station when it blew up
flick("intro_nuke",cinematic)
sleep(35)
world << sound('sound/effects/explosionfar.ogg')
flick("station_intact_fade_red",cinematic)
cinematic.icon_state = "summary_nukefail"
else
flick("intro_nuke",cinematic)
sleep(35)
world << sound('sound/effects/explosionfar.ogg')
//flick("end",cinematic)
if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation
sleep(50)
world << sound('sound/effects/explosionfar.ogg')
else //station was destroyed
if( mode && !override )
override = mode.name
switch( override )
if("mercenary") //Nuke Ops successfully bombed the station
flick("intro_nuke",cinematic)
sleep(35)
flick("station_explode_fade_red",cinematic)
world << sound('sound/effects/explosionfar.ogg')
cinematic.icon_state = "summary_nukewin"
if("AI malfunction") //Malf (screen,explosion,summary)
flick("intro_malf",cinematic)
sleep(76)
flick("station_explode_fade_red",cinematic)
world << sound('sound/effects/explosionfar.ogg')
cinematic.icon_state = "summary_malf"
if("blob") //Station nuked (nuke,explosion,summary)
flick("intro_nuke",cinematic)
sleep(35)
flick("station_explode_fade_red",cinematic)
world << sound('sound/effects/explosionfar.ogg')
cinematic.icon_state = "summary_selfdes"
else //Station nuked (nuke,explosion,summary)
flick("intro_nuke",cinematic)
sleep(35)
flick("station_explode_fade_red", cinematic)
world << sound('sound/effects/explosionfar.ogg')
cinematic.icon_state = "summary_selfdes"
//If its actually the end of the round, wait for it to end.
//Otherwise if its a verb it will continue on afterwards.
sleep(300)
if(cinematic)
QDEL_NULL(cinematic) //end the cinematic
if(temp_buckle)
QDEL_NULL(temp_buckle) //release everybody
// Round setup stuff.
/datum/controller/subsystem/ticker/proc/create_characters()
for(var/mob/new_player/player in player_list)
if(player && player.ready && player.mind)
if(player.mind.assigned_role=="AI")
player.close_spawn_windows()
player.AIize()
else if(!player.mind.assigned_role)
continue
else
player.create_character()
qdel(player)
CHECK_TICK
/datum/controller/subsystem/ticker/proc/collect_minds()
for(var/mob/living/player in player_list)
if(player.mind)
minds += player.mind
/datum/controller/subsystem/ticker/proc/equip_characters()
var/captainless = TRUE
for(var/mob/living/carbon/human/player in player_list)
if(player && player.mind && player.mind.assigned_role)
if(player.mind.assigned_role == "Captain")
captainless = FALSE
if(!player_is_antag(player.mind, only_offstation_roles = 1))
SSjobs.EquipRank(player, player.mind.assigned_role, 0)
UpdateFactionList(player)
equip_custom_items(player)
CHECK_TICK
if(captainless)
for(var/mob/M in player_list)
if(!istype(M,/mob/new_player))
M << "Captainship not forced on anyone."
// Registers a callback to run on round-start.
/datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/callback)
if (!istype(callback))
return
LAZYADD(roundstart_callbacks, callback)
#undef LOBBY_TIME