mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-24 16:13:20 +00:00
* TGUI Latejoin Menu! * [PR PR] Fixes up the TGUI Latejoin Menu! (#18044) * Makes Angel, Dragon and Robotic wings available in the preferences menu again (#18013) Makes Angel, Dragon and Robotic wings available in the prefs menu again * Automatic changelog for PR #18013 [ci skip] * [MIRROR] Petrified human statues now drop a (stone) brain on destruction [MDB IGNORE] (#18026) * Petrified human statues now drop a (stone) brain on destruction (#71816) ## About The Pull Request Destroying a petrified human statue will now drop their brain, albeit a little more statue-like.  Changed from original PR after talking w/ Fikou about it ## Why It's Good For The Game While the combo *does* step on the toes of Smite, the statue+rod combo is capable of fully removing you from the round without a way back very quickly. This keeps the strength of the combo while not making it entirely impossible to get back into the round. ## Changelog 🆑 balance: Destroying a petrified human statue will now drop their brain. /🆑 * Petrified human statues now drop a (stone) brain on destruction Co-authored-by: Zonespace <41448081+Zonespace27@users.noreply.github.com> * Automatic changelog for PR #18026 [ci skip] * [MIRROR] Chaplain armor beacon now uses radial + previews possible armor sets, plus some choice beacon code cleanup. [MDB IGNORE] (#18019) * Chaplain armor beacon now uses radial + previews possible armor sets, plus some choice beacon code cleanup. (#71674) ## About The Pull Request - The chaplain choice beacon now uses a radial to select the armor set, instead of a list, giving the user a preview of what each looks like.  - Lots of additional cleanup to choice beacon code in general. Less copy pasted code. - All beacons now speak from the beacon with their message, instead of some going by "headset message". Soul removed ## Why It's Good For The Game I always forgot when selecting my armor which looks like what, and choosing an ugly one is a pain since you only get one choice. This should help chaplains get the armor they actually want without needing to check the wiki. ## Changelog 🆑 Melbert qol: The chaplain's armament beacon now displays a radial instead of a text list, showing previews of what all the armor sets look like qol: (Almost) all choice beacons now use a pod to send their item, instead of just magicking it under your feet code: Cleaned up some choice beacon code. /🆑 Co-authored-by: Fikou <23585223+Fikou@ users.noreply.github.com> * Chaplain armor beacon now uses radial + previews possible armor sets, plus some choice beacon code cleanup. * update modular Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Fikou <23585223+Fikou@ users.noreply.github.com> Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> * Automatic changelog for PR #18019 [ci skip] * [MIRROR] Fixed an improper proc ref. [MDB IGNORE] (#18031) Fixed an improper proc ref. Co-authored-by: lizardqueenlexi <105025397+lizardqueenlexi@users.noreply.github.com> Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> * Automatic changelog for PR #18031 [ci skip] * Fiiix! * Fuck * [MIRROR] Windoors are vulnerable to a jaws of life [MDB IGNORE] (#18040) * Windoors are vulnerable to a jaws of life (#71733) ## About The Pull Request Windoors can be pried by a jaws of life now ## Why It's Good For The Game Windoors are already immune to any kind of deconstruction outside of bashing it, so making it immune to the jaws of life as well seems excessive ## Changelog 🆑 balance: jaws of life can pry windoors /🆑 Co-authored-by: Candycaneannihalator <candycane@ thisisnotarealaddr.com> * Windoors are vulnerable to a jaws of life Co-authored-by: texan-down-under <73374039+etherware-novice@users.noreply.github.com> Co-authored-by: Candycaneannihalator <candycane@ thisisnotarealaddr.com> * Automatic changelog for PR #18040 [ci skip] * Automatic changelog compile [ci skip] * [fix] fixes det revolver icons (#18049) * Delete guns.dmi * Add files via upload * Automatic changelog for PR #18049 [ci skip] * [MIRROR] Fixes fake links in Adminwho that led to "no_feedback_link" [MDB IGNORE] (#18027) * Fixes fake links in Adminwho that led to "no_feedback_link" (#71804) ## About The Pull Request Basically, sometimes, very rarely, it was possible for this to happen, when it shouldn't be happening. I was wondering for a little while what it was about, and then I finally downloaded my chat logs for a round and realized what was going on. It should no longer happen. ## Why It's Good For The Game Clickable links that lead nowhere (and thus don't do anything) aren't very good for the game. ## Changelog 🆑 GoldenAlpharex fix: There shouldn't be clickable names in Adminwho that don't do anything anymore. /🆑 * Fixes fake links in Adminwho that led to "no_feedback_link" Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> * Fix: Self-Actualization Device Doubling Damage (#18015) * Change adjustOrganLoss/BruteLoss/FireLoss to set * Reverts my naive soluton * Automatic changelog for PR #18027 [ci skip] * Automatic changelog for PR #18015 [ci skip] * Fix TGUI latejoin menu not closing properly, properly this time * Oh yea, flavourtext requirement exists. Not that we actually have it enabled upstream. * Revert "Merge remote-tracking branch 'upstream/upstream-merge-71883' into fixlatejoinmenu" This reverts commit 3279e73418f95637162a2923b0431e9e26b43403, reversing changes made to d46aab7b421fee94d49b57e863bfd8e0917fc43f. * Fucking stupid code merge shit * Cool newline bro * Dumb stupid commented code * Also this Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Co-authored-by: Zonespace <41448081+Zonespace27@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Fikou <23585223+Fikou@ users.noreply.github.com> Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> Co-authored-by: lizardqueenlexi <105025397+lizardqueenlexi@users.noreply.github.com> Co-authored-by: texan-down-under <73374039+etherware-novice@users.noreply.github.com> Co-authored-by: Candycaneannihalator <candycane@ thisisnotarealaddr.com> Co-authored-by: Changelogs <action@github.com> Co-authored-by: RatFromTheJungle <62520989+RatFromTheJungle@users.noreply.github.com> Co-authored-by: Dani Glore <fantasticdragons@gmail.com> Co-authored-by: distributivgesetz <distributivgesetz93@gmail.com> * fix Co-authored-by: Rimi Nosha <riminosha@gmail.com> Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com> Co-authored-by: Zonespace <41448081+Zonespace27@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Fikou <23585223+Fikou@ users.noreply.github.com> Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> Co-authored-by: lizardqueenlexi <105025397+lizardqueenlexi@users.noreply.github.com> Co-authored-by: texan-down-under <73374039+etherware-novice@users.noreply.github.com> Co-authored-by: Candycaneannihalator <candycane@ thisisnotarealaddr.com> Co-authored-by: Changelogs <action@github.com> Co-authored-by: RatFromTheJungle <62520989+RatFromTheJungle@users.noreply.github.com> Co-authored-by: Dani Glore <fantasticdragons@gmail.com> Co-authored-by: distributivgesetz <distributivgesetz93@gmail.com> Co-authored-by: tastyfish <crazychris32@gmail.com>
726 lines
28 KiB
Plaintext
726 lines
28 KiB
Plaintext
#define ROUND_START_MUSIC_LIST "strings/round_start_sounds.txt"
|
|
|
|
SUBSYSTEM_DEF(ticker)
|
|
name = "Ticker"
|
|
init_order = INIT_ORDER_TICKER
|
|
|
|
priority = FIRE_PRIORITY_TICKER
|
|
flags = SS_KEEP_TIMING
|
|
runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME
|
|
|
|
/// state of current round (used by process()) Use the defines GAME_STATE_* !
|
|
var/current_state = GAME_STATE_STARTUP
|
|
/// Boolean to track if round was ended by admin intervention or a "round-ending" event, like summoning Nar'Sie, a blob victory, the nuke going off, etc.
|
|
var/force_ending = FALSE
|
|
/// If TRUE, there is no lobby phase, the game starts immediately.
|
|
var/start_immediately = FALSE
|
|
/// Boolean to track and check if our subsystem setup is done.
|
|
var/setup_done = FALSE
|
|
|
|
var/datum/game_mode/mode = null
|
|
|
|
var/login_music //music played in pregame lobby
|
|
var/round_end_sound //music/jingle played when the world reboots
|
|
var/round_end_sound_sent = TRUE //If all clients have loaded it
|
|
|
|
var/list/datum/mind/minds = list() //The characters in the game. Used for objective tracking.
|
|
|
|
var/delay_end = FALSE //if set true, the round will not restart on it's own
|
|
var/admin_delay_notice = "" //a message to display to anyone who tries to restart the world after a delay
|
|
var/ready_for_reboot = FALSE //all roundend preparation done with, all that's left is reboot
|
|
|
|
var/tipped = FALSE //Did we broadcast the tip of the day yet?
|
|
var/selected_tip // What will be the tip of the day?
|
|
|
|
var/timeLeft //pregame timer
|
|
var/start_at
|
|
|
|
var/gametime_offset = 432000 //Deciseconds to add to world.time for station time.
|
|
var/station_time_rate_multiplier = 12 //factor of station time progressal vs real time.
|
|
|
|
/// Num of players, used for pregame stats on statpanel
|
|
var/totalPlayers = 0
|
|
/// Num of ready players, used for pregame stats on statpanel (only viewable by admins)
|
|
var/totalPlayersReady = 0
|
|
/// Num of ready admins, used for pregame stats on statpanel (only viewable by admins)
|
|
var/total_admins_ready = 0
|
|
|
|
var/queue_delay = 0
|
|
var/list/queued_players = list() //used for join queues when the server exceeds the hard population cap
|
|
|
|
var/news_report
|
|
|
|
|
|
var/roundend_check_paused = FALSE
|
|
|
|
var/round_start_time = 0
|
|
var/list/round_start_events
|
|
var/list/round_end_events
|
|
var/mode_result = "undefined"
|
|
var/end_state = "undefined"
|
|
|
|
/// People who have been commended and will receive a heart
|
|
var/list/hearts
|
|
|
|
/// Why an emergency shuttle was called
|
|
var/emergency_reason
|
|
|
|
var/real_round_start_time = 0 //SKYRAT EDIT ADDITION
|
|
|
|
var/discord_alerted = FALSE //SKYRAT EDIT - DISCORD PING SPAM PREVENTION
|
|
|
|
/datum/controller/subsystem/ticker/Initialize()
|
|
load_mentors() // SKYRAT EDIT ADDITION - MENTORS STOPPED LOADING AUTOMATICALLY DUE TO RECENT TG CHANGES
|
|
var/list/byond_sound_formats = list(
|
|
"mid" = TRUE,
|
|
"midi" = TRUE,
|
|
"mod" = TRUE,
|
|
"it" = TRUE,
|
|
"s3m" = TRUE,
|
|
"xm" = TRUE,
|
|
"oxm" = TRUE,
|
|
"wav" = TRUE,
|
|
"ogg" = TRUE,
|
|
"raw" = TRUE,
|
|
"wma" = TRUE,
|
|
"aiff" = TRUE,
|
|
)
|
|
|
|
var/list/provisional_title_music = flist("[global.config.directory]/title_music/sounds/")
|
|
var/list/music = list()
|
|
var/use_rare_music = prob(1)
|
|
|
|
for(var/S in provisional_title_music)
|
|
var/lower = lowertext(S)
|
|
var/list/L = splittext(lower,"+")
|
|
switch(L.len)
|
|
if(3) //rare+MAP+sound.ogg or MAP+rare.sound.ogg -- Rare Map-specific sounds
|
|
if(use_rare_music)
|
|
if(L[1] == "rare" && L[2] == SSmapping.config.map_name)
|
|
music += S
|
|
else if(L[2] == "rare" && L[1] == SSmapping.config.map_name)
|
|
music += S
|
|
if(2) //rare+sound.ogg or MAP+sound.ogg -- Rare sounds or Map-specific sounds
|
|
if((use_rare_music && L[1] == "rare") || (L[1] == SSmapping.config.map_name))
|
|
music += S
|
|
if(1) //sound.ogg -- common sound
|
|
if(L[1] == "exclude")
|
|
continue
|
|
music += S
|
|
|
|
var/old_login_music = trim(file2text("data/last_round_lobby_music.txt"))
|
|
if(music.len > 1)
|
|
music -= old_login_music
|
|
|
|
for(var/S in music)
|
|
var/list/L = splittext(S,".")
|
|
if(L.len >= 2)
|
|
var/ext = lowertext(L[L.len]) //pick the real extension, no 'honk.ogg.exe' nonsense here
|
|
if(byond_sound_formats[ext])
|
|
continue
|
|
music -= S
|
|
|
|
if(!length(music))
|
|
music = world.file2list(ROUND_START_MUSIC_LIST, "\n")
|
|
login_music = pick(music)
|
|
else
|
|
login_music = "[global.config.directory]/title_music/sounds/[pick(music)]"
|
|
|
|
|
|
if(!GLOB.syndicate_code_phrase)
|
|
GLOB.syndicate_code_phrase = generate_code_phrase(return_list=TRUE)
|
|
|
|
var/codewords = jointext(GLOB.syndicate_code_phrase, "|")
|
|
var/regex/codeword_match = new("([codewords])", "ig")
|
|
|
|
GLOB.syndicate_code_phrase_regex = codeword_match
|
|
|
|
if(!GLOB.syndicate_code_response)
|
|
GLOB.syndicate_code_response = generate_code_phrase(return_list=TRUE)
|
|
|
|
var/codewords = jointext(GLOB.syndicate_code_response, "|")
|
|
var/regex/codeword_match = new("([codewords])", "ig")
|
|
|
|
GLOB.syndicate_code_response_regex = codeword_match
|
|
|
|
start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
|
|
if(CONFIG_GET(flag/randomize_shift_time))
|
|
gametime_offset = rand(0, 23) HOURS
|
|
else if(CONFIG_GET(flag/shift_time_realtime))
|
|
gametime_offset = world.timeofday
|
|
return SS_INIT_SUCCESS
|
|
|
|
/datum/controller/subsystem/ticker/fire()
|
|
switch(current_state)
|
|
if(GAME_STATE_STARTUP)
|
|
if(Master.initializations_finished_with_no_players_logged_in)
|
|
start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
|
|
for(var/client/C in GLOB.clients)
|
|
window_flash(C, ignorepref = TRUE) //let them know lobby has opened up.
|
|
to_chat(world, span_notice("<b>Welcome to [station_name()]!</b>"))
|
|
//SKYRAT EDIT START - DISCORD SPAM PREVENTION
|
|
if(!discord_alerted)
|
|
discord_alerted = TRUE
|
|
send2chat("<@&[CONFIG_GET(string/game_alert_role_id)]> New round starting on [SSmapping.config.map_name], [CONFIG_GET(string/servername)]! \nIf you wish to be pinged for game related stuff, go to <#[CONFIG_GET(string/role_assign_channel_id)]> and assign yourself the roles.", CONFIG_GET(string/chat_announce_new_game)) // Skyrat EDIT -- role pingcurrent_state = GAME_STATE_PREGAME
|
|
current_state = GAME_STATE_PREGAME
|
|
SStitle.change_title_screen() //SKYRAT EDIT ADDITION - Title screen
|
|
addtimer(CALLBACK(SStitle, TYPE_PROC_REF(/datum/controller/subsystem/title, change_title_screen)), 1 SECONDS) //SKYRAT EDIT ADDITION - Title screen
|
|
//Everyone who wants to be an observer is now spawned
|
|
SEND_SIGNAL(src, COMSIG_TICKER_ENTER_PREGAME)
|
|
fire()
|
|
if(GAME_STATE_PREGAME)
|
|
//lobby stats for statpanels
|
|
if(isnull(timeLeft))
|
|
timeLeft = max(0,start_at - world.time)
|
|
totalPlayers = LAZYLEN(GLOB.new_player_list)
|
|
totalPlayersReady = 0
|
|
total_admins_ready = 0
|
|
for(var/mob/dead/new_player/player as anything in GLOB.new_player_list)
|
|
if(player.ready == PLAYER_READY_TO_PLAY)
|
|
++totalPlayersReady
|
|
if(player.client?.holder)
|
|
++total_admins_ready
|
|
|
|
if(start_immediately)
|
|
timeLeft = 0
|
|
|
|
//countdown
|
|
if(timeLeft < 0)
|
|
return
|
|
timeLeft -= wait
|
|
|
|
if(timeLeft <= 300 && !tipped)
|
|
send_tip_of_the_round(world, selected_tip)
|
|
tipped = TRUE
|
|
|
|
if(timeLeft <= 0)
|
|
SEND_SIGNAL(src, COMSIG_TICKER_ENTER_SETTING_UP)
|
|
current_state = GAME_STATE_SETTING_UP
|
|
Master.SetRunLevel(RUNLEVEL_SETUP)
|
|
SSevents.reschedule() // SKYRAT EDIT ADDITION
|
|
if(start_immediately)
|
|
fire()
|
|
|
|
if(GAME_STATE_SETTING_UP)
|
|
if(!setup())
|
|
//setup failed
|
|
current_state = GAME_STATE_STARTUP
|
|
start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
|
|
timeLeft = null
|
|
Master.SetRunLevel(RUNLEVEL_LOBBY)
|
|
SEND_SIGNAL(src, COMSIG_TICKER_ERROR_SETTING_UP)
|
|
|
|
if(GAME_STATE_PLAYING)
|
|
mode.process(wait * 0.1)
|
|
check_queue()
|
|
|
|
if(!roundend_check_paused && mode.check_finished(force_ending) || force_ending)
|
|
current_state = GAME_STATE_FINISHED
|
|
toggle_ooc(TRUE) // Turn it on
|
|
toggle_dooc(TRUE)
|
|
declare_completion(force_ending)
|
|
check_maprotate()
|
|
Master.SetRunLevel(RUNLEVEL_POSTGAME)
|
|
|
|
|
|
/datum/controller/subsystem/ticker/proc/setup()
|
|
to_chat(world, span_boldannounce("Starting game..."))
|
|
var/init_start = world.timeofday
|
|
|
|
mode = new /datum/game_mode/dynamic
|
|
|
|
CHECK_TICK
|
|
//Configure mode and assign player to special mode stuff
|
|
var/can_continue = 0
|
|
can_continue = src.mode.pre_setup() //Choose antagonists
|
|
CHECK_TICK
|
|
can_continue = can_continue && SSjob.DivideOccupations() //Distribute jobs
|
|
CHECK_TICK
|
|
|
|
if(!GLOB.Debug2)
|
|
if(!can_continue)
|
|
log_game("Game failed pre_setup")
|
|
QDEL_NULL(mode)
|
|
to_chat(world, "<B>Error setting up game.</B> Reverting to pre-game lobby.")
|
|
SSjob.ResetOccupations()
|
|
return FALSE
|
|
else
|
|
message_admins(span_notice("DEBUG: Bypassing prestart checks..."))
|
|
|
|
CHECK_TICK
|
|
|
|
// There may be various config settings that have been set or modified by this point.
|
|
// This is the point of no return before spawning in new players, let's run over the
|
|
// job trim singletons and update them based on any config settings.
|
|
SSid_access.refresh_job_trim_singletons()
|
|
|
|
CHECK_TICK
|
|
|
|
if(!CONFIG_GET(flag/ooc_during_round))
|
|
toggle_ooc(FALSE) // Turn it off
|
|
|
|
CHECK_TICK
|
|
GLOB.start_landmarks_list = shuffle(GLOB.start_landmarks_list) //Shuffle the order of spawn points so they dont always predictably spawn bottom-up and right-to-left
|
|
create_characters() //Create player characters
|
|
collect_minds()
|
|
equip_characters()
|
|
|
|
GLOB.data_core.manifest()
|
|
|
|
transfer_characters() //transfer keys to the new mobs
|
|
|
|
for(var/I in round_start_events)
|
|
var/datum/callback/cb = I
|
|
cb.InvokeAsync()
|
|
LAZYCLEARLIST(round_start_events)
|
|
|
|
round_start_time = world.time //otherwise round_start_time would be 0 for the signals
|
|
SEND_SIGNAL(src, COMSIG_TICKER_ROUND_STARTING, world.time)
|
|
real_round_start_time = world.timeofday //SKYRAT EDIT ADDITION
|
|
|
|
log_world("Game start took [(world.timeofday - init_start)/10]s")
|
|
INVOKE_ASYNC(SSdbcore, TYPE_PROC_REF(/datum/controller/subsystem/dbcore,SetRoundStart))
|
|
|
|
to_chat(world, span_notice("<B>Welcome to [station_name()], enjoy your stay!</B>"))
|
|
alert_sound_to_playing(sound(SSstation.announcer.get_rand_welcome_sound())) //SKYRAT EDIT CHANGE
|
|
|
|
current_state = GAME_STATE_PLAYING
|
|
Master.SetRunLevel(RUNLEVEL_GAME)
|
|
|
|
if(length(GLOB.holidays))
|
|
to_chat(world, span_notice("and..."))
|
|
for(var/holidayname in GLOB.holidays)
|
|
var/datum/holiday/holiday = GLOB.holidays[holidayname]
|
|
to_chat(world, "<h4>[holiday.greet()]</h4>")
|
|
|
|
PostSetup()
|
|
|
|
return TRUE
|
|
|
|
/datum/controller/subsystem/ticker/proc/PostSetup()
|
|
set waitfor = FALSE
|
|
mode.post_setup()
|
|
GLOB.start_state = new /datum/station_state()
|
|
GLOB.start_state.count()
|
|
|
|
var/list/adm = get_admin_counts()
|
|
var/list/allmins = adm["present"]
|
|
send2adminchat("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]" : ""] has started[allmins.len ? ".":" with no active admins online!"]")
|
|
setup_done = TRUE
|
|
|
|
for(var/i in GLOB.start_landmarks_list)
|
|
var/obj/effect/landmark/start/S = i
|
|
if(istype(S)) //we can not runtime here. not in this important of a proc.
|
|
S.after_round_start()
|
|
else
|
|
stack_trace("[S] [S.type] found in start landmarks list, which isn't a start landmark!")
|
|
|
|
// handle persistence stuff that requires ckeys, in this case hardcore mode and temporal scarring
|
|
for(var/i in GLOB.player_list)
|
|
if(!ishuman(i))
|
|
continue
|
|
var/mob/living/carbon/human/iter_human = i
|
|
|
|
iter_human.increment_scar_slot()
|
|
iter_human.load_persistent_scars()
|
|
|
|
if(!iter_human.hardcore_survival_score)
|
|
continue
|
|
if(iter_human.mind?.special_role)
|
|
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!"))
|
|
|
|
//These callbacks will fire after roundstart key transfer
|
|
/datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/cb)
|
|
if(!HasRoundStarted())
|
|
LAZYADD(round_start_events, cb)
|
|
else
|
|
cb.InvokeAsync()
|
|
|
|
//These callbacks will fire before roundend report
|
|
/datum/controller/subsystem/ticker/proc/OnRoundend(datum/callback/cb)
|
|
if(current_state >= GAME_STATE_FINISHED)
|
|
cb.InvokeAsync()
|
|
else
|
|
LAZYADD(round_end_events, cb)
|
|
|
|
/datum/controller/subsystem/ticker/proc/station_explosion_detonation(atom/bomb)
|
|
if(bomb) //BOOM
|
|
qdel(bomb)
|
|
|
|
/datum/controller/subsystem/ticker/proc/create_characters()
|
|
for(var/i in GLOB.new_player_list)
|
|
var/mob/dead/new_player/player = i
|
|
if(player.ready == PLAYER_READY_TO_PLAY && player.mind)
|
|
GLOB.joined_player_list += player.ckey
|
|
var/atom/destination = player.mind.assigned_role.get_roundstart_spawn_point()
|
|
if(!destination) // Failed to fetch a proper roundstart location, won't be going anywhere.
|
|
player.show_title_screen() //SKYRAT EDIT CHANGE
|
|
continue
|
|
player.create_character(destination)
|
|
else
|
|
player.show_title_screen() //SKYRAT EDIT ADDITION
|
|
|
|
|
|
CHECK_TICK
|
|
|
|
/datum/controller/subsystem/ticker/proc/collect_minds()
|
|
for(var/i in GLOB.new_player_list)
|
|
var/mob/dead/new_player/P = i
|
|
if(P.new_character && P.new_character.mind)
|
|
SSticker.minds += P.new_character.mind
|
|
CHECK_TICK
|
|
|
|
|
|
/datum/controller/subsystem/ticker/proc/equip_characters()
|
|
GLOB.security_officer_distribution = decide_security_officer_departments(
|
|
shuffle(GLOB.new_player_list),
|
|
shuffle(GLOB.available_depts),
|
|
)
|
|
|
|
var/captainless = TRUE
|
|
|
|
var/highest_rank = length(SSjob.chain_of_command) + 1
|
|
var/list/spare_id_candidates = list()
|
|
var/mob/dead/new_player/picked_spare_id_candidate
|
|
|
|
// Find a suitable player to hold captaincy.
|
|
for(var/mob/dead/new_player/new_player_mob as anything in GLOB.new_player_list)
|
|
if(is_banned_from(new_player_mob.ckey, list(JOB_CAPTAIN)))
|
|
CHECK_TICK
|
|
continue
|
|
if(!ishuman(new_player_mob.new_character))
|
|
continue
|
|
var/mob/living/carbon/human/new_player_human = new_player_mob.new_character
|
|
if(!new_player_human.mind || is_unassigned_job(new_player_human.mind.assigned_role))
|
|
continue
|
|
// Keep a rolling tally of who'll get the cap's spare ID vault code.
|
|
// Check assigned_role's priority and curate the candidate list appropriately.
|
|
var/player_assigned_role = new_player_human.mind.assigned_role.title
|
|
var/spare_id_priority = SSjob.chain_of_command[player_assigned_role]
|
|
if(spare_id_priority)
|
|
if(spare_id_priority < highest_rank)
|
|
spare_id_candidates.Cut()
|
|
spare_id_candidates += new_player_mob
|
|
highest_rank = spare_id_priority
|
|
else if(spare_id_priority == highest_rank)
|
|
spare_id_candidates += new_player_mob
|
|
CHECK_TICK
|
|
|
|
if(length(spare_id_candidates))
|
|
picked_spare_id_candidate = pick(spare_id_candidates)
|
|
|
|
for(var/mob/dead/new_player/new_player_mob as anything in GLOB.new_player_list)
|
|
if(QDELETED(new_player_mob) || !isliving(new_player_mob.new_character))
|
|
CHECK_TICK
|
|
continue
|
|
var/mob/living/new_player_living = new_player_mob.new_character
|
|
if(!new_player_living.mind)
|
|
CHECK_TICK
|
|
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)
|
|
player_assigned_role.after_roundstart_spawn(new_player_living, new_player_mob.client)
|
|
if(picked_spare_id_candidate == new_player_mob)
|
|
captainless = FALSE
|
|
var/acting_captain = !is_captain_job(player_assigned_role)
|
|
SSjob.promote_to_captain(new_player_living, acting_captain)
|
|
OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(minor_announce), player_assigned_role.get_captaincy_announcement(new_player_living)))
|
|
if((player_assigned_role.job_flags & JOB_ASSIGN_QUIRKS) && ishuman(new_player_living) && CONFIG_GET(flag/roundstart_traits))
|
|
if(new_player_mob.client?.prefs?.should_be_random_hardcore(player_assigned_role, new_player_living.mind))
|
|
new_player_mob.client.prefs.hardcore_random_setup(new_player_living)
|
|
SSquirks.AssignQuirks(new_player_living, new_player_mob.client)
|
|
|
|
//SKYRAT EDIT ADDITION
|
|
if(ishuman(new_player_living))
|
|
for(var/datum/loadout_item/item as anything in loadout_list_to_datums(new_player_mob.client?.prefs?.loadout_list))
|
|
if (item.restricted_roles && length(item.restricted_roles) && !(player_assigned_role.title in item.restricted_roles))
|
|
continue
|
|
item.post_equip_item(new_player_mob.client?.prefs, new_player_living)
|
|
//SKYRAT EDIT END
|
|
CHECK_TICK
|
|
|
|
if(captainless)
|
|
for(var/mob/dead/new_player/new_player_mob as anything in GLOB.new_player_list)
|
|
var/mob/living/carbon/human/new_player_human = new_player_mob.new_character
|
|
if(new_player_human)
|
|
to_chat(new_player_mob, span_notice("Captainship not forced on anyone."))
|
|
CHECK_TICK
|
|
|
|
|
|
/datum/controller/subsystem/ticker/proc/decide_security_officer_departments(
|
|
list/new_players,
|
|
list/departments,
|
|
)
|
|
var/list/officer_mobs = list()
|
|
var/list/officer_preferences = list()
|
|
|
|
for (var/mob/dead/new_player/new_player_mob as anything in new_players)
|
|
var/mob/living/carbon/human/character = new_player_mob.new_character
|
|
if (istype(character) && is_security_officer_job(character.mind?.assigned_role))
|
|
officer_mobs += character
|
|
|
|
var/datum/client_interface/client = GET_CLIENT(new_player_mob)
|
|
var/preference = client?.prefs?.read_preference(/datum/preference/choiced/security_department)
|
|
officer_preferences += preference
|
|
|
|
var/distribution = get_officer_departments(officer_preferences, departments)
|
|
|
|
var/list/output = list()
|
|
|
|
for (var/index in 1 to officer_mobs.len)
|
|
output[REF(officer_mobs[index])] = distribution[index]
|
|
|
|
return output
|
|
|
|
/datum/controller/subsystem/ticker/proc/transfer_characters()
|
|
var/list/livings = list()
|
|
for(var/i in GLOB.new_player_list)
|
|
var/mob/dead/new_player/player = i
|
|
var/mob/living = player.transfer_character()
|
|
if(living)
|
|
qdel(player)
|
|
living.notransform = TRUE
|
|
if(living.client)
|
|
var/atom/movable/screen/splash/S = new(null, living.client, TRUE)
|
|
S.Fade(TRUE)
|
|
living.client.init_verbs()
|
|
livings += living
|
|
if(livings.len)
|
|
addtimer(CALLBACK(src, PROC_REF(release_characters), livings), 30, TIMER_CLIENT_TIME)
|
|
|
|
/datum/controller/subsystem/ticker/proc/release_characters(list/livings)
|
|
for(var/I in livings)
|
|
var/mob/living/L = I
|
|
L.notransform = FALSE
|
|
|
|
/datum/controller/subsystem/ticker/proc/check_queue()
|
|
if(!queued_players.len)
|
|
return
|
|
var/hard_popcap = CONFIG_GET(number/hard_popcap)
|
|
if(!hard_popcap)
|
|
list_clear_nulls(queued_players)
|
|
for (var/mob/dead/new_player/new_player in queued_players)
|
|
to_chat(new_player, span_userdanger("The alive players limit has been released!<br><a href='?src=[REF(new_player)];late_join=override'>[html_encode(">>Join Game<<")]</a>"))
|
|
SEND_SOUND(new_player, sound('sound/misc/notice1.ogg'))
|
|
GLOB.latejoin_menu.ui_interact(new_player)
|
|
queued_players.len = 0
|
|
queue_delay = 0
|
|
return
|
|
|
|
queue_delay++
|
|
var/mob/dead/new_player/next_in_line = queued_players[1]
|
|
|
|
switch(queue_delay)
|
|
if(5) //every 5 ticks check if there is a slot available
|
|
list_clear_nulls(queued_players)
|
|
if(living_player_count() < hard_popcap)
|
|
if(next_in_line?.client)
|
|
to_chat(next_in_line, span_userdanger("A slot has opened! You have approximately 20 seconds to join. <a href='?src=[REF(next_in_line)];late_join=override'>\>\>Join Game\<\<</a>"))
|
|
SEND_SOUND(next_in_line, sound('sound/misc/notice1.ogg'))
|
|
next_in_line.ui_interact(next_in_line)
|
|
return
|
|
queued_players -= next_in_line //Client disconnected, remove he
|
|
queue_delay = 0 //No vacancy: restart timer
|
|
if(25 to INFINITY) //No response from the next in line when a vacancy exists, remove he
|
|
to_chat(next_in_line, span_danger("No response received. You have been removed from the line."))
|
|
queued_players -= next_in_line
|
|
queue_delay = 0
|
|
|
|
/datum/controller/subsystem/ticker/proc/check_maprotate()
|
|
if(!CONFIG_GET(flag/maprotation))
|
|
return
|
|
if(world.time - SSticker.round_start_time < 10 MINUTES) //Not forcing map rotation for very short rounds.
|
|
return
|
|
INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping/, maprotate))
|
|
|
|
/datum/controller/subsystem/ticker/proc/HasRoundStarted()
|
|
return current_state >= GAME_STATE_PLAYING
|
|
|
|
/datum/controller/subsystem/ticker/proc/IsRoundInProgress()
|
|
return current_state == GAME_STATE_PLAYING
|
|
|
|
/datum/controller/subsystem/ticker/Recover()
|
|
current_state = SSticker.current_state
|
|
force_ending = SSticker.force_ending
|
|
mode = SSticker.mode
|
|
|
|
login_music = SSticker.login_music
|
|
round_end_sound = SSticker.round_end_sound
|
|
|
|
minds = SSticker.minds
|
|
|
|
delay_end = SSticker.delay_end
|
|
|
|
tipped = SSticker.tipped
|
|
selected_tip = SSticker.selected_tip
|
|
|
|
timeLeft = SSticker.timeLeft
|
|
|
|
totalPlayers = SSticker.totalPlayers
|
|
totalPlayersReady = SSticker.totalPlayersReady
|
|
total_admins_ready = SSticker.total_admins_ready
|
|
|
|
queue_delay = SSticker.queue_delay
|
|
queued_players = SSticker.queued_players
|
|
round_start_time = SSticker.round_start_time
|
|
|
|
queue_delay = SSticker.queue_delay
|
|
queued_players = SSticker.queued_players
|
|
|
|
if (Master) //Set Masters run level if it exists
|
|
switch (current_state)
|
|
if(GAME_STATE_SETTING_UP)
|
|
Master.SetRunLevel(RUNLEVEL_SETUP)
|
|
if(GAME_STATE_PLAYING)
|
|
Master.SetRunLevel(RUNLEVEL_GAME)
|
|
if(GAME_STATE_FINISHED)
|
|
Master.SetRunLevel(RUNLEVEL_POSTGAME)
|
|
|
|
/datum/controller/subsystem/ticker/proc/send_news_report()
|
|
var/news_message
|
|
var/news_source = "Nanotrasen News Network"
|
|
var/decoded_station_name = html_decode("[CONFIG_GET(string/cross_comms_name)]([station_name()])") //decode station_name to avoid minor_announce double encode //SKYRAT EDIT CHANGE
|
|
switch(news_report)
|
|
if(NUKE_SYNDICATE_BASE)
|
|
news_message = "In a daring raid, the heroic crew of [decoded_station_name] detonated a nuclear device in the heart of a terrorist base."
|
|
if(STATION_DESTROYED_NUKE)
|
|
news_message = "We would like to reassure all employees that the reports of a Syndicate backed nuclear attack on [decoded_station_name] are, in fact, a hoax. Have a secure day!"
|
|
if(STATION_EVACUATED)
|
|
if(emergency_reason)
|
|
news_message = "[decoded_station_name] has been evacuated after transmitting the following distress beacon:\n\n[html_decode(emergency_reason)]"
|
|
else
|
|
news_message = "The crew of [decoded_station_name] has been evacuated amid unconfirmed reports of enemy activity."
|
|
if(BLOB_WIN)
|
|
news_message = "[decoded_station_name] was overcome by an unknown biological outbreak, killing all crew on board. Don't let it happen to you! Remember, a clean work station is a safe work station."
|
|
if(BLOB_NUKE)
|
|
news_message = "[decoded_station_name] is currently undergoing decontanimation after a controlled burst of radiation was used to remove a biological ooze. All employees were safely evacuated prior, and are enjoying a relaxing vacation."
|
|
if(BLOB_DESTROYED)
|
|
news_message = "[decoded_station_name] is currently undergoing decontamination procedures after the destruction of a biological hazard. As a reminder, any crew members experiencing cramps or bloating should report immediately to security for incineration."
|
|
if(CULT_ESCAPE)
|
|
news_message = "Security Alert: A group of religious fanatics have escaped from [decoded_station_name]."
|
|
if(CULT_FAILURE)
|
|
news_message = "Following the dismantling of a restricted cult aboard [decoded_station_name], we would like to remind all employees that worship outside of the Chapel is strictly prohibited, and cause for termination."
|
|
if(CULT_SUMMON)
|
|
news_message = "Company officials would like to clarify that [decoded_station_name] was scheduled to be decommissioned following meteor damage earlier this year. Earlier reports of an unknowable eldritch horror were made in error."
|
|
if(NUKE_MISS)
|
|
news_message = "The Syndicate have bungled a terrorist attack [decoded_station_name], detonating a nuclear weapon in empty space nearby."
|
|
if(OPERATIVES_KILLED)
|
|
news_message = "Repairs to [decoded_station_name] are underway after an elite Syndicate death squad was wiped out by the crew."
|
|
if(OPERATIVE_SKIRMISH)
|
|
news_message = "A skirmish between security forces and Syndicate agents aboard [decoded_station_name] ended with both sides bloodied but intact."
|
|
if(REVS_WIN)
|
|
news_message = "Company officials have reassured investors that despite a union led revolt aboard [decoded_station_name] there will be no wage increases for workers."
|
|
if(REVS_LOSE)
|
|
news_message = "[decoded_station_name] quickly put down a misguided attempt at mutiny. Remember, unionizing is illegal!"
|
|
if(WIZARD_KILLED)
|
|
news_message = "Tensions have flared with the Space Wizard Federation following the death of one of their members aboard [decoded_station_name]."
|
|
if(STATION_NUKED)
|
|
news_message = "[decoded_station_name] activated its self-destruct device for unknown reasons. Attempts to clone the Captain for arrest and execution are underway."
|
|
if(SHUTTLE_HIJACK)
|
|
news_message = "During routine evacuation procedures, the emergency shuttle of [decoded_station_name] had its navigation protocols corrupted and went off course, but was recovered shortly after."
|
|
if(SUPERMATTER_CASCADE)
|
|
news_message = "Officials are advising nearby colonies about a newly declared exclusion zone in the sector surrounding [decoded_station_name]."
|
|
|
|
//SKYRAT EDIT - START
|
|
if(SSblackbox.first_death)
|
|
var/list/ded = SSblackbox.first_death
|
|
if(ded.len)
|
|
news_message += " NT Sanctioned Psykers picked up faint traces of someone near the station, allegedly having had died. Their name was: [ded["name"]], [ded["role"]], at [ded["area"]].[ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : ""]" // " // An Extra quote and comment because highlighting goes weird
|
|
else
|
|
news_message += " NT Sanctioned Psykers proudly confirm reports that nobody died this shift!"
|
|
//SKYRAT EDIT - END
|
|
|
|
if(news_message && length(CONFIG_GET(keyed_list/cross_server))) //SKYRAT EDIT - CONFIG CHECK MOVED FROM ROUNDEND.DM
|
|
news_message += " (Shift on [CONFIG_GET(string/cross_server_name)] ending!)" //SKYRAT EDIT ADDITION
|
|
send2otherserver(news_source, news_message,"News_Report")
|
|
//SKYRAT EDIT - START
|
|
if(news_message)
|
|
return news_message
|
|
else
|
|
return "We regret to inform you that shit be whack, yo. None of our reporters have any idea of what may or may not have gone on."
|
|
//SKYRAT EDIT - END
|
|
|
|
/datum/controller/subsystem/ticker/proc/GetTimeLeft()
|
|
if(isnull(SSticker.timeLeft))
|
|
return max(0, start_at - world.time)
|
|
return timeLeft
|
|
|
|
/datum/controller/subsystem/ticker/proc/SetTimeLeft(newtime)
|
|
if(newtime >= 0 && isnull(timeLeft)) //remember, negative means delayed
|
|
start_at = world.time + newtime
|
|
else
|
|
timeLeft = newtime
|
|
|
|
/datum/controller/subsystem/ticker/proc/SetRoundEndSound(the_sound)
|
|
set waitfor = FALSE
|
|
round_end_sound_sent = FALSE
|
|
round_end_sound = fcopy_rsc(the_sound)
|
|
for(var/thing in GLOB.clients)
|
|
var/client/C = thing
|
|
if (!C)
|
|
continue
|
|
C.Export("##action=load_rsc", round_end_sound)
|
|
round_end_sound_sent = TRUE
|
|
|
|
/datum/controller/subsystem/ticker/proc/Reboot(reason, end_string, delay)
|
|
set waitfor = FALSE
|
|
if(usr && !check_rights(R_SERVER, TRUE))
|
|
return
|
|
|
|
if(!delay)
|
|
delay = CONFIG_GET(number/round_end_countdown) * 10
|
|
|
|
var/skip_delay = check_rights()
|
|
if(delay_end && !skip_delay)
|
|
to_chat(world, span_boldannounce("An admin has delayed the round end."))
|
|
return
|
|
|
|
to_chat(world, span_boldannounce("Rebooting World in [DisplayTimeText(delay)]. [reason]"))
|
|
|
|
var/start_wait = world.time
|
|
UNTIL(round_end_sound_sent || (world.time - start_wait) > (delay * 2)) //don't wait forever
|
|
sleep(delay - (world.time - start_wait))
|
|
|
|
if(delay_end && !skip_delay)
|
|
to_chat(world, span_boldannounce("Reboot was cancelled by an admin."))
|
|
return
|
|
if(end_string)
|
|
end_state = end_string
|
|
|
|
var/statspage = CONFIG_GET(string/roundstatsurl)
|
|
var/gamelogloc = CONFIG_GET(string/gamelogurl)
|
|
if(statspage)
|
|
to_chat(world, span_info("Round statistics and logs can be viewed <a href=\"[statspage][GLOB.round_id]\">at this website!</a>"))
|
|
else if(gamelogloc)
|
|
to_chat(world, span_info("Round logs can be located <a href=\"[gamelogloc]\">at this website!</a>"))
|
|
|
|
log_game(span_boldannounce("Rebooting World. [reason]"))
|
|
|
|
world.Reboot()
|
|
|
|
/datum/controller/subsystem/ticker/Shutdown()
|
|
gather_newscaster() //called here so we ensure the log is created even upon admin reboot
|
|
save_admin_data()
|
|
update_everything_flag_in_db()
|
|
if(!round_end_sound)
|
|
round_end_sound = choose_round_end_song()
|
|
///The reference to the end of round sound that we have chosen.
|
|
var/sound/end_of_round_sound_ref = sound(round_end_sound)
|
|
for(var/mob/M in GLOB.player_list)
|
|
if(M.client.prefs.read_preference(/datum/preference/toggle/sound_endofround))
|
|
SEND_SOUND(M.client, end_of_round_sound_ref)
|
|
|
|
text2file(login_music, "data/last_round_lobby_music.txt")
|
|
|
|
/datum/controller/subsystem/ticker/proc/choose_round_end_song()
|
|
var/list/reboot_sounds = flist("[global.config.directory]/reboot_themes/")
|
|
var/list/possible_themes = list()
|
|
|
|
for(var/themes in reboot_sounds)
|
|
possible_themes += themes
|
|
if(possible_themes.len)
|
|
return "[global.config.directory]/reboot_themes/[pick(possible_themes)]"
|