@@ -0,0 +1,80 @@
|
||||
SUBSYSTEM_DEF(chat)
|
||||
name = "Chat"
|
||||
flags = SS_TICKER
|
||||
wait = 1
|
||||
priority = FIRE_PRIORITY_CHAT
|
||||
init_order = INIT_ORDER_CHAT
|
||||
var/list/payload = list()
|
||||
|
||||
|
||||
/datum/controller/subsystem/chat/fire()
|
||||
for(var/i in payload)
|
||||
var/client/C = i
|
||||
C << output(payload[C], "browseroutput:output")
|
||||
payload -= C
|
||||
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
|
||||
/datum/controller/subsystem/chat/proc/queue(target, message, handle_whitespace = TRUE)
|
||||
if(!target || !message)
|
||||
return
|
||||
|
||||
if(!istext(message))
|
||||
stack_trace("to_chat called with invalid input type")
|
||||
return
|
||||
|
||||
if(target == world)
|
||||
target = GLOB.clients
|
||||
|
||||
//Some macros remain in the string even after parsing and fuck up the eventual output
|
||||
var/original_message = message
|
||||
message = replacetext(message, "\improper", "")
|
||||
message = replacetext(message, "\proper", "")
|
||||
if(handle_whitespace)
|
||||
message = replacetext(message, "\n", "<br>")
|
||||
message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]")
|
||||
message += "<br>"
|
||||
|
||||
|
||||
//url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
|
||||
//Do the double-encoding here to save nanoseconds
|
||||
var/twiceEncoded = url_encode(url_encode(message))
|
||||
|
||||
if(islist(target))
|
||||
for(var/I in target)
|
||||
var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
|
||||
|
||||
if(!C)
|
||||
return
|
||||
|
||||
//Send it to the old style output window.
|
||||
SEND_TEXT(C, original_message)
|
||||
|
||||
if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
|
||||
continue
|
||||
|
||||
if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
|
||||
C.chatOutput.messageQueue += message
|
||||
continue
|
||||
|
||||
payload[C] += twiceEncoded
|
||||
|
||||
else
|
||||
var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible
|
||||
|
||||
if(!C)
|
||||
return
|
||||
|
||||
//Send it to the old style output window.
|
||||
SEND_TEXT(C, original_message)
|
||||
|
||||
if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
|
||||
return
|
||||
|
||||
if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
|
||||
C.chatOutput.messageQueue += message
|
||||
return
|
||||
|
||||
payload[C] += twiceEncoded
|
||||
@@ -0,0 +1,741 @@
|
||||
SUBSYSTEM_DEF(job)
|
||||
name = "Jobs"
|
||||
init_order = INIT_ORDER_JOBS
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
var/list/occupations = list() //List of all jobs
|
||||
var/list/datum/job/name_occupations = list() //Dict of all jobs, keys are titles
|
||||
var/list/type_occupations = list() //Dict of all jobs, keys are types
|
||||
var/list/unassigned = list() //Players who need jobs
|
||||
var/initial_players_to_assign = 0 //used for checking against population caps
|
||||
|
||||
var/list/prioritized_jobs = list()
|
||||
var/list/latejoin_trackers = list() //Don't read this list, use GetLateJoinTurfs() instead
|
||||
|
||||
var/overflow_role = "Assistant"
|
||||
|
||||
var/list/level_order = list(JP_HIGH,JP_MEDIUM,JP_LOW)
|
||||
|
||||
/datum/controller/subsystem/job/Initialize(timeofday)
|
||||
SSmapping.HACK_LoadMapConfig()
|
||||
if(!occupations.len)
|
||||
SetupOccupations()
|
||||
if(CONFIG_GET(flag/load_jobs_from_txt))
|
||||
LoadJobs()
|
||||
generate_selectable_species()
|
||||
set_overflow_role(CONFIG_GET(string/overflow_job))
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/job/proc/set_overflow_role(new_overflow_role)
|
||||
var/datum/job/new_overflow = GetJob(new_overflow_role)
|
||||
var/cap = CONFIG_GET(number/overflow_cap)
|
||||
|
||||
new_overflow.spawn_positions = cap
|
||||
new_overflow.total_positions = cap
|
||||
|
||||
if(new_overflow_role != overflow_role)
|
||||
var/datum/job/old_overflow = GetJob(overflow_role)
|
||||
old_overflow.spawn_positions = initial(old_overflow.spawn_positions)
|
||||
old_overflow.total_positions = initial(old_overflow.total_positions)
|
||||
overflow_role = new_overflow_role
|
||||
JobDebug("Overflow role set to : [new_overflow_role]")
|
||||
|
||||
/datum/controller/subsystem/job/proc/SetupOccupations(faction = "Station")
|
||||
occupations = list()
|
||||
var/list/all_jobs = subtypesof(/datum/job)
|
||||
if(!all_jobs.len)
|
||||
to_chat(world, "<span class='boldannounce'>Error setting up jobs, no job datums found</span>")
|
||||
return 0
|
||||
|
||||
for(var/J in all_jobs)
|
||||
var/datum/job/job = new J()
|
||||
if(!job)
|
||||
continue
|
||||
if(job.faction != faction)
|
||||
continue
|
||||
if(!job.config_check())
|
||||
continue
|
||||
if(!job.map_check()) //Even though we initialize before mapping, this is fine because the config is loaded at new
|
||||
testing("Removed [job.type] due to map config");
|
||||
continue
|
||||
occupations += job
|
||||
name_occupations[job.title] = job
|
||||
type_occupations[J] = job
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
/datum/controller/subsystem/job/proc/GetJob(rank)
|
||||
if(!occupations.len)
|
||||
SetupOccupations()
|
||||
return name_occupations[rank]
|
||||
|
||||
/datum/controller/subsystem/job/proc/GetJobType(jobtype)
|
||||
if(!occupations.len)
|
||||
SetupOccupations()
|
||||
return type_occupations[jobtype]
|
||||
|
||||
/datum/controller/subsystem/job/proc/AssignRole(mob/dead/new_player/player, rank, latejoin = FALSE)
|
||||
JobDebug("Running AR, Player: [player], Rank: [rank], LJ: [latejoin]")
|
||||
if(player && player.mind && rank)
|
||||
var/datum/job/job = GetJob(rank)
|
||||
if(!job)
|
||||
return FALSE
|
||||
if(jobban_isbanned(player, rank) || QDELETED(player))
|
||||
return FALSE
|
||||
if(!job.player_old_enough(player.client))
|
||||
return FALSE
|
||||
if(job.required_playtime_remaining(player.client))
|
||||
return FALSE
|
||||
var/position_limit = job.total_positions
|
||||
if(!latejoin)
|
||||
position_limit = job.spawn_positions
|
||||
JobDebug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JPL:[position_limit]")
|
||||
player.mind.assigned_role = rank
|
||||
unassigned -= player
|
||||
job.current_positions++
|
||||
return TRUE
|
||||
JobDebug("AR has failed, Player: [player], Rank: [rank]")
|
||||
return FALSE
|
||||
|
||||
|
||||
/datum/controller/subsystem/job/proc/FindOccupationCandidates(datum/job/job, level, flag)
|
||||
JobDebug("Running FOC, Job: [job], Level: [level], Flag: [flag]")
|
||||
var/list/candidates = list()
|
||||
for(var/mob/dead/new_player/player in unassigned)
|
||||
if(jobban_isbanned(player, job.title) || QDELETED(player))
|
||||
JobDebug("FOC isbanned failed, Player: [player]")
|
||||
continue
|
||||
if(!job.player_old_enough(player.client))
|
||||
JobDebug("FOC player not old enough, Player: [player]")
|
||||
continue
|
||||
if(job.required_playtime_remaining(player.client))
|
||||
JobDebug("FOC player not enough xp, Player: [player]")
|
||||
continue
|
||||
if(!player.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features))
|
||||
JobDebug("FOC non-human failed, Player: [player]")
|
||||
continue
|
||||
if(flag && (!(flag in player.client.prefs.be_special)))
|
||||
JobDebug("FOC flag failed, Player: [player], Flag: [flag], ")
|
||||
continue
|
||||
if(player.mind && job.title in player.mind.restricted_roles)
|
||||
JobDebug("FOC incompatible with antagonist role, Player: [player]")
|
||||
continue
|
||||
if(player.client.prefs.job_preferences[job.title] == level)
|
||||
JobDebug("FOC pass, Player: [player], Level:[level]")
|
||||
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 in shuffle(occupations))
|
||||
if(!job)
|
||||
continue
|
||||
|
||||
if(istype(job, GetJob(SSjob.overflow_role))) // We don't want to give him assistant, that's boring!
|
||||
continue
|
||||
|
||||
if(job.title in GLOB.command_positions) //If you want a command position, select it!
|
||||
continue
|
||||
|
||||
if(jobban_isbanned(player, job.title) || QDELETED(player))
|
||||
if(QDELETED(player))
|
||||
JobDebug("GRJ isbanned failed, Player deleted")
|
||||
break
|
||||
JobDebug("GRJ isbanned failed, Player: [player], Job: [job.title]")
|
||||
continue
|
||||
|
||||
if(!job.player_old_enough(player.client))
|
||||
JobDebug("GRJ player not old enough, Player: [player]")
|
||||
continue
|
||||
|
||||
if(!player.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features))
|
||||
JobDebug("GRJ non-human failed, Player: [player]")
|
||||
continue
|
||||
|
||||
if(job.required_playtime_remaining(player.client))
|
||||
JobDebug("GRJ player not enough xp, Player: [player]")
|
||||
continue
|
||||
|
||||
if(player.mind && job.title in player.mind.restricted_roles)
|
||||
JobDebug("GRJ incompatible with antagonist role, Player: [player], Job: [job.title]")
|
||||
continue
|
||||
|
||||
if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1)
|
||||
JobDebug("GRJ Random job given, Player: [player], Job: [job]")
|
||||
if(AssignRole(player, job.title))
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/job/proc/ResetOccupations()
|
||||
JobDebug("Occupations reset.")
|
||||
for(var/mob/dead/new_player/player in GLOB.player_list)
|
||||
if((player) && (player.mind))
|
||||
player.mind.assigned_role = null
|
||||
player.mind.special_role = null
|
||||
SSpersistence.antag_rep_change[player.ckey] = 0
|
||||
SetupOccupations()
|
||||
unassigned = list()
|
||||
return
|
||||
|
||||
|
||||
//This proc is called before the level loop of DivideOccupations() and will try to select a head, ignoring ALL non-head preferences for every level until
|
||||
//it locates a head or runs out of levels to check
|
||||
//This is basically to ensure that there's atleast a few heads in the round
|
||||
/datum/controller/subsystem/job/proc/FillHeadPosition()
|
||||
for(var/level in level_order)
|
||||
for(var/command_position in GLOB.command_positions)
|
||||
var/datum/job/job = GetJob(command_position)
|
||||
if(!job)
|
||||
continue
|
||||
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)
|
||||
if(AssignRole(candidate, command_position))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
//This proc is called at the start of the level loop of DivideOccupations() and will cause head jobs to be checked before any other jobs of the same level
|
||||
//This is also to ensure we get as many heads as possible
|
||||
/datum/controller/subsystem/job/proc/CheckHeadPositions(level)
|
||||
for(var/command_position in GLOB.command_positions)
|
||||
var/datum/job/job = GetJob(command_position)
|
||||
if(!job)
|
||||
continue
|
||||
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)
|
||||
AssignRole(candidate, command_position)
|
||||
|
||||
/datum/controller/subsystem/job/proc/FillAIPosition()
|
||||
var/ai_selected = 0
|
||||
var/datum/job/job = GetJob("AI")
|
||||
if(!job)
|
||||
return 0
|
||||
for(var/i = job.total_positions, i > 0, i--)
|
||||
for(var/level in level_order)
|
||||
var/list/candidates = list()
|
||||
candidates = FindOccupationCandidates(job, level)
|
||||
if(candidates.len)
|
||||
var/mob/dead/new_player/candidate = pick(candidates)
|
||||
if(AssignRole(candidate, "AI"))
|
||||
ai_selected++
|
||||
break
|
||||
if(ai_selected)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
/** Proc DivideOccupations
|
||||
* 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(list/required_jobs)
|
||||
//Setup new player list and get the jobs list
|
||||
JobDebug("Running DO")
|
||||
|
||||
//Holder for Triumvirate is stored in the SSticker, this just processes it
|
||||
if(SSticker.triai)
|
||||
for(var/datum/job/ai/A in occupations)
|
||||
A.spawn_positions = 3
|
||||
for(var/obj/effect/landmark/start/ai/secondary/S in GLOB.start_landmarks_list)
|
||||
S.latejoin_active = TRUE
|
||||
|
||||
//Get the players who are ready
|
||||
for(var/mob/dead/new_player/player in GLOB.player_list)
|
||||
if(player.ready == PLAYER_READY_TO_PLAY && player.check_preferences() && player.mind && !player.mind.assigned_role)
|
||||
unassigned += player
|
||||
|
||||
initial_players_to_assign = unassigned.len
|
||||
|
||||
JobDebug("DO, Len: [unassigned?.len]")
|
||||
if(unassigned.len == 0)
|
||||
return validate_required_jobs(required_jobs)
|
||||
|
||||
//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)
|
||||
CONFIG_SET(flag/jobs_have_minimal_access, FALSE)
|
||||
else
|
||||
CONFIG_SET(flag/jobs_have_minimal_access, TRUE)
|
||||
|
||||
//Shuffle players and jobs
|
||||
unassigned = shuffle(unassigned)
|
||||
|
||||
HandleFeedbackGathering()
|
||||
|
||||
//People who wants to be the overflow role, sure, go on.
|
||||
JobDebug("DO, Running Overflow Check 1")
|
||||
var/datum/job/overflow = GetJob(SSjob.overflow_role)
|
||||
var/list/overflow_candidates = FindOccupationCandidates(overflow, JP_LOW)
|
||||
JobDebug("AC1, Candidates: [overflow_candidates?.len]")
|
||||
for(var/mob/dead/new_player/player in overflow_candidates)
|
||||
JobDebug("AC1 pass, Player: [player]")
|
||||
AssignRole(player, SSjob.overflow_role)
|
||||
overflow_candidates -= player
|
||||
JobDebug("DO, AC1 end")
|
||||
|
||||
//Select one head
|
||||
JobDebug("DO, Running Head Check")
|
||||
FillHeadPosition()
|
||||
JobDebug("DO, Head Check end")
|
||||
|
||||
//Check for an AI
|
||||
JobDebug("DO, Running AI Check")
|
||||
FillAIPosition()
|
||||
JobDebug("DO, AI Check end")
|
||||
|
||||
//Other jobs are now checked
|
||||
JobDebug("DO, Running Standard Check")
|
||||
|
||||
|
||||
// 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.
|
||||
|
||||
// Loop through all levels from high to low
|
||||
var/list/shuffledoccupations = shuffle(occupations)
|
||||
for(var/level in level_order)
|
||||
//Check the head jobs first each level
|
||||
CheckHeadPositions(level)
|
||||
|
||||
// Loop through all unassigned players
|
||||
for(var/mob/dead/new_player/player in unassigned)
|
||||
if(PopcapReached())
|
||||
RejectPlayer(player)
|
||||
|
||||
// Loop through all jobs
|
||||
for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY
|
||||
if(!job)
|
||||
continue
|
||||
|
||||
if(jobban_isbanned(player, job.title))
|
||||
JobDebug("DO isbanned failed, Player: [player], Job:[job.title]")
|
||||
continue
|
||||
|
||||
if(QDELETED(player))
|
||||
JobDebug("DO player deleted during job ban check")
|
||||
break
|
||||
|
||||
if(!job.player_old_enough(player.client))
|
||||
JobDebug("DO player not old enough, Player: [player], Job:[job.title]")
|
||||
continue
|
||||
|
||||
if(job.required_playtime_remaining(player.client))
|
||||
JobDebug("DO player not enough xp, Player: [player], Job:[job.title]")
|
||||
continue
|
||||
|
||||
if(!player.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features))
|
||||
JobDebug("DO non-human failed, Player: [player], Job:[job.title]")
|
||||
continue
|
||||
|
||||
if(player.mind && job.title in player.mind.restricted_roles)
|
||||
JobDebug("DO incompatible with antagonist role, Player: [player], Job:[job.title]")
|
||||
continue
|
||||
|
||||
// If the player wants that job on this level, then try give it to him.
|
||||
if(player.client.prefs.job_preferences[job.title] == level)
|
||||
// If the job isn't filled
|
||||
if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1)
|
||||
JobDebug("DO pass, Player: [player], Level:[level], Job:[job.title]")
|
||||
AssignRole(player, job.title)
|
||||
unassigned -= player
|
||||
break
|
||||
|
||||
|
||||
JobDebug("DO, Handling 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
|
||||
for(var/mob/dead/new_player/player in unassigned)
|
||||
HandleUnassigned(player)
|
||||
|
||||
JobDebug("DO, Handling 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, SSjob.overflow_role)) //If everything is already filled, make them an assistant
|
||||
return FALSE //Living on the edge, the forced antagonist couldn't be assigned to overflow role (bans, client age) - just reroll
|
||||
|
||||
return validate_required_jobs(required_jobs)
|
||||
|
||||
/datum/controller/subsystem/job/proc/validate_required_jobs(list/required_jobs)
|
||||
if(!required_jobs.len)
|
||||
return TRUE
|
||||
for(var/required_group in required_jobs)
|
||||
var/group_ok = TRUE
|
||||
for(var/rank in required_group)
|
||||
var/datum/job/J = GetJob(rank)
|
||||
if(!J)
|
||||
SSticker.mode.setup_error = "Invalid job [rank] in gamemode required jobs."
|
||||
return FALSE
|
||||
if(J.current_positions < required_group[rank])
|
||||
group_ok = FALSE
|
||||
break
|
||||
if(group_ok)
|
||||
return TRUE
|
||||
SSticker.mode.setup_error = "Required jobs not present."
|
||||
return FALSE
|
||||
|
||||
//We couldn't find a job from prefs for this guy.
|
||||
/datum/controller/subsystem/job/proc/HandleUnassigned(mob/dead/new_player/player)
|
||||
if(PopcapReached())
|
||||
RejectPlayer(player)
|
||||
else if(player.client.prefs.joblessrole == BEOVERFLOW)
|
||||
var/allowed_to_be_a_loser = !jobban_isbanned(player, SSjob.overflow_role)
|
||||
if(QDELETED(player) || !allowed_to_be_a_loser)
|
||||
RejectPlayer(player)
|
||||
else
|
||||
if(!AssignRole(player, SSjob.overflow_role))
|
||||
RejectPlayer(player)
|
||||
else if(player.client.prefs.joblessrole == BERANDOMJOB)
|
||||
if(!GiveRandomJob(player))
|
||||
RejectPlayer(player)
|
||||
else if(player.client.prefs.joblessrole == RETURNTOLOBBY)
|
||||
RejectPlayer(player)
|
||||
else //Something gone wrong if we got here.
|
||||
var/message = "DO: [player] fell through handling unassigned"
|
||||
JobDebug(message)
|
||||
log_game(message)
|
||||
message_admins(message)
|
||||
RejectPlayer(player)
|
||||
//Gives the player the stuff he should have with his rank
|
||||
/datum/controller/subsystem/job/proc/EquipRank(mob/M, rank, joined_late = FALSE)
|
||||
var/mob/dead/new_player/N
|
||||
var/mob/living/H
|
||||
if(!joined_late)
|
||||
N = M
|
||||
H = N.new_character
|
||||
else
|
||||
H = M
|
||||
|
||||
var/datum/job/job = GetJob(rank)
|
||||
|
||||
H.job = rank
|
||||
|
||||
//If we joined at roundstart we should be positioned at our workstation
|
||||
if(!joined_late)
|
||||
var/obj/S = null
|
||||
for(var/obj/effect/landmark/start/sloc in GLOB.start_landmarks_list)
|
||||
if(sloc.name != rank)
|
||||
S = sloc //so we can revert to spawning them on top of eachother if something goes wrong
|
||||
continue
|
||||
if(locate(/mob/living) in sloc.loc)
|
||||
continue
|
||||
S = sloc
|
||||
sloc.used = TRUE
|
||||
break
|
||||
if(length(GLOB.jobspawn_overrides[rank]))
|
||||
S = pick(GLOB.jobspawn_overrides[rank])
|
||||
if(S)
|
||||
S.JoinPlayerHere(H, FALSE)
|
||||
if(!S) //if there isn't a spawnpoint send them to latejoin, if there's no latejoin go yell at your mapper
|
||||
log_world("Couldn't find a round start spawn point for [rank]")
|
||||
SendToLateJoin(H)
|
||||
|
||||
|
||||
if(H.mind)
|
||||
H.mind.assigned_role = rank
|
||||
|
||||
if(job)
|
||||
if(!job.dresscodecompliant)// CIT CHANGE - dress code compliance
|
||||
equip_loadout(N, H) // CIT CHANGE - allows players to spawn with loadout items
|
||||
var/new_mob = job.equip(H, null, null, joined_late , null, M.client)
|
||||
if(ismob(new_mob))
|
||||
H = new_mob
|
||||
if(!joined_late)
|
||||
N.new_character = H
|
||||
else
|
||||
M = H
|
||||
|
||||
SSpersistence.antag_rep_change[M.client.ckey] += job.GetAntagRep()
|
||||
|
||||
/* if(M.client.holder)
|
||||
if(CONFIG_GET(flag/auto_deadmin_players) || (M.client.prefs?.toggles & DEADMIN_ALWAYS))
|
||||
M.client.holder.auto_deadmin()
|
||||
else
|
||||
handle_auto_deadmin_roles(M.client, rank) */
|
||||
|
||||
to_chat(M, "<b>You are the [rank].</b>")
|
||||
if(job)
|
||||
to_chat(M, "<b>As the [rank] you answer directly to [job.supervisors]. Special circumstances may change this.</b>")
|
||||
job.radio_help_message(M)
|
||||
if(job.req_admin_notify)
|
||||
to_chat(M, "<b>You are playing a job that is important for Game Progression. If you have to disconnect immediately, please notify the admins via adminhelp. Otherwise put your locker gear back into the locker and cryo out.</b>")
|
||||
if(job.custom_spawn_text)
|
||||
to_chat(M, "<b>[job.custom_spawn_text]</b>")
|
||||
if(CONFIG_GET(number/minimal_access_threshold))
|
||||
to_chat(M, "<span class='notice'><B>As this station was initially staffed with a [CONFIG_GET(flag/jobs_have_minimal_access) ? "full crew, only your job's necessities" : "skeleton crew, additional access may"] have been added to your ID card.</B></span>")
|
||||
if(job && H)
|
||||
if(job.dresscodecompliant)// CIT CHANGE - dress code compliance
|
||||
equip_loadout(N, H) // CIT CHANGE - allows players to spawn with loadout items
|
||||
job.after_spawn(H, M, joined_late) // note: this happens before the mob has a key! M will always have a client, H might not.
|
||||
equip_loadout(N, H, TRUE)//CIT CHANGE - makes players spawn with in-backpack loadout items properly. A little hacky but it works
|
||||
|
||||
return H
|
||||
/*
|
||||
/datum/controller/subsystem/job/proc/handle_auto_deadmin_roles(client/C, rank)
|
||||
if(!C?.holder)
|
||||
return TRUE
|
||||
var/datum/job/job = GetJob(rank)
|
||||
if(!job)
|
||||
return
|
||||
if((job.auto_deadmin_role_flags & DEADMIN_POSITION_HEAD) && (CONFIG_GET(flag/auto_deadmin_heads) || (C.prefs?.toggles & DEADMIN_POSITION_HEAD)))
|
||||
return C.holder.auto_deadmin()
|
||||
else if((job.auto_deadmin_role_flags & DEADMIN_POSITION_SECURITY) && (CONFIG_GET(flag/auto_deadmin_security) || (C.prefs?.toggles & DEADMIN_POSITION_SECURITY)))
|
||||
return C.holder.auto_deadmin()
|
||||
else if((job.auto_deadmin_role_flags & DEADMIN_POSITION_SILICON) && (CONFIG_GET(flag/auto_deadmin_silicons) || (C.prefs?.toggles & DEADMIN_POSITION_SILICON))) //in the event there's ever psuedo-silicon roles added, ie synths.
|
||||
return C.holder.auto_deadmin()*/
|
||||
|
||||
/datum/controller/subsystem/job/proc/setup_officer_positions()
|
||||
var/datum/job/J = SSjob.GetJob("Security Officer")
|
||||
if(!J)
|
||||
CRASH("setup_officer_positions(): Security officer job is missing")
|
||||
|
||||
var/ssc = CONFIG_GET(number/security_scaling_coeff)
|
||||
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]")
|
||||
J.total_positions = officer_positions
|
||||
J.spawn_positions = officer_positions
|
||||
|
||||
//Spawn some extra eqipment lockers if we have more than 5 officers
|
||||
var/equip_needed = J.total_positions
|
||||
if(equip_needed < 0) // -1: infinite available slots
|
||||
equip_needed = 12
|
||||
for(var/i=equip_needed-5, i>0, i--)
|
||||
if(GLOB.secequipment.len)
|
||||
var/spawnloc = GLOB.secequipment[1]
|
||||
new /obj/structure/closet/secure_closet/security/sec(spawnloc)
|
||||
GLOB.secequipment -= spawnloc
|
||||
else //We ran out of spare locker spawns!
|
||||
break
|
||||
|
||||
|
||||
/datum/controller/subsystem/job/proc/LoadJobs()
|
||||
var/jobstext = file2text("[global.config.directory]/jobs.txt")
|
||||
for(var/datum/job/J in occupations)
|
||||
var/regex/jobs = new("[J.title]=(-1|\\d+),(-1|\\d+)")
|
||||
jobs.Find(jobstext)
|
||||
J.total_positions = text2num(jobs.group[1])
|
||||
J.spawn_positions = text2num(jobs.group[2])
|
||||
|
||||
/datum/controller/subsystem/job/proc/HandleFeedbackGathering()
|
||||
for(var/datum/job/job in occupations)
|
||||
var/high = 0 //high
|
||||
var/medium = 0 //medium
|
||||
var/low = 0 //low
|
||||
var/never = 0 //never
|
||||
var/banned = 0 //banned
|
||||
var/young = 0 //account too young
|
||||
for(var/mob/dead/new_player/player in GLOB.player_list)
|
||||
if(!(player.ready == PLAYER_READY_TO_PLAY && player.mind && !player.mind.assigned_role))
|
||||
continue //This player is not ready
|
||||
if(jobban_isbanned(player, job.title) || QDELETED(player))
|
||||
banned++
|
||||
continue
|
||||
if(!job.player_old_enough(player.client))
|
||||
young++
|
||||
continue
|
||||
if(job.required_playtime_remaining(player.client))
|
||||
young++
|
||||
continue
|
||||
switch(player.client.prefs.job_preferences[job.title])
|
||||
if(JP_HIGH)
|
||||
high++
|
||||
if(JP_MEDIUM)
|
||||
medium++
|
||||
if(JP_LOW)
|
||||
low++
|
||||
else
|
||||
never++
|
||||
SSblackbox.record_feedback("nested tally", "job_preferences", high, list("[job.title]", "high"))
|
||||
SSblackbox.record_feedback("nested tally", "job_preferences", medium, list("[job.title]", "medium"))
|
||||
SSblackbox.record_feedback("nested tally", "job_preferences", low, list("[job.title]", "low"))
|
||||
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"))
|
||||
|
||||
/datum/controller/subsystem/job/proc/PopcapReached()
|
||||
var/hpc = CONFIG_GET(number/hard_popcap)
|
||||
var/epc = CONFIG_GET(number/extreme_popcap)
|
||||
if(hpc || epc)
|
||||
var/relevent_cap = max(hpc, epc)
|
||||
if((initial_players_to_assign - unassigned.len) >= relevent_cap)
|
||||
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]")
|
||||
to_chat(player, "<b>You have failed to qualify for any job you desired.</b>")
|
||||
unassigned -= player
|
||||
player.ready = PLAYER_NOT_READY
|
||||
|
||||
|
||||
/datum/controller/subsystem/job/Recover()
|
||||
set waitfor = FALSE
|
||||
var/oldjobs = SSjob.occupations
|
||||
sleep(20)
|
||||
for (var/datum/job/J in oldjobs)
|
||||
INVOKE_ASYNC(src, .proc/RecoverJob, J)
|
||||
|
||||
/datum/controller/subsystem/job/proc/RecoverJob(datum/job/J)
|
||||
var/datum/job/newjob = GetJob(J.title)
|
||||
if (!istype(newjob))
|
||||
return
|
||||
newjob.total_positions = J.total_positions
|
||||
newjob.spawn_positions = J.spawn_positions
|
||||
newjob.current_positions = J.current_positions
|
||||
|
||||
/atom/proc/JoinPlayerHere(mob/M, buckle)
|
||||
// By default, just place the mob on the same turf as the marker or whatever.
|
||||
M.forceMove(get_turf(src))
|
||||
|
||||
/obj/structure/chair/JoinPlayerHere(mob/M, buckle)
|
||||
// Placing a mob in a chair will attempt to buckle it, or else fall back to default.
|
||||
if (buckle && isliving(M) && buckle_mob(M, FALSE, FALSE))
|
||||
return
|
||||
..()
|
||||
|
||||
/datum/controller/subsystem/job/proc/SendToLateJoin(mob/M, buckle = TRUE)
|
||||
var/atom/destination
|
||||
if(M.mind && M.mind.assigned_role && length(GLOB.jobspawn_overrides[M.mind.assigned_role])) //We're doing something special today.
|
||||
destination = pick(GLOB.jobspawn_overrides[M.mind.assigned_role])
|
||||
destination.JoinPlayerHere(M, FALSE)
|
||||
return
|
||||
|
||||
if(latejoin_trackers.len)
|
||||
destination = pick(latejoin_trackers)
|
||||
destination.JoinPlayerHere(M, buckle)
|
||||
return
|
||||
|
||||
//bad mojo
|
||||
var/area/shuttle/arrival/A = GLOB.areas_by_type[/area/shuttle/arrival]
|
||||
if(A)
|
||||
//first check if we can find a chair
|
||||
var/obj/structure/chair/C = locate() in A
|
||||
if(C)
|
||||
C.JoinPlayerHere(M, buckle)
|
||||
return
|
||||
|
||||
//last hurrah
|
||||
var/list/avail = list()
|
||||
for(var/turf/T in A)
|
||||
if(!is_blocked_turf(T, TRUE))
|
||||
avail += T
|
||||
if(avail.len)
|
||||
destination = pick(avail)
|
||||
destination.JoinPlayerHere(M, FALSE)
|
||||
return
|
||||
|
||||
//pick an open spot on arrivals and dump em
|
||||
var/list/arrivals_turfs = shuffle(get_area_turfs(/area/shuttle/arrival))
|
||||
if(arrivals_turfs.len)
|
||||
for(var/turf/T in arrivals_turfs)
|
||||
if(!is_blocked_turf(T, TRUE))
|
||||
T.JoinPlayerHere(M, FALSE)
|
||||
return
|
||||
//last chance, pick ANY spot on arrivals and dump em
|
||||
destination = arrivals_turfs[1]
|
||||
destination.JoinPlayerHere(M, FALSE)
|
||||
else
|
||||
var/msg = "Unable to send mob [M] to late join!"
|
||||
message_admins(msg)
|
||||
CRASH(msg)
|
||||
|
||||
/datum/controller/subsystem/job/proc/equip_loadout(mob/dead/new_player/N, mob/living/M, equipbackpackstuff)
|
||||
var/mob/the_mob = N
|
||||
if(!the_mob)
|
||||
the_mob = M // cause this doesn't get assigned if player is a latejoiner
|
||||
if(the_mob.client && the_mob.client.prefs && (the_mob.client.prefs.chosen_gear && the_mob.client.prefs.chosen_gear.len))
|
||||
if(!ishuman(M))//no silicons allowed
|
||||
return
|
||||
for(var/i in the_mob.client.prefs.chosen_gear)
|
||||
var/datum/gear/G = i
|
||||
G = GLOB.loadout_items[slot_to_string(initial(G.category))][initial(G.name)]
|
||||
if(!G)
|
||||
continue
|
||||
var/permitted = TRUE
|
||||
if(G.restricted_roles && G.restricted_roles.len && !(M.mind.assigned_role in G.restricted_roles))
|
||||
permitted = FALSE
|
||||
if(G.donoritem && !G.donator_ckey_check(the_mob.client.ckey))
|
||||
permitted = FALSE
|
||||
if(!equipbackpackstuff && G.category == SLOT_IN_BACKPACK)//snowflake check since plopping stuff in the backpack doesnt work for pre-job equip loadout stuffs
|
||||
permitted = FALSE
|
||||
if(equipbackpackstuff && G.category != SLOT_IN_BACKPACK)//ditto
|
||||
permitted = FALSE
|
||||
if(!permitted)
|
||||
continue
|
||||
var/obj/item/I = new G.path
|
||||
if(!M.equip_to_slot_if_possible(I, G.category, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // If the job's dresscode compliant, try to put it in its slot, first
|
||||
if(iscarbon(M))
|
||||
var/mob/living/carbon/C = M
|
||||
var/obj/item/storage/backpack/B = C.back
|
||||
if(!B || !SEND_SIGNAL(B, COMSIG_TRY_STORAGE_INSERT, I, null, TRUE, TRUE)) // Otherwise, try to put it in the backpack, for carbons.
|
||||
I.forceMove(get_turf(C))
|
||||
else if(!M.equip_to_slot_if_possible(I, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // Otherwise, try to put it in the backpack
|
||||
I.forceMove(get_turf(M)) // If everything fails, just put it on the floor under the mob.
|
||||
|
||||
/datum/controller/subsystem/job/proc/FreeRole(rank)
|
||||
if(!rank)
|
||||
return
|
||||
var/datum/job/job = GetJob(rank)
|
||||
if(!job)
|
||||
return FALSE
|
||||
job.current_positions = max(0, job.current_positions - 1)
|
||||
|
||||
///////////////////////////////////
|
||||
//Keeps track of all living heads//
|
||||
///////////////////////////////////
|
||||
/datum/controller/subsystem/job/proc/get_living_heads()
|
||||
. = list()
|
||||
for(var/mob/living/carbon/human/player in GLOB.alive_mob_list)
|
||||
if(player.stat != DEAD && player.mind && (player.mind.assigned_role in GLOB.command_positions))
|
||||
. |= player.mind
|
||||
|
||||
|
||||
////////////////////////////
|
||||
//Keeps track of all heads//
|
||||
////////////////////////////
|
||||
/datum/controller/subsystem/job/proc/get_all_heads()
|
||||
. = list()
|
||||
for(var/i in GLOB.mob_list)
|
||||
var/mob/player = i
|
||||
if(player.mind && (player.mind.assigned_role in GLOB.command_positions))
|
||||
. |= player.mind
|
||||
|
||||
//////////////////////////////////////////////
|
||||
//Keeps track of all living security members//
|
||||
//////////////////////////////////////////////
|
||||
/datum/controller/subsystem/job/proc/get_living_sec()
|
||||
. = list()
|
||||
for(var/mob/living/carbon/human/player in GLOB.carbon_list)
|
||||
if(player.stat != DEAD && player.mind && (player.mind.assigned_role in GLOB.security_positions))
|
||||
. |= player.mind
|
||||
|
||||
////////////////////////////////////////
|
||||
//Keeps track of all security members//
|
||||
////////////////////////////////////////
|
||||
/datum/controller/subsystem/job/proc/get_all_sec()
|
||||
. = list()
|
||||
for(var/mob/living/carbon/human/player in GLOB.carbon_list)
|
||||
if(player.mind && (player.mind.assigned_role in GLOB.security_positions))
|
||||
. |= player.mind
|
||||
|
||||
/datum/controller/subsystem/job/proc/JobDebug(message)
|
||||
log_job_debug(message)
|
||||
@@ -0,0 +1,122 @@
|
||||
SUBSYSTEM_DEF(jukeboxes)
|
||||
name = "Jukeboxes"
|
||||
wait = 5
|
||||
var/list/songs = list()
|
||||
var/list/activejukeboxes = list()
|
||||
var/list/freejukeboxchannels = list()
|
||||
|
||||
/datum/track
|
||||
var/song_name = "generic"
|
||||
var/song_path = null
|
||||
var/song_length = 0
|
||||
var/song_beat = 0
|
||||
var/song_associated_id = null
|
||||
|
||||
/datum/track/New(name, path, length, beat, assocID)
|
||||
song_name = name
|
||||
song_path = path
|
||||
song_length = length
|
||||
song_beat = beat
|
||||
song_associated_id = assocID
|
||||
|
||||
/datum/controller/subsystem/jukeboxes/proc/addjukebox(obj/jukebox, datum/track/T, jukefalloff = 1)
|
||||
if(!istype(T))
|
||||
CRASH("[src] tried to play a song with a nonexistant track")
|
||||
var/channeltoreserve = pick(freejukeboxchannels)
|
||||
if(!channeltoreserve)
|
||||
return FALSE
|
||||
freejukeboxchannels -= channeltoreserve
|
||||
var/list/youvegotafreejukebox = list(T, channeltoreserve, jukebox, jukefalloff)
|
||||
activejukeboxes.len++
|
||||
activejukeboxes[activejukeboxes.len] = youvegotafreejukebox
|
||||
|
||||
//Due to changes in later versions of 512, SOUND_UPDATE no longer properly plays audio when a file is defined in the sound datum. As such, we are now required to init the audio before we can actually do anything with it.
|
||||
//Downsides to this? This means that you can *only* hear the jukebox audio if you were present on the server when it started playing, and it means that it's now impossible to add loops to the jukebox track list.
|
||||
var/sound/song_to_init = sound(T.song_path)
|
||||
song_to_init.status = SOUND_MUTE
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(!M.client)
|
||||
continue
|
||||
if(!(M.client.prefs.toggles & SOUND_INSTRUMENTS))
|
||||
continue
|
||||
|
||||
M.playsound_local(M, null, 100, channel = youvegotafreejukebox[2], S = song_to_init)
|
||||
return activejukeboxes.len
|
||||
|
||||
/datum/controller/subsystem/jukeboxes/proc/removejukebox(IDtoremove)
|
||||
if(islist(activejukeboxes[IDtoremove]))
|
||||
var/jukechannel = activejukeboxes[IDtoremove][2]
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(!M.client)
|
||||
continue
|
||||
M.stop_sound_channel(jukechannel)
|
||||
freejukeboxchannels |= jukechannel
|
||||
activejukeboxes.Cut(IDtoremove, IDtoremove+1)
|
||||
return TRUE
|
||||
else
|
||||
CRASH("Tried to remove jukebox with invalid ID")
|
||||
|
||||
/datum/controller/subsystem/jukeboxes/proc/findjukeboxindex(obj/jukebox)
|
||||
if(activejukeboxes.len)
|
||||
for(var/list/jukeinfo in activejukeboxes)
|
||||
if(jukebox in jukeinfo)
|
||||
return activejukeboxes.Find(jukeinfo)
|
||||
return FALSE
|
||||
|
||||
/datum/controller/subsystem/jukeboxes/Initialize()
|
||||
var/list/tracks = flist("config/jukebox_music/sounds/")
|
||||
for(var/S in tracks)
|
||||
var/datum/track/T = new()
|
||||
T.song_path = file("config/jukebox_music/sounds/[S]")
|
||||
var/list/L = splittext(S,"+")
|
||||
T.song_name = L[1]
|
||||
T.song_length = text2num(L[2])
|
||||
T.song_beat = text2num(L[3])
|
||||
T.song_associated_id = L[4]
|
||||
songs |= T
|
||||
for(var/i in CHANNEL_JUKEBOX_START to CHANNEL_JUKEBOX)
|
||||
freejukeboxchannels |= i
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/jukeboxes/fire()
|
||||
if(!activejukeboxes.len)
|
||||
return
|
||||
for(var/list/jukeinfo in activejukeboxes)
|
||||
if(!jukeinfo.len)
|
||||
EXCEPTION("Active jukebox without any associated metadata.")
|
||||
continue
|
||||
var/datum/track/juketrack = jukeinfo[1]
|
||||
if(!istype(juketrack))
|
||||
EXCEPTION("Invalid jukebox track datum.")
|
||||
continue
|
||||
var/obj/jukebox = jukeinfo[3]
|
||||
if(!istype(jukebox))
|
||||
EXCEPTION("Nonexistant or invalid object associated with jukebox.")
|
||||
continue
|
||||
var/sound/song_played = sound(juketrack.song_path)
|
||||
var/area/currentarea = get_area(jukebox)
|
||||
var/turf/currentturf = get_turf(jukebox)
|
||||
var/list/hearerscache = hearers(7, jukebox)
|
||||
|
||||
song_played.falloff = jukeinfo[4]
|
||||
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(!M.client)
|
||||
continue
|
||||
if(!(M.client.prefs.toggles & SOUND_INSTRUMENTS))
|
||||
M.stop_sound_channel(jukeinfo[2])
|
||||
continue
|
||||
|
||||
var/inrange = FALSE
|
||||
if(jukebox.z == M.z) //todo - expand this to work with mining planet z-levels when robust jukebox audio gets merged to master
|
||||
song_played.status = SOUND_UPDATE
|
||||
if(get_area(M) == currentarea)
|
||||
inrange = TRUE
|
||||
else if(M in hearerscache)
|
||||
inrange = TRUE
|
||||
else
|
||||
song_played.status = SOUND_MUTE | SOUND_UPDATE //Setting volume = 0 doesn't let the sound properties update at all, which is lame.
|
||||
|
||||
M.playsound_local(currentturf, null, 100, channel = jukeinfo[2], S = song_played, envwet = (inrange ? -250 : 0), envdry = (inrange ? 0 : -10000))
|
||||
CHECK_TICK
|
||||
return
|
||||
@@ -0,0 +1,527 @@
|
||||
SUBSYSTEM_DEF(mapping)
|
||||
name = "Mapping"
|
||||
init_order = INIT_ORDER_MAPPING
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
var/list/nuke_tiles = list()
|
||||
var/list/nuke_threats = list()
|
||||
|
||||
var/datum/map_config/config
|
||||
var/datum/map_config/next_map_config
|
||||
|
||||
var/list/map_templates = list()
|
||||
|
||||
var/list/ruins_templates = list()
|
||||
var/list/space_ruins_templates = list()
|
||||
var/list/lava_ruins_templates = list()
|
||||
var/datum/space_level/isolated_ruins_z //Created on demand during ruin loading.
|
||||
|
||||
var/list/shuttle_templates = list()
|
||||
var/list/shelter_templates = list()
|
||||
|
||||
var/list/areas_in_z = list()
|
||||
|
||||
var/loading_ruins = FALSE
|
||||
var/list/turf/unused_turfs = list() //Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs)
|
||||
var/list/datum/turf_reservations //list of turf reservations
|
||||
var/list/used_turfs = list() //list of turf = datum/turf_reservation
|
||||
|
||||
var/list/reservation_ready = list()
|
||||
var/clearing_reserved_turfs = FALSE
|
||||
|
||||
// Z-manager stuff
|
||||
var/station_start // should only be used for maploading-related tasks
|
||||
var/space_levels_so_far = 0
|
||||
var/list/z_list
|
||||
var/datum/space_level/transit
|
||||
var/datum/space_level/empty_space
|
||||
var/num_of_res_levels = 1
|
||||
|
||||
//dlete dis once #39770 is resolved
|
||||
/datum/controller/subsystem/mapping/proc/HACK_LoadMapConfig()
|
||||
if(!config)
|
||||
#ifdef FORCE_MAP
|
||||
config = load_map_config(FORCE_MAP)
|
||||
#else
|
||||
config = load_map_config(error_if_missing = FALSE)
|
||||
#endif
|
||||
|
||||
/datum/controller/subsystem/mapping/Initialize(timeofday)
|
||||
HACK_LoadMapConfig()
|
||||
if(initialized)
|
||||
return
|
||||
if(config.defaulted)
|
||||
var/old_config = config
|
||||
config = global.config.defaultmap
|
||||
if(!config || config.defaulted)
|
||||
to_chat(world, "<span class='boldannounce'>Unable to load next or default map config, defaulting to Box Station</span>")
|
||||
config = old_config
|
||||
GLOB.year_integer += config.year_offset
|
||||
GLOB.announcertype = (config.announcertype == "standard" ? (prob(1) ? "medibot" : "classic") : config.announcertype)
|
||||
loadWorld()
|
||||
repopulate_sorted_areas()
|
||||
process_teleport_locs() //Sets up the wizard teleport locations
|
||||
preloadTemplates()
|
||||
#ifndef LOWMEMORYMODE
|
||||
// Create space ruin levels
|
||||
while (space_levels_so_far < config.space_ruin_levels)
|
||||
++space_levels_so_far
|
||||
add_new_zlevel("Empty Area [space_levels_so_far]", ZTRAITS_SPACE)
|
||||
// and one level with no ruins
|
||||
for (var/i in 1 to config.space_empty_levels)
|
||||
++space_levels_so_far
|
||||
empty_space = add_new_zlevel("Empty Area [space_levels_so_far]", list(ZTRAIT_LINKAGE = CROSSLINKED))
|
||||
// and the transit level
|
||||
transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE))
|
||||
|
||||
// Pick a random away mission.
|
||||
if(CONFIG_GET(flag/roundstart_away))
|
||||
createRandomZlevel()
|
||||
|
||||
|
||||
// Generate mining ruins
|
||||
loading_ruins = TRUE
|
||||
var/list/lava_ruins = levels_by_trait(ZTRAIT_LAVA_RUINS)
|
||||
if (lava_ruins.len)
|
||||
seedRuins(lava_ruins, CONFIG_GET(number/lavaland_budget), /area/lavaland/surface/outdoors/unexplored, lava_ruins_templates)
|
||||
for (var/lava_z in lava_ruins)
|
||||
spawn_rivers(lava_z)
|
||||
|
||||
// Generate deep space ruins
|
||||
var/list/space_ruins = levels_by_trait(ZTRAIT_SPACE_RUINS)
|
||||
if (space_ruins.len)
|
||||
seedRuins(space_ruins, CONFIG_GET(number/space_budget), /area/space, space_ruins_templates)
|
||||
loading_ruins = FALSE
|
||||
#endif
|
||||
repopulate_sorted_areas()
|
||||
// Set up Z-level transitions.
|
||||
setup_map_transitions()
|
||||
generate_station_area_list()
|
||||
initialize_reserved_level(transit.z_value)
|
||||
return ..()
|
||||
|
||||
/* Nuke threats, for making the blue tiles on the station go RED
|
||||
Used by the AI doomsday and the self destruct nuke.
|
||||
*/
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/wipe_reservations(wipe_safety_delay = 100)
|
||||
if(clearing_reserved_turfs || !initialized) //in either case this is just not needed.
|
||||
return
|
||||
clearing_reserved_turfs = TRUE
|
||||
SSshuttle.transit_requesters.Cut()
|
||||
message_admins("Clearing dynamic reservation space.")
|
||||
var/list/obj/docking_port/mobile/in_transit = list()
|
||||
for(var/i in SSshuttle.transit)
|
||||
var/obj/docking_port/stationary/transit/T = i
|
||||
if(!istype(T))
|
||||
continue
|
||||
in_transit[T] = T.get_docked()
|
||||
var/go_ahead = world.time + wipe_safety_delay
|
||||
if(in_transit.len)
|
||||
message_admins("Shuttles in transit detected. Attempting to fast travel. Timeout is [wipe_safety_delay/10] seconds.")
|
||||
var/list/cleared = list()
|
||||
for(var/i in in_transit)
|
||||
INVOKE_ASYNC(src, .proc/safety_clear_transit_dock, i, in_transit[i], cleared)
|
||||
UNTIL((go_ahead < world.time) || (cleared.len == in_transit.len))
|
||||
do_wipe_turf_reservations()
|
||||
clearing_reserved_turfs = FALSE
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/safety_clear_transit_dock(obj/docking_port/stationary/transit/T, obj/docking_port/mobile/M, list/returning)
|
||||
M.setTimer(0)
|
||||
var/error = M.initiate_docking(M.destination, M.preferred_direction)
|
||||
if(!error)
|
||||
returning += M
|
||||
qdel(T, TRUE)
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/add_nuke_threat(datum/nuke)
|
||||
nuke_threats[nuke] = TRUE
|
||||
check_nuke_threats()
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/remove_nuke_threat(datum/nuke)
|
||||
nuke_threats -= nuke
|
||||
check_nuke_threats()
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/check_nuke_threats()
|
||||
for(var/datum/d in nuke_threats)
|
||||
if(!istype(d) || QDELETED(d))
|
||||
nuke_threats -= d
|
||||
|
||||
for(var/N in nuke_tiles)
|
||||
var/turf/open/floor/circuit/C = N
|
||||
C.update_icon()
|
||||
|
||||
/datum/controller/subsystem/mapping/Recover()
|
||||
flags |= SS_NO_INIT
|
||||
initialized = SSmapping.initialized
|
||||
map_templates = SSmapping.map_templates
|
||||
ruins_templates = SSmapping.ruins_templates
|
||||
space_ruins_templates = SSmapping.space_ruins_templates
|
||||
lava_ruins_templates = SSmapping.lava_ruins_templates
|
||||
shuttle_templates = SSmapping.shuttle_templates
|
||||
shelter_templates = SSmapping.shelter_templates
|
||||
unused_turfs = SSmapping.unused_turfs
|
||||
turf_reservations = SSmapping.turf_reservations
|
||||
used_turfs = SSmapping.used_turfs
|
||||
|
||||
config = SSmapping.config
|
||||
next_map_config = SSmapping.next_map_config
|
||||
|
||||
clearing_reserved_turfs = SSmapping.clearing_reserved_turfs
|
||||
|
||||
z_list = SSmapping.z_list
|
||||
|
||||
#define INIT_ANNOUNCE(X) to_chat(world, "<span class='boldannounce'>[X]</span>"); log_world(X)
|
||||
/datum/controller/subsystem/mapping/proc/LoadGroup(list/errorList, name, path, files, list/traits, list/default_traits, silent = FALSE)
|
||||
. = list()
|
||||
var/start_time = REALTIMEOFDAY
|
||||
|
||||
if (!islist(files)) // handle single-level maps
|
||||
files = list(files)
|
||||
|
||||
// check that the total z count of all maps matches the list of traits
|
||||
var/total_z = 0
|
||||
var/list/parsed_maps = list()
|
||||
for (var/file in files)
|
||||
var/full_path = "_maps/[path]/[file]"
|
||||
var/datum/parsed_map/pm = new(file(full_path))
|
||||
var/bounds = pm?.bounds
|
||||
if (!bounds)
|
||||
errorList |= full_path
|
||||
continue
|
||||
parsed_maps[pm] = total_z // save the start Z of this file
|
||||
total_z += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1
|
||||
|
||||
if (!length(traits)) // null or empty - default
|
||||
for (var/i in 1 to total_z)
|
||||
traits += list(default_traits)
|
||||
else if (total_z != traits.len) // mismatch
|
||||
INIT_ANNOUNCE("WARNING: [traits.len] trait sets specified for [total_z] z-levels in [path]!")
|
||||
if (total_z < traits.len) // ignore extra traits
|
||||
traits.Cut(total_z + 1)
|
||||
while (total_z > traits.len) // fall back to defaults on extra levels
|
||||
traits += list(default_traits)
|
||||
|
||||
// preload the relevant space_level datums
|
||||
var/start_z = world.maxz + 1
|
||||
var/i = 0
|
||||
for (var/level in traits)
|
||||
add_new_zlevel("[name][i ? " [i + 1]" : ""]", level)
|
||||
++i
|
||||
|
||||
// load the maps
|
||||
for (var/P in parsed_maps)
|
||||
var/datum/parsed_map/pm = P
|
||||
if (!pm.load(1, 1, start_z + parsed_maps[P], no_changeturf = TRUE))
|
||||
errorList |= pm.original_path
|
||||
if(!silent)
|
||||
INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!")
|
||||
return parsed_maps
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/loadWorld()
|
||||
//if any of these fail, something has gone horribly, HORRIBLY, wrong
|
||||
var/list/FailedZs = list()
|
||||
|
||||
// ensure we have space_level datums for compiled-in maps
|
||||
InitializeDefaultZLevels()
|
||||
|
||||
// load the station
|
||||
station_start = world.maxz + 1
|
||||
INIT_ANNOUNCE("Loading [config.map_name]...")
|
||||
LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION)
|
||||
|
||||
if(SSdbcore.Connect())
|
||||
var/datum/DBQuery/query_round_map_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET map_name = '[config.map_name]' WHERE id = [GLOB.round_id]")
|
||||
query_round_map_name.Execute()
|
||||
qdel(query_round_map_name)
|
||||
|
||||
#ifndef LOWMEMORYMODE
|
||||
// TODO: remove this when the DB is prepared for the z-levels getting reordered
|
||||
while (world.maxz < (5 - 1) && space_levels_so_far < config.space_ruin_levels)
|
||||
++space_levels_so_far
|
||||
add_new_zlevel("Empty Area [space_levels_so_far]", ZTRAITS_SPACE)
|
||||
|
||||
// load mining
|
||||
if(config.minetype == "lavaland")
|
||||
LoadGroup(FailedZs, "Lavaland", "map_files/Mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND)
|
||||
else if (!isnull(config.minetype))
|
||||
INIT_ANNOUNCE("WARNING: An unknown minetype '[config.minetype]' was set! This is being ignored! Update the maploader code!")
|
||||
#endif
|
||||
|
||||
if(LAZYLEN(FailedZs)) //but seriously, unless the server's filesystem is messed up this will never happen
|
||||
var/msg = "RED ALERT! The following map files failed to load: [FailedZs[1]]"
|
||||
if(FailedZs.len > 1)
|
||||
for(var/I in 2 to FailedZs.len)
|
||||
msg += ", [FailedZs[I]]"
|
||||
msg += ". Yell at your server host!"
|
||||
INIT_ANNOUNCE(msg)
|
||||
#undef INIT_ANNOUNCE
|
||||
|
||||
GLOBAL_LIST_EMPTY(the_station_areas)
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/generate_station_area_list()
|
||||
var/list/station_areas_blacklist = typecacheof(list(/area/space, /area/mine, /area/ruin, /area/asteroid/nearstation))
|
||||
for(var/area/A in world)
|
||||
if (is_type_in_typecache(A, station_areas_blacklist))
|
||||
continue
|
||||
if (!A.contents.len || !A.unique)
|
||||
continue
|
||||
var/turf/picked = A.contents[1]
|
||||
if (is_station_level(picked.z))
|
||||
GLOB.the_station_areas += A.type
|
||||
|
||||
if(!GLOB.the_station_areas.len)
|
||||
log_world("ERROR: Station areas list failed to generate!")
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/maprotate()
|
||||
var/players = GLOB.clients.len
|
||||
var/list/mapvotes = list()
|
||||
//count votes
|
||||
var/amv = CONFIG_GET(flag/allow_map_voting)
|
||||
if(amv)
|
||||
for (var/client/c in GLOB.clients)
|
||||
var/vote = c.prefs.preferred_map
|
||||
if (!vote)
|
||||
if (global.config.defaultmap)
|
||||
mapvotes[global.config.defaultmap.map_name] += 1
|
||||
continue
|
||||
mapvotes[vote] += 1
|
||||
else
|
||||
for(var/M in global.config.maplist)
|
||||
mapvotes[M] = 1
|
||||
|
||||
//filter votes
|
||||
for (var/map in mapvotes)
|
||||
if (!map)
|
||||
mapvotes.Remove(map)
|
||||
if (!(map in global.config.maplist))
|
||||
mapvotes.Remove(map)
|
||||
continue
|
||||
var/datum/map_config/VM = global.config.maplist[map]
|
||||
if (!VM)
|
||||
mapvotes.Remove(map)
|
||||
continue
|
||||
if (VM.voteweight <= 0)
|
||||
mapvotes.Remove(map)
|
||||
continue
|
||||
if (VM.config_min_users > 0 && players < VM.config_min_users)
|
||||
mapvotes.Remove(map)
|
||||
continue
|
||||
if (VM.config_max_users > 0 && players > VM.config_max_users)
|
||||
mapvotes.Remove(map)
|
||||
continue
|
||||
|
||||
if(amv)
|
||||
mapvotes[map] = mapvotes[map]*VM.voteweight
|
||||
|
||||
var/pickedmap = pickweight(mapvotes)
|
||||
if (!pickedmap)
|
||||
return
|
||||
var/datum/map_config/VM = global.config.maplist[pickedmap]
|
||||
message_admins("Randomly rotating map to [VM.map_name]")
|
||||
. = changemap(VM)
|
||||
if (. && VM.map_name != config.map_name)
|
||||
to_chat(world, "<span class='boldannounce'>Map rotation has chosen [VM.map_name] for next round!</span>")
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/changemap(var/datum/map_config/VM)
|
||||
if(!VM.MakeNextMap())
|
||||
next_map_config = load_map_config(default_to_box = TRUE)
|
||||
message_admins("Failed to set new map with next_map.json for [VM.map_name]! Using default as backup!")
|
||||
return
|
||||
|
||||
next_map_config = VM
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/preloadTemplates(path = "_maps/templates/") //see master controller setup
|
||||
var/list/filelist = flist(path)
|
||||
for(var/map in filelist)
|
||||
var/datum/map_template/T = new(path = "[path][map]", rename = "[map]")
|
||||
map_templates[T.name] = T
|
||||
|
||||
preloadRuinTemplates()
|
||||
preloadShuttleTemplates()
|
||||
preloadShelterTemplates()
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/preloadRuinTemplates()
|
||||
// Still supporting bans by filename
|
||||
var/list/banned = generateMapList("[global.config.directory]/lavaruinblacklist.txt")
|
||||
banned += generateMapList("[global.config.directory]/spaceruinblacklist.txt")
|
||||
|
||||
for(var/item in sortList(subtypesof(/datum/map_template/ruin), /proc/cmp_ruincost_priority))
|
||||
var/datum/map_template/ruin/ruin_type = item
|
||||
// screen out the abstract subtypes
|
||||
if(!initial(ruin_type.id))
|
||||
continue
|
||||
var/datum/map_template/ruin/R = new ruin_type()
|
||||
|
||||
if(banned.Find(R.mappath))
|
||||
continue
|
||||
|
||||
map_templates[R.name] = R
|
||||
ruins_templates[R.name] = R
|
||||
|
||||
if(istype(R, /datum/map_template/ruin/lavaland))
|
||||
lava_ruins_templates[R.name] = R
|
||||
else if(istype(R, /datum/map_template/ruin/space))
|
||||
space_ruins_templates[R.name] = R
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/preloadShuttleTemplates()
|
||||
var/list/unbuyable = generateMapList("[global.config.directory]/unbuyableshuttles.txt")
|
||||
|
||||
for(var/item in subtypesof(/datum/map_template/shuttle))
|
||||
var/datum/map_template/shuttle/shuttle_type = item
|
||||
if(!(initial(shuttle_type.suffix)))
|
||||
continue
|
||||
|
||||
var/datum/map_template/shuttle/S = new shuttle_type()
|
||||
if(unbuyable.Find(S.mappath))
|
||||
S.can_be_bought = FALSE
|
||||
|
||||
shuttle_templates[S.shuttle_id] = S
|
||||
map_templates[S.shuttle_id] = S
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/preloadShelterTemplates()
|
||||
for(var/item in subtypesof(/datum/map_template/shelter))
|
||||
var/datum/map_template/shelter/shelter_type = item
|
||||
if(!(initial(shelter_type.mappath)))
|
||||
continue
|
||||
var/datum/map_template/shelter/S = new shelter_type()
|
||||
|
||||
shelter_templates[S.shelter_id] = S
|
||||
map_templates[S.shelter_id] = S
|
||||
|
||||
//Manual loading of away missions.
|
||||
/client/proc/admin_away()
|
||||
set name = "Load Away Mission"
|
||||
set category = "Fun"
|
||||
|
||||
if(!holder ||!check_rights(R_FUN))
|
||||
return
|
||||
|
||||
|
||||
if(!GLOB.the_gateway)
|
||||
if(alert("There's no home gateway on the station. You sure you want to continue ?", "Uh oh", "Yes", "No") != "Yes")
|
||||
return
|
||||
|
||||
var/list/possible_options = GLOB.potentialRandomZlevels + "Custom"
|
||||
var/away_name
|
||||
var/datum/space_level/away_level
|
||||
|
||||
var/answer = input("What kind ? ","Away") as null|anything in possible_options
|
||||
switch(answer)
|
||||
if("Custom")
|
||||
var/mapfile = input("Pick file:", "File") as null|file
|
||||
if(!mapfile)
|
||||
return
|
||||
away_name = "[mapfile] custom"
|
||||
to_chat(usr,"<span class='notice'>Loading [away_name]...</span>")
|
||||
var/datum/map_template/template = new(mapfile, "Away Mission")
|
||||
away_level = template.load_new_z()
|
||||
else
|
||||
if(answer in GLOB.potentialRandomZlevels)
|
||||
away_name = answer
|
||||
to_chat(usr,"<span class='notice'>Loading [away_name]...</span>")
|
||||
var/datum/map_template/template = new(away_name, "Away Mission")
|
||||
away_level = template.load_new_z()
|
||||
else
|
||||
return
|
||||
|
||||
message_admins("Admin [key_name_admin(usr)] has loaded [away_name] away mission.")
|
||||
log_admin("Admin [key_name(usr)] has loaded [away_name] away mission.")
|
||||
if(!away_level)
|
||||
message_admins("Loading [away_name] failed!")
|
||||
return
|
||||
|
||||
|
||||
if(GLOB.the_gateway)
|
||||
//Link any found away gate with station gate
|
||||
var/obj/machinery/gateway/centeraway/new_gate
|
||||
for(var/obj/machinery/gateway/centeraway/G in GLOB.machines)
|
||||
if(G.z == away_level.z_value) //I'll have to refactor gateway shitcode before multi-away support.
|
||||
new_gate = G
|
||||
break
|
||||
//Link station gate with away gate and remove wait time.
|
||||
GLOB.the_gateway.awaygate = new_gate
|
||||
GLOB.the_gateway.wait = world.time
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/RequestBlockReservation(width, height, z, type = /datum/turf_reservation, turf_type_override, border_type_override)
|
||||
UNTIL((!z || reservation_ready["[z]"]) && !clearing_reserved_turfs)
|
||||
var/datum/turf_reservation/reserve = new type
|
||||
if(turf_type_override)
|
||||
reserve.turf_type = turf_type_override
|
||||
if(border_type_override)
|
||||
reserve.borderturf = border_type_override
|
||||
if(!z)
|
||||
for(var/i in levels_by_trait(ZTRAIT_RESERVED))
|
||||
if(reserve.Reserve(width, height, i))
|
||||
return reserve
|
||||
//If we didn't return at this point, theres a good chance we ran out of room on the exisiting reserved z levels, so lets try a new one
|
||||
num_of_res_levels += 1
|
||||
var/datum/space_level/newReserved = add_new_zlevel("Transit/Reserved [num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE))
|
||||
initialize_reserved_level(newReserved.z_value)
|
||||
if(reserve.Reserve(width, height, newReserved.z_value))
|
||||
return reserve
|
||||
else
|
||||
if(!level_trait(z, ZTRAIT_RESERVED))
|
||||
qdel(reserve)
|
||||
return
|
||||
else
|
||||
if(reserve.Reserve(width, height, z))
|
||||
return reserve
|
||||
QDEL_NULL(reserve)
|
||||
|
||||
//This is not for wiping reserved levels, use wipe_reservations() for that.
|
||||
/datum/controller/subsystem/mapping/proc/initialize_reserved_level(z)
|
||||
UNTIL(!clearing_reserved_turfs) //regardless, lets add a check just in case.
|
||||
clearing_reserved_turfs = TRUE //This operation will likely clear any existing reservations, so lets make sure nothing tries to make one while we're doing it.
|
||||
if(!level_trait(z,ZTRAIT_RESERVED))
|
||||
clearing_reserved_turfs = FALSE
|
||||
CRASH("Invalid z level prepared for reservations.")
|
||||
var/turf/A = get_turf(locate(SHUTTLE_TRANSIT_BORDER,SHUTTLE_TRANSIT_BORDER,z))
|
||||
var/turf/B = get_turf(locate(world.maxx - SHUTTLE_TRANSIT_BORDER,world.maxy - SHUTTLE_TRANSIT_BORDER,z))
|
||||
var/block = block(A, B)
|
||||
for(var/t in block)
|
||||
// No need to empty() these, because it's world init and they're
|
||||
// already /turf/open/space/basic.
|
||||
var/turf/T = t
|
||||
T.flags_1 |= UNUSED_RESERVATION_TURF_1
|
||||
unused_turfs["[z]"] = block
|
||||
reservation_ready["[z]"] = TRUE
|
||||
clearing_reserved_turfs = FALSE
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs)
|
||||
for(var/i in turfs)
|
||||
var/turf/T = i
|
||||
T.empty(RESERVED_TURF_TYPE, RESERVED_TURF_TYPE, null, TRUE)
|
||||
LAZYINITLIST(unused_turfs["[T.z]"])
|
||||
unused_turfs["[T.z]"] |= T
|
||||
T.flags_1 |= UNUSED_RESERVATION_TURF_1
|
||||
GLOB.areas_by_type[world.area].contents += T
|
||||
CHECK_TICK
|
||||
|
||||
//DO NOT CALL THIS PROC DIRECTLY, CALL wipe_reservations().
|
||||
/datum/controller/subsystem/mapping/proc/do_wipe_turf_reservations()
|
||||
UNTIL(initialized) //This proc is for AFTER init, before init turf reservations won't even exist and using this will likely break things.
|
||||
for(var/i in turf_reservations)
|
||||
var/datum/turf_reservation/TR = i
|
||||
if(!QDELETED(TR))
|
||||
qdel(TR, TRUE)
|
||||
UNSETEMPTY(turf_reservations)
|
||||
var/list/clearing = list()
|
||||
for(var/l in unused_turfs) //unused_turfs is a assoc list by z = list(turfs)
|
||||
if(islist(unused_turfs[l]))
|
||||
clearing |= unused_turfs[l]
|
||||
clearing |= used_turfs //used turfs is an associative list, BUT, reserve_turfs() can still handle it. If the code above works properly, this won't even be needed as the turfs would be freed already.
|
||||
unused_turfs.Cut()
|
||||
used_turfs.Cut()
|
||||
reserve_turfs(clearing)
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/reg_in_areas_in_z(list/areas)
|
||||
for(var/B in areas)
|
||||
var/area/A = B
|
||||
A.reg_in_areas_in_z()
|
||||
|
||||
/datum/controller/subsystem/mapping/proc/get_isolated_ruin_z()
|
||||
if(!isolated_ruins_z)
|
||||
isolated_ruins_z = add_new_zlevel("Isolated Ruins/Reserved", list(ZTRAIT_RESERVED = TRUE, ZTRAIT_ISOLATED_RUINS = TRUE))
|
||||
initialize_reserved_level(isolated_ruins_z.z_value)
|
||||
return isolated_ruins_z.z_value
|
||||
@@ -0,0 +1,34 @@
|
||||
SUBSYSTEM_DEF(npcpool)
|
||||
name = "NPC Pool"
|
||||
flags = SS_KEEP_TIMING | SS_NO_INIT
|
||||
priority = FIRE_PRIORITY_NPC
|
||||
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
|
||||
|
||||
var/list/currentrun = list()
|
||||
|
||||
/datum/controller/subsystem/npcpool/stat_entry()
|
||||
var/list/activelist = GLOB.simple_animals[AI_ON]
|
||||
..("NPCS:[activelist.len]")
|
||||
|
||||
/datum/controller/subsystem/npcpool/fire(resumed = FALSE)
|
||||
|
||||
if (!resumed)
|
||||
var/list/activelist = GLOB.simple_animals[AI_ON]
|
||||
src.currentrun = activelist.Copy()
|
||||
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/currentrun = src.currentrun
|
||||
|
||||
while(currentrun.len)
|
||||
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
|
||||
--currentrun.len
|
||||
|
||||
if(!SA.ckey && !SA.notransform)
|
||||
if(SA.stat != DEAD)
|
||||
SA.handle_automated_movement()
|
||||
if(SA.stat != DEAD)
|
||||
SA.handle_automated_action()
|
||||
if(SA.stat != DEAD)
|
||||
SA.handle_automated_speech()
|
||||
if (MC_TICK_CHECK)
|
||||
return
|
||||
@@ -0,0 +1,203 @@
|
||||
SUBSYSTEM_DEF(pai)
|
||||
name = "pAI"
|
||||
|
||||
flags = SS_NO_INIT|SS_NO_FIRE
|
||||
|
||||
var/list/candidates = list()
|
||||
var/ghost_spam = FALSE
|
||||
var/spam_delay = 100
|
||||
var/list/pai_card_list = list()
|
||||
|
||||
/datum/controller/subsystem/pai/Topic(href, href_list[])
|
||||
if(href_list["download"])
|
||||
var/datum/paiCandidate/candidate = locate(href_list["candidate"]) in candidates
|
||||
var/obj/item/paicard/card = locate(href_list["device"]) in pai_card_list
|
||||
if(card.pai)
|
||||
return
|
||||
if(istype(card, /obj/item/paicard) && istype(candidate, /datum/paiCandidate))
|
||||
if(check_ready(candidate) != candidate)
|
||||
return FALSE
|
||||
var/mob/living/silicon/pai/pai = new(card)
|
||||
if(!candidate.name)
|
||||
pai.name = pick(GLOB.ninja_names)
|
||||
else
|
||||
pai.name = candidate.name
|
||||
pai.real_name = pai.name
|
||||
pai.key = candidate.key
|
||||
|
||||
card.setPersonality(pai)
|
||||
|
||||
SSticker.mode.update_cult_icons_removed(card.pai.mind)
|
||||
|
||||
candidates -= candidate
|
||||
usr << browse(null, "window=findPai")
|
||||
|
||||
if(href_list["new"])
|
||||
var/datum/paiCandidate/candidate = locate(href_list["candidate"]) in candidates
|
||||
var/option = href_list["option"]
|
||||
var/t = ""
|
||||
|
||||
switch(option)
|
||||
if("name")
|
||||
t = input("Enter a name for your pAI", "pAI Name", candidate.name) as text
|
||||
if(t)
|
||||
candidate.name = copytext(sanitize(t),1,MAX_NAME_LEN)
|
||||
if("desc")
|
||||
t = input("Enter a description for your pAI", "pAI Description", candidate.description) as message
|
||||
if(t)
|
||||
candidate.description = copytext(sanitize(t),1,MAX_MESSAGE_LEN)
|
||||
if("role")
|
||||
t = input("Enter a role for your pAI", "pAI Role", candidate.role) as text
|
||||
if(t)
|
||||
candidate.role = copytext(sanitize(t),1,MAX_MESSAGE_LEN)
|
||||
if("ooc")
|
||||
t = input("Enter any OOC comments", "pAI OOC Comments", candidate.comments) as message
|
||||
if(t)
|
||||
candidate.comments = copytext(sanitize(t),1,MAX_MESSAGE_LEN)
|
||||
if("save")
|
||||
candidate.savefile_save(usr)
|
||||
if("load")
|
||||
candidate.savefile_load(usr)
|
||||
//In case people have saved unsanitized stuff.
|
||||
if(candidate.name)
|
||||
candidate.name = copytext(sanitize(candidate.name),1,MAX_NAME_LEN)
|
||||
if(candidate.description)
|
||||
candidate.description = copytext(sanitize(candidate.description),1,MAX_MESSAGE_LEN)
|
||||
if(candidate.role)
|
||||
candidate.role = copytext(sanitize(candidate.role),1,MAX_MESSAGE_LEN)
|
||||
if(candidate.comments)
|
||||
candidate.comments = copytext(sanitize(candidate.comments),1,MAX_MESSAGE_LEN)
|
||||
|
||||
if("submit")
|
||||
if(isobserver(usr))
|
||||
var/mob/dead/observer/O = usr
|
||||
if(!O.can_reenter_round())
|
||||
return FALSE
|
||||
if(candidate)
|
||||
candidate.ready = 1
|
||||
for(var/obj/item/paicard/p in pai_card_list)
|
||||
if(!p.pai)
|
||||
p.alertUpdate()
|
||||
usr << browse(null, "window=paiRecruit")
|
||||
return
|
||||
recruitWindow(usr)
|
||||
|
||||
/datum/controller/subsystem/pai/proc/recruitWindow(mob/M)
|
||||
var/datum/paiCandidate/candidate
|
||||
for(var/datum/paiCandidate/c in candidates)
|
||||
if(c.key == M.key)
|
||||
candidate = c
|
||||
if(!candidate)
|
||||
candidate = new /datum/paiCandidate()
|
||||
candidate.key = M.key
|
||||
candidates.Add(candidate)
|
||||
|
||||
|
||||
var/dat = ""
|
||||
dat += {"
|
||||
<style type="text/css">
|
||||
|
||||
p.top {
|
||||
background-color: #AAAAAA; color: black;
|
||||
}
|
||||
|
||||
tr.d0 td {
|
||||
background-color: #CC9999; color: black;
|
||||
}
|
||||
tr.d1 td {
|
||||
background-color: #9999CC; color: black;
|
||||
}
|
||||
</style>
|
||||
"}
|
||||
|
||||
dat += "<p class=\"top\">Please configure your pAI personality's options. Remember, what you enter here could determine whether or not the user requesting a personality chooses you!</p>"
|
||||
dat += "<table>"
|
||||
dat += "<tr class=\"d0\"><td>Name:</td><td>[candidate.name]</td></tr>"
|
||||
dat += "<tr class=\"d1\"><td><a href='byond://?src=[REF(src)];option=name;new=1;candidate=[REF(candidate)]'>\[Edit\]</a></td><td>What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI.</td></tr>"
|
||||
|
||||
dat += "<tr class=\"d0\"><td>Description:</td><td>[candidate.description]</td></tr>"
|
||||
dat += "<tr class=\"d1\"><td><a href='byond://?src=[REF(src)];option=desc;new=1;candidate=[REF(candidate)]'>\[Edit\]</a></td><td>What sort of pAI you typically play; your mannerisms, your quirks, etc. This can be as sparse or as detailed as you like.</td></tr>"
|
||||
|
||||
dat += "<tr class=\"d0\"><td>Preferred Role:</td><td>[candidate.role]</td></tr>"
|
||||
dat += "<tr class=\"d1\"><td><a href='byond://?src=[REF(src)];option=role;new=1;candidate=[REF(candidate)]'>\[Edit\]</a></td><td>Do you like to partner with sneaky social ninjas? Like to help security hunt down thugs? Enjoy watching an engineer's back while he saves the station yet again? This doesn't have to be limited to just station jobs. Pretty much any general descriptor for what you'd like to be doing works here.</td></tr>"
|
||||
|
||||
dat += "<tr class=\"d0\"><td>OOC Comments:</td><td>[candidate.comments]</td></tr>"
|
||||
dat += "<tr class=\"d1\"><td><a href='byond://?src=[REF(src)];option=ooc;new=1;candidate=[REF(candidate)]'>\[Edit\]</a></td><td>Anything you'd like to address specifically to the player reading this in an OOC manner. \"I prefer more serious RP.\", \"I'm still learning the interface!\", etc. Feel free to leave this blank if you want.</td></tr>"
|
||||
|
||||
dat += "</table>"
|
||||
|
||||
dat += "<br>"
|
||||
dat += "<h3><a href='byond://?src=[REF(src)];option=submit;new=1;candidate=[REF(candidate)]'>Submit Personality</a></h3><br>"
|
||||
dat += "<a href='byond://?src=[REF(src)];option=save;new=1;candidate=[REF(candidate)]'>Save Personality</a><br>"
|
||||
dat += "<a href='byond://?src=[REF(src)];option=load;new=1;candidate=[REF(candidate)]'>Load Personality</a><br>"
|
||||
|
||||
M << browse(dat, "window=paiRecruit")
|
||||
|
||||
/datum/controller/subsystem/pai/proc/spam_again()
|
||||
ghost_spam = FALSE
|
||||
|
||||
/datum/controller/subsystem/pai/proc/check_ready(var/datum/paiCandidate/C)
|
||||
if(!C.ready)
|
||||
return FALSE
|
||||
for(var/mob/dead/observer/O in GLOB.player_list)
|
||||
if(O.key == C.key)
|
||||
return C
|
||||
return FALSE
|
||||
|
||||
/datum/controller/subsystem/pai/proc/findPAI(obj/item/paicard/p, mob/user)
|
||||
if(!ghost_spam)
|
||||
ghost_spam = TRUE
|
||||
for(var/mob/dead/observer/G in GLOB.player_list)
|
||||
if(!G.key || !G.client)
|
||||
continue
|
||||
if(!(ROLE_PAI in G.client.prefs.be_special))
|
||||
continue
|
||||
if(!G.can_reenter_round()) // this should use notify_ghosts() instead one day.
|
||||
return FALSE
|
||||
to_chat(G, "<span class='ghostalert'>[user] is requesting a pAI personality! Use the pAI button to submit yourself as one.</span>")
|
||||
addtimer(CALLBACK(src, .proc/spam_again), spam_delay)
|
||||
var/list/available = list()
|
||||
for(var/datum/paiCandidate/c in SSpai.candidates)
|
||||
available.Add(check_ready(c))
|
||||
var/dat = ""
|
||||
|
||||
dat += {"
|
||||
<style type="text/css">
|
||||
|
||||
p.top {
|
||||
background-color: #AAAAAA; color: black;
|
||||
}
|
||||
|
||||
tr.d0 td {
|
||||
background-color: #CC9999; color: black;
|
||||
}
|
||||
tr.d1 td {
|
||||
background-color: #9999CC; color: black;
|
||||
}
|
||||
tr.d2 td {
|
||||
background-color: #99CC99; color: black;
|
||||
}
|
||||
</style>
|
||||
"}
|
||||
dat += "<p class=\"top\">Requesting AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.</p>"
|
||||
|
||||
dat += "<table>"
|
||||
|
||||
for(var/datum/paiCandidate/c in available)
|
||||
dat += "<tr class=\"d0\"><td>Name:</td><td>[c.name]</td></tr>"
|
||||
dat += "<tr class=\"d1\"><td>Description:</td><td>[c.description]</td></tr>"
|
||||
dat += "<tr class=\"d0\"><td>Preferred Role:</td><td>[c.role]</td></tr>"
|
||||
dat += "<tr class=\"d1\"><td>OOC Comments:</td><td>[c.comments]</td></tr>"
|
||||
dat += "<tr class=\"d2\"><td><a href='byond://?src=[REF(src)];download=1;candidate=[REF(c)];device=[REF(p)]'>\[Download [c.name]\]</a></td><td></td></tr>"
|
||||
|
||||
dat += "</table>"
|
||||
|
||||
user << browse(dat, "window=findPai")
|
||||
|
||||
/datum/paiCandidate
|
||||
var/name
|
||||
var/key
|
||||
var/description
|
||||
var/role
|
||||
var/comments
|
||||
var/ready = 0
|
||||
@@ -0,0 +1,437 @@
|
||||
#define FILE_ANTAG_REP "data/AntagReputation.json"
|
||||
#define MAX_RECENT_MAP_RECORD 10
|
||||
|
||||
SUBSYSTEM_DEF(persistence)
|
||||
name = "Persistence"
|
||||
init_order = INIT_ORDER_PERSISTENCE
|
||||
flags = SS_NO_FIRE
|
||||
var/list/satchel_blacklist = list() //this is a typecache
|
||||
var/list/new_secret_satchels = list() //these are objects
|
||||
var/list/old_secret_satchels = list()
|
||||
|
||||
var/list/obj/structure/chisel_message/chisel_messages = list()
|
||||
var/list/saved_messages = list()
|
||||
var/list/saved_modes = list(1,2,3)
|
||||
var/list/saved_maps
|
||||
var/list/saved_trophies = list()
|
||||
var/list/spawned_objects = list()
|
||||
var/list/antag_rep = list()
|
||||
var/list/antag_rep_change = list()
|
||||
var/list/picture_logging_information = list()
|
||||
var/list/obj/structure/sign/picture_frame/photo_frames
|
||||
var/list/obj/item/storage/photo_album/photo_albums
|
||||
|
||||
/datum/controller/subsystem/persistence/Initialize()
|
||||
LoadSatchels()
|
||||
LoadPoly()
|
||||
LoadChiselMessages()
|
||||
LoadTrophies()
|
||||
LoadRecentModes()
|
||||
LoadRecentMaps()
|
||||
LoadPhotoPersistence()
|
||||
if(CONFIG_GET(flag/use_antag_rep))
|
||||
LoadAntagReputation()
|
||||
LoadRandomizedRecipes()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadSatchels()
|
||||
var/placed_satchel = 0
|
||||
var/path
|
||||
if(fexists("data/npc_saves/SecretSatchels.sav")) //legacy conversion. Will only ever run once.
|
||||
var/savefile/secret_satchels = new /savefile("data/npc_saves/SecretSatchels.sav")
|
||||
for(var/map in secret_satchels)
|
||||
var/json_file = file("data/npc_saves/SecretSatchels[map].json")
|
||||
var/list/legacy_secret_satchels = splittext(secret_satchels[map],"#")
|
||||
var/list/satchels = list()
|
||||
for(var/i=1,i<=legacy_secret_satchels.len,i++)
|
||||
var/satchel_string = legacy_secret_satchels[i]
|
||||
var/list/chosen_satchel = splittext(satchel_string,"|")
|
||||
if(chosen_satchel.len == 3)
|
||||
var/list/data = list()
|
||||
data["x"] = text2num(chosen_satchel[1])
|
||||
data["y"] = text2num(chosen_satchel[2])
|
||||
data["saved_obj"] = chosen_satchel[3]
|
||||
satchels += list(data)
|
||||
var/list/file_data = list()
|
||||
file_data["data"] = satchels
|
||||
WRITE_FILE(json_file, json_encode(file_data))
|
||||
fdel("data/npc_saves/SecretSatchels.sav")
|
||||
|
||||
var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json")
|
||||
var/list/json = list()
|
||||
if(fexists(json_file))
|
||||
json = json_decode(file2text(json_file))
|
||||
|
||||
old_secret_satchels = json["data"]
|
||||
var/obj/item/storage/backpack/satchel/flat/F
|
||||
if(old_secret_satchels && old_secret_satchels.len >= 10) //guards against low drop pools assuring that one player cannot reliably find his own gear.
|
||||
var/pos = rand(1, old_secret_satchels.len)
|
||||
F = new()
|
||||
old_secret_satchels.Cut(pos, pos+1 % old_secret_satchels.len)
|
||||
F.x = old_secret_satchels[pos]["x"]
|
||||
F.y = old_secret_satchels[pos]["y"]
|
||||
F.z = SSmapping.station_start
|
||||
path = text2path(old_secret_satchels[pos]["saved_obj"])
|
||||
|
||||
if(F)
|
||||
if(isfloorturf(F.loc) && !isplatingturf(F.loc))
|
||||
F.hide(1)
|
||||
if(ispath(path))
|
||||
var/spawned_item = new path(F)
|
||||
spawned_objects[spawned_item] = TRUE
|
||||
placed_satchel++
|
||||
var/free_satchels = 0
|
||||
for(var/turf/T in shuffle(block(locate(TRANSITIONEDGE,TRANSITIONEDGE,SSmapping.station_start), locate(world.maxx-TRANSITIONEDGE,world.maxy-TRANSITIONEDGE,SSmapping.station_start)))) //Nontrivially expensive but it's roundstart only
|
||||
if(isfloorturf(T) && !isplatingturf(T))
|
||||
new /obj/item/storage/backpack/satchel/flat/secret(T)
|
||||
free_satchels++
|
||||
if((free_satchels + placed_satchel) == 10) //ten tiles, more than enough to kill anything that moves
|
||||
break
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadPoly()
|
||||
for(var/mob/living/simple_animal/parrot/Poly/P in GLOB.alive_mob_list)
|
||||
twitterize(P.speech_buffer, "polytalk")
|
||||
break //Who's been duping the bird?!
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadChiselMessages()
|
||||
var/list/saved_messages = list()
|
||||
if(fexists("data/npc_saves/ChiselMessages.sav")) //legacy compatability to convert old format to new
|
||||
var/savefile/chisel_messages_sav = new /savefile("data/npc_saves/ChiselMessages.sav")
|
||||
var/saved_json
|
||||
chisel_messages_sav[SSmapping.config.map_name] >> saved_json
|
||||
if(!saved_json)
|
||||
return
|
||||
saved_messages = json_decode(saved_json)
|
||||
fdel("data/npc_saves/ChiselMessages.sav")
|
||||
else
|
||||
var/json_file = file("data/npc_saves/ChiselMessages[SSmapping.config.map_name].json")
|
||||
if(!fexists(json_file))
|
||||
return
|
||||
var/list/json = json_decode(file2text(json_file))
|
||||
|
||||
if(!json)
|
||||
return
|
||||
saved_messages = json["data"]
|
||||
|
||||
for(var/item in saved_messages)
|
||||
if(!islist(item))
|
||||
continue
|
||||
|
||||
var/xvar = item["x"]
|
||||
var/yvar = item["y"]
|
||||
var/zvar = item["z"]
|
||||
|
||||
if(!xvar || !yvar || !zvar)
|
||||
continue
|
||||
|
||||
var/turf/T = locate(xvar, yvar, zvar)
|
||||
if(!isturf(T))
|
||||
continue
|
||||
|
||||
if(locate(/obj/structure/chisel_message) in T)
|
||||
continue
|
||||
|
||||
var/obj/structure/chisel_message/M = new(T)
|
||||
|
||||
if(!QDELETED(M))
|
||||
M.unpack(item)
|
||||
|
||||
log_world("Loaded [saved_messages.len] engraved messages on map [SSmapping.config.map_name]")
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadTrophies()
|
||||
if(fexists("data/npc_saves/TrophyItems.sav")) //legacy compatability to convert old format to new
|
||||
var/savefile/S = new /savefile("data/npc_saves/TrophyItems.sav")
|
||||
var/saved_json
|
||||
S >> saved_json
|
||||
if(!saved_json)
|
||||
return
|
||||
saved_trophies = json_decode(saved_json)
|
||||
fdel("data/npc_saves/TrophyItems.sav")
|
||||
else
|
||||
var/json_file = file("data/npc_saves/TrophyItems.json")
|
||||
if(!fexists(json_file))
|
||||
return
|
||||
var/list/json = json_decode(file2text(json_file))
|
||||
if(!json)
|
||||
return
|
||||
saved_trophies = json["data"]
|
||||
SetUpTrophies(saved_trophies.Copy())
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadRecentModes()
|
||||
var/json_file = file("data/RecentModes.json")
|
||||
if(!fexists(json_file))
|
||||
return
|
||||
var/list/json = json_decode(file2text(json_file))
|
||||
if(!json)
|
||||
return
|
||||
saved_modes = json["data"]
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadRecentMaps()
|
||||
var/json_file = file("data/RecentMaps.json")
|
||||
if(!fexists(json_file))
|
||||
return
|
||||
var/list/json = json_decode(file2text(json_file))
|
||||
if(!json)
|
||||
return
|
||||
saved_maps = json["maps"]
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadAntagReputation()
|
||||
var/json = file2text(FILE_ANTAG_REP)
|
||||
if(!json)
|
||||
var/json_file = file(FILE_ANTAG_REP)
|
||||
if(!fexists(json_file))
|
||||
WARNING("Failed to load antag reputation. File likely corrupt.")
|
||||
return
|
||||
return
|
||||
antag_rep = json_decode(json)
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/SetUpTrophies(list/trophy_items)
|
||||
for(var/A in GLOB.trophy_cases)
|
||||
var/obj/structure/displaycase/trophy/T = A
|
||||
if (T.showpiece)
|
||||
continue
|
||||
T.added_roundstart = TRUE
|
||||
|
||||
var/trophy_data = pick_n_take(trophy_items)
|
||||
|
||||
if(!islist(trophy_data))
|
||||
continue
|
||||
|
||||
var/list/chosen_trophy = trophy_data
|
||||
|
||||
if(!chosen_trophy || isemptylist(chosen_trophy)) //Malformed
|
||||
continue
|
||||
|
||||
var/path = text2path(chosen_trophy["path"]) //If the item no longer exist, this returns null
|
||||
if(!path)
|
||||
continue
|
||||
|
||||
T.showpiece = new /obj/item/showpiece_dummy(T, path)
|
||||
T.trophy_message = chosen_trophy["message"]
|
||||
T.placer_key = chosen_trophy["placer_key"]
|
||||
T.update_icon()
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/CollectData()
|
||||
CollectChiselMessages()
|
||||
CollectSecretSatchels()
|
||||
CollectTrophies()
|
||||
CollectRoundtype()
|
||||
RecordMaps()
|
||||
SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION.
|
||||
if(CONFIG_GET(flag/use_antag_rep))
|
||||
CollectAntagReputation()
|
||||
SaveRandomizedRecipes()
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/GetPhotoAlbums()
|
||||
var/album_path = file("data/photo_albums.json")
|
||||
if(fexists(album_path))
|
||||
return json_decode(file2text(album_path))
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/GetPhotoFrames()
|
||||
var/frame_path = file("data/photo_frames.json")
|
||||
if(fexists(frame_path))
|
||||
return json_decode(file2text(frame_path))
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadPhotoPersistence()
|
||||
var/album_path = file("data/photo_albums.json")
|
||||
var/frame_path = file("data/photo_frames.json")
|
||||
if(fexists(album_path))
|
||||
var/list/json = json_decode(file2text(album_path))
|
||||
if(json.len)
|
||||
for(var/i in photo_albums)
|
||||
var/obj/item/storage/photo_album/A = i
|
||||
if(!A.persistence_id)
|
||||
continue
|
||||
if(json[A.persistence_id])
|
||||
A.populate_from_id_list(json[A.persistence_id])
|
||||
|
||||
if(fexists(frame_path))
|
||||
var/list/json = json_decode(file2text(frame_path))
|
||||
if(json.len)
|
||||
for(var/i in photo_frames)
|
||||
var/obj/structure/sign/picture_frame/PF = i
|
||||
if(!PF.persistence_id)
|
||||
continue
|
||||
if(json[PF.persistence_id])
|
||||
PF.load_from_id(json[PF.persistence_id])
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/SavePhotoPersistence()
|
||||
var/album_path = file("data/photo_albums.json")
|
||||
var/frame_path = file("data/photo_frames.json")
|
||||
|
||||
var/list/frame_json = list()
|
||||
var/list/album_json = list()
|
||||
|
||||
if(fexists(album_path))
|
||||
album_json = json_decode(file2text(album_path))
|
||||
fdel(album_path)
|
||||
|
||||
for(var/i in photo_albums)
|
||||
var/obj/item/storage/photo_album/A = i
|
||||
if(!istype(A) || !A.persistence_id)
|
||||
continue
|
||||
var/list/L = A.get_picture_id_list()
|
||||
album_json[A.persistence_id] = L
|
||||
|
||||
album_json = json_encode(album_json)
|
||||
|
||||
WRITE_FILE(album_path, album_json)
|
||||
|
||||
if(fexists(frame_path))
|
||||
frame_json = json_decode(file2text(frame_path))
|
||||
fdel(frame_path)
|
||||
|
||||
for(var/i in photo_frames)
|
||||
var/obj/structure/sign/picture_frame/F = i
|
||||
if(!istype(F) || !F.persistence_id)
|
||||
continue
|
||||
frame_json[F.persistence_id] = F.get_photo_id()
|
||||
|
||||
frame_json = json_encode(frame_json)
|
||||
|
||||
WRITE_FILE(frame_path, frame_json)
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/CollectSecretSatchels()
|
||||
satchel_blacklist = typecacheof(list(/obj/item/stack/tile/plasteel, /obj/item/crowbar))
|
||||
var/list/satchels_to_add = list()
|
||||
for(var/A in new_secret_satchels)
|
||||
var/obj/item/storage/backpack/satchel/flat/F = A
|
||||
if(QDELETED(F) || F.z != SSmapping.station_start || F.invisibility != INVISIBILITY_MAXIMUM)
|
||||
continue
|
||||
var/list/savable_obj = list()
|
||||
for(var/obj/O in F)
|
||||
if(is_type_in_typecache(O, satchel_blacklist) || (O.flags_1 & ADMIN_SPAWNED_1))
|
||||
continue
|
||||
if(O.persistence_replacement)
|
||||
savable_obj += O.persistence_replacement
|
||||
else
|
||||
savable_obj += O.type
|
||||
if(isemptylist(savable_obj))
|
||||
continue
|
||||
var/list/data = list()
|
||||
data["x"] = F.x
|
||||
data["y"] = F.y
|
||||
data["saved_obj"] = pick(savable_obj)
|
||||
satchels_to_add += list(data)
|
||||
|
||||
var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json")
|
||||
var/list/file_data = list()
|
||||
fdel(json_file)
|
||||
file_data["data"] = old_secret_satchels + satchels_to_add
|
||||
WRITE_FILE(json_file, json_encode(file_data))
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/CollectChiselMessages()
|
||||
var/json_file = file("data/npc_saves/ChiselMessages[SSmapping.config.map_name].json")
|
||||
|
||||
for(var/obj/structure/chisel_message/M in chisel_messages)
|
||||
saved_messages += list(M.pack())
|
||||
|
||||
log_world("Saved [saved_messages.len] engraved messages on map [SSmapping.config.map_name]")
|
||||
var/list/file_data = list()
|
||||
file_data["data"] = saved_messages
|
||||
fdel(json_file)
|
||||
WRITE_FILE(json_file, json_encode(file_data))
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/SaveChiselMessage(obj/structure/chisel_message/M)
|
||||
saved_messages += list(M.pack()) // dm eats one list
|
||||
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/CollectTrophies()
|
||||
var/json_file = file("data/npc_saves/TrophyItems.json")
|
||||
var/list/file_data = list()
|
||||
file_data["data"] = remove_duplicate_trophies(saved_trophies)
|
||||
fdel(json_file)
|
||||
WRITE_FILE(json_file, json_encode(file_data))
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/remove_duplicate_trophies(list/trophies)
|
||||
var/list/ukeys = list()
|
||||
. = list()
|
||||
for(var/trophy in trophies)
|
||||
var/tkey = "[trophy["path"]]-[trophy["message"]]"
|
||||
if(ukeys[tkey])
|
||||
continue
|
||||
else
|
||||
. += list(trophy)
|
||||
ukeys[tkey] = TRUE
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/SaveTrophy(obj/structure/displaycase/trophy/T)
|
||||
if(!T.added_roundstart && T.showpiece)
|
||||
var/list/data = list()
|
||||
data["path"] = T.showpiece.type
|
||||
data["message"] = T.trophy_message
|
||||
data["placer_key"] = T.placer_key
|
||||
saved_trophies += list(data)
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/CollectRoundtype()
|
||||
saved_modes[3] = saved_modes[2]
|
||||
saved_modes[2] = saved_modes[1]
|
||||
saved_modes[1] = SSticker.mode.config_tag
|
||||
var/json_file = file("data/RecentModes.json")
|
||||
var/list/file_data = list()
|
||||
file_data["data"] = saved_modes
|
||||
fdel(json_file)
|
||||
WRITE_FILE(json_file, json_encode(file_data))
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/RecordMaps()
|
||||
saved_maps = saved_maps?.len ? list("[SSmapping.config.map_name]") | saved_maps : list("[SSmapping.config.map_name]")
|
||||
var/json_file = file("data/RecentMaps.json")
|
||||
var/list/file_data = list()
|
||||
file_data["maps"] = saved_maps
|
||||
fdel(json_file)
|
||||
WRITE_FILE(json_file, json_encode(file_data))
|
||||
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/CollectAntagReputation()
|
||||
var/ANTAG_REP_MAXIMUM = CONFIG_GET(number/antag_rep_maximum)
|
||||
|
||||
for(var/p_ckey in antag_rep_change)
|
||||
// var/start = antag_rep[p_ckey]
|
||||
antag_rep[p_ckey] = max(0, min(antag_rep[p_ckey]+antag_rep_change[p_ckey], ANTAG_REP_MAXIMUM))
|
||||
|
||||
// WARNING("AR_DEBUG: [p_ckey]: Committed [antag_rep_change[p_ckey]] reputation, going from [start] to [antag_rep[p_ckey]]")
|
||||
|
||||
antag_rep_change = list()
|
||||
|
||||
fdel(FILE_ANTAG_REP)
|
||||
text2file(json_encode(antag_rep), FILE_ANTAG_REP)
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadRandomizedRecipes()
|
||||
var/json_file = file("data/RandomizedChemRecipes.json")
|
||||
var/json
|
||||
if(fexists(json_file))
|
||||
json = json_decode(file2text(json_file))
|
||||
|
||||
for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized))
|
||||
var/datum/chemical_reaction/randomized/R = new randomized_type
|
||||
var/loaded = FALSE
|
||||
if(R.persistent && json)
|
||||
var/list/recipe_data = json[R.id]
|
||||
if(recipe_data && R.LoadOldRecipe(recipe_data) && (daysSince(R.created) <= R.persistence_period))
|
||||
loaded = TRUE
|
||||
if(!loaded) //We do not have information for whatever reason, just generate new one
|
||||
R.GenerateRecipe()
|
||||
|
||||
if(!R.HasConflicts()) //Might want to try again if conflicts happened in the future.
|
||||
add_chemical_reaction(R)
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/SaveRandomizedRecipes()
|
||||
var/json_file = file("data/RandomizedChemRecipes.json")
|
||||
var/list/file_data = list()
|
||||
|
||||
//asert globchems done
|
||||
for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized))
|
||||
var/datum/chemical_reaction/randomized/R = randomized_type
|
||||
R = get_chemical_reaction(initial(R.id)) //ew, would be nice to add some simple tracking
|
||||
if(R && R.persistent && R.id)
|
||||
var/recipe_data = list()
|
||||
recipe_data["timestamp"] = R.created
|
||||
recipe_data["required_reagents"] = R.required_reagents
|
||||
recipe_data["required_catalysts"] = R.required_catalysts
|
||||
recipe_data["required_temp"] = R.required_temp
|
||||
recipe_data["is_cold_recipe"] = R.is_cold_recipe
|
||||
recipe_data["results"] = R.results
|
||||
recipe_data["required_container"] = "[R.required_container]"
|
||||
file_data["[R.id]"] = recipe_data
|
||||
|
||||
fdel(json_file)
|
||||
WRITE_FILE(json_file, json_encode(file_data))
|
||||
@@ -0,0 +1,530 @@
|
||||
|
||||
SUBSYSTEM_DEF(research)
|
||||
name = "Research"
|
||||
priority = FIRE_PRIORITY_RESEARCH
|
||||
wait = 10
|
||||
init_order = INIT_ORDER_RESEARCH
|
||||
//TECHWEB STATIC
|
||||
var/list/techweb_nodes = list() //associative id = node datum
|
||||
var/list/techweb_designs = list() //associative id = node datum
|
||||
var/list/datum/techweb/techwebs = list()
|
||||
var/datum/techweb/science/science_tech
|
||||
var/datum/techweb/admin/admin_tech
|
||||
var/datum/techweb_node/error_node/error_node //These two are what you get if a node/design is deleted and somehow still stored in a console.
|
||||
var/datum/design/error_design/error_design
|
||||
|
||||
//ERROR LOGGING
|
||||
var/list/invalid_design_ids = list() //associative id = number of times
|
||||
var/list/invalid_node_ids = list() //associative id = number of times
|
||||
var/list/invalid_node_boost = list() //associative id = error message
|
||||
|
||||
var/list/obj/machinery/rnd/server/servers = list()
|
||||
|
||||
var/list/techweb_nodes_starting = list() //associative id = TRUE
|
||||
var/list/techweb_categories = list() //category name = list(node.id = TRUE)
|
||||
var/list/techweb_boost_items = list() //associative double-layer path = list(id = list(point_type = point_discount))
|
||||
var/list/techweb_nodes_hidden = list() //Node ids that should be hidden by default.
|
||||
var/list/techweb_point_items = list( //path = list(point type = value)
|
||||
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 5000), // Cit three more anomalys anomalys
|
||||
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 7500),
|
||||
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000),
|
||||
// - Slime Extracts! -
|
||||
/obj/item/slime_extract/grey = list(TECHWEB_POINT_TYPE_GENERIC = 500), // Adds in slime core deconing
|
||||
/obj/item/slime_extract/metal = list(TECHWEB_POINT_TYPE_GENERIC = 750),
|
||||
/obj/item/slime_extract/purple = list(TECHWEB_POINT_TYPE_GENERIC = 750),
|
||||
/obj/item/slime_extract/orange = list(TECHWEB_POINT_TYPE_GENERIC = 750),
|
||||
/obj/item/slime_extract/blue = list(TECHWEB_POINT_TYPE_GENERIC = 750),
|
||||
/obj/item/slime_extract/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 1000),
|
||||
/obj/item/slime_extract/silver = list(TECHWEB_POINT_TYPE_GENERIC = 1000),
|
||||
/obj/item/slime_extract/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 1000),
|
||||
/obj/item/slime_extract/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 1000),
|
||||
/obj/item/slime_extract/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 1250),
|
||||
/obj/item/slime_extract/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 1250),
|
||||
/obj/item/slime_extract/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 1250),
|
||||
/obj/item/slime_extract/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 1250),
|
||||
/obj/item/slime_extract/red = list(TECHWEB_POINT_TYPE_GENERIC = 1250),
|
||||
/obj/item/slime_extract/green = list(TECHWEB_POINT_TYPE_GENERIC = 1250),
|
||||
/obj/item/slime_extract/pink = list(TECHWEB_POINT_TYPE_GENERIC = 1250),
|
||||
/obj/item/slime_extract/gold = list(TECHWEB_POINT_TYPE_GENERIC = 1250),
|
||||
/obj/item/slime_extract/black = list(TECHWEB_POINT_TYPE_GENERIC = 1500),
|
||||
/obj/item/slime_extract/adamantine =list (TECHWEB_POINT_TYPE_GENERIC = 1500),
|
||||
/obj/item/slime_extract/oil = list(TECHWEB_POINT_TYPE_GENERIC = 1500),
|
||||
/obj/item/slime_extract/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 1500),
|
||||
/obj/item/slime_extract/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
// Reproductive - Crossbreading Cores! - (Grey Cores)
|
||||
/obj/item/slimecross/reproductive/grey = list(TECHWEB_POINT_TYPE_GENERIC = 1000),
|
||||
/obj/item/slimecross/reproductive/orange = list(TECHWEB_POINT_TYPE_GENERIC = 1500),
|
||||
/obj/item/slimecross/reproductive/purple = list(TECHWEB_POINT_TYPE_GENERIC = 1500),
|
||||
/obj/item/slimecross/reproductive/blue = list(TECHWEB_POINT_TYPE_GENERIC = 1500),
|
||||
/obj/item/slimecross/reproductive/metal = list(TECHWEB_POINT_TYPE_GENERIC = 1500),
|
||||
/obj/item/slimecross/reproductive/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 1750),
|
||||
/obj/item/slimecross/reproductive/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 1750),
|
||||
/obj/item/slimecross/reproductive/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 1750),
|
||||
/obj/item/slimecross/reproductive/silver = list(TECHWEB_POINT_TYPE_GENERIC = 1750),
|
||||
/obj/item/slimecross/reproductive/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 2000),
|
||||
/obj/item/slimecross/reproductive/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 2000),
|
||||
/obj/item/slimecross/reproductive/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 2000),
|
||||
/obj/item/slimecross/reproductive/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 2000),
|
||||
/obj/item/slimecross/reproductive/red = list(TECHWEB_POINT_TYPE_GENERIC = 2250),
|
||||
/obj/item/slimecross/reproductive/green = list(TECHWEB_POINT_TYPE_GENERIC = 2250),
|
||||
/obj/item/slimecross/reproductive/pink = list(TECHWEB_POINT_TYPE_GENERIC = 2250),
|
||||
/obj/item/slimecross/reproductive/gold = list(TECHWEB_POINT_TYPE_GENERIC = 2250),
|
||||
/obj/item/slimecross/reproductive/oil = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/reproductive/black = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/reproductive/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/reproductive/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/reproductive/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
// Burning - Crossbreading Cores! - (Orange Cores)
|
||||
/obj/item/slimecross/burning/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000),
|
||||
/obj/item/slimecross/burning/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/burning/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/burning/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/burning/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/burning/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/burning/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/burning/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/burning/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/burning/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/burning/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/burning/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/burning/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/burning/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/burning/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/burning/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/burning/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/burning/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/burning/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/burning/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/burning/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/burning/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
// Regenerative - Crossbreading Cores! - (Purple Cores)
|
||||
/obj/item/slimecross/regenerative/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000),
|
||||
/obj/item/slimecross/regenerative/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/regenerative/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/regenerative/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/regenerative/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/regenerative/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/regenerative/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/regenerative/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/regenerative/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/regenerative/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/regenerative/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/regenerative/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/regenerative/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/regenerative/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/regenerative/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/regenerative/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/regenerative/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/regenerative/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/regenerative/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/regenerative/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/regenerative/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/regenerative/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
// Stabilized - Crossbreading Cores! - (Blue Cores)
|
||||
/obj/item/slimecross/stabilized/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000),
|
||||
/obj/item/slimecross/stabilized/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/stabilized/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/stabilized/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/stabilized/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/stabilized/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/stabilized/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/stabilized/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/stabilized/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/stabilized/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/stabilized/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/stabilized/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/stabilized/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/stabilized/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/stabilized/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/stabilized/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/stabilized/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/stabilized/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/stabilized/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/stabilized/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/stabilized/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/stabilized/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
// Industrial - Crossbreading Cores! - (Metal Cores)
|
||||
/obj/item/slimecross/industrial/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000),
|
||||
/obj/item/slimecross/industrial/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/industrial/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/industrial/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/industrial/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/industrial/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/industrial/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/industrial/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/industrial/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/industrial/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/industrial/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/industrial/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/industrial/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/industrial/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/industrial/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/industrial/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/industrial/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/industrial/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/industrial/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/industrial/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/industrial/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/industrial/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
// Charged - Crossbreading Cores! - (Yellow Cores)
|
||||
/obj/item/slimecross/charged/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250),
|
||||
/obj/item/slimecross/charged/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/charged/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/charged/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/charged/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/charged/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/charged/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/charged/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/charged/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/charged/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/charged/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/charged/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/charged/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/charged/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/charged/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/charged/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/charged/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/charged/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/charged/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/charged/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/charged/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/charged/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
// Selfsustaining - Crossbreading Cores! - (Dark Purple Cores)
|
||||
/obj/item/slimecross/selfsustaining/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250),
|
||||
/obj/item/slimecross/selfsustaining/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/selfsustaining/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/selfsustaining/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/selfsustaining/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/selfsustaining/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/selfsustaining/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/selfsustaining/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/selfsustaining/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/selfsustaining/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/selfsustaining/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/selfsustaining/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/selfsustaining/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/selfsustaining/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/selfsustaining/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/selfsustaining/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/selfsustaining/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/selfsustaining/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/selfsustaining/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/selfsustaining/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/selfsustaining/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/selfsustaining/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
// Consuming - Crossbreading Cores! - (Sliver Cores)
|
||||
/obj/item/slimecross/consuming/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250),
|
||||
/obj/item/slimecross/consuming/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/consuming/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/consuming/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/consuming/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750),
|
||||
/obj/item/slimecross/consuming/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/consuming/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/consuming/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/consuming/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/consuming/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/consuming/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/consuming/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/consuming/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/consuming/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/consuming/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/consuming/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/consuming/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/consuming/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/consuming/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/consuming/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/consuming/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/consuming/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
// Prismatic - Crossbreading Cores! - (Pyrite Cores)
|
||||
/obj/item/slimecross/prismatic/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/prismatic/orange = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/prismatic/purple = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/prismatic/blue = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/prismatic/metal = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/prismatic/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/prismatic/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/prismatic/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/prismatic/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/prismatic/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/prismatic/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/prismatic/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/prismatic/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/prismatic/red = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/prismatic/green = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/prismatic/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/prismatic/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/prismatic/oil = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
/obj/item/slimecross/prismatic/black = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
/obj/item/slimecross/prismatic/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
/obj/item/slimecross/prismatic/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
/obj/item/slimecross/prismatic/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4250),
|
||||
// Recurring - Crossbreading Cores! - (Cerulean Cores)
|
||||
/obj/item/slimecross/recurring/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2500),
|
||||
/obj/item/slimecross/recurring/orange = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/recurring/purple = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/recurring/blue = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/recurring/metal = list(TECHWEB_POINT_TYPE_GENERIC = 3000),
|
||||
/obj/item/slimecross/recurring/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/recurring/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/recurring/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/recurring/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3250),
|
||||
/obj/item/slimecross/recurring/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/recurring/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/recurring/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/recurring/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3500),
|
||||
/obj/item/slimecross/recurring/red = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/recurring/green = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/recurring/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/recurring/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3750),
|
||||
/obj/item/slimecross/recurring/oil = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
/obj/item/slimecross/recurring/black = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
/obj/item/slimecross/recurring/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
/obj/item/slimecross/recurring/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 4000),
|
||||
/obj/item/slimecross/recurring/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4250)
|
||||
) // End of Cit changes
|
||||
var/list/errored_datums = list()
|
||||
var/list/point_types = list() //typecache style type = TRUE list
|
||||
//----------------------------------------------
|
||||
var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = 35) //citadel edit - techwebs nerf
|
||||
var/multiserver_calculation = FALSE
|
||||
var/last_income
|
||||
//^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^
|
||||
|
||||
//Aiming for 1.5 hours to max R&D
|
||||
//[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min]
|
||||
//Around 450000 points max???
|
||||
|
||||
/datum/controller/subsystem/research/Initialize()
|
||||
point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES
|
||||
initialize_all_techweb_designs()
|
||||
initialize_all_techweb_nodes()
|
||||
science_tech = new /datum/techweb/science
|
||||
admin_tech = new /datum/techweb/admin
|
||||
autosort_categories()
|
||||
error_design = new
|
||||
error_node = new
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/research/fire()
|
||||
var/list/bitcoins = list()
|
||||
if(multiserver_calculation)
|
||||
var/eff = calculate_server_coefficient()
|
||||
for(var/obj/machinery/rnd/server/miner in servers)
|
||||
var/list/result = (miner.mine()) //SLAVE AWAY, SLAVE.
|
||||
for(var/i in result)
|
||||
result[i] *= eff
|
||||
bitcoins[i] = bitcoins[i]? bitcoins[i] + result[i] : result[i]
|
||||
else
|
||||
for(var/obj/machinery/rnd/server/miner in servers)
|
||||
if(miner.working)
|
||||
bitcoins = single_server_income.Copy()
|
||||
break //Just need one to work.
|
||||
if (!isnull(last_income))
|
||||
var/income_time_difference = world.time - last_income
|
||||
science_tech.last_bitcoins = bitcoins // Doesn't take tick drift into account
|
||||
for(var/i in bitcoins)
|
||||
bitcoins[i] *= income_time_difference / 10
|
||||
science_tech.add_point_list(bitcoins)
|
||||
last_income = world.time
|
||||
|
||||
/datum/controller/subsystem/research/proc/calculate_server_coefficient() //Diminishing returns.
|
||||
var/amt = servers.len
|
||||
if(!amt)
|
||||
return 0
|
||||
var/coeff = 100
|
||||
coeff = sqrt(coeff / amt)
|
||||
return coeff
|
||||
|
||||
/datum/controller/subsystem/research/proc/autosort_categories()
|
||||
for(var/i in techweb_nodes)
|
||||
var/datum/techweb_node/I = techweb_nodes[i]
|
||||
if(techweb_categories[I.category])
|
||||
techweb_categories[I.category][I.id] = TRUE
|
||||
else
|
||||
techweb_categories[I.category] = list(I.id = TRUE)
|
||||
|
||||
/datum/controller/subsystem/research/proc/techweb_node_by_id(id)
|
||||
return techweb_nodes[id] || error_node
|
||||
|
||||
/datum/controller/subsystem/research/proc/techweb_design_by_id(id)
|
||||
return techweb_designs[id] || error_design
|
||||
|
||||
/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D)
|
||||
for(var/i in techweb_nodes)
|
||||
var/datum/techweb_node/TN = techwebs[i]
|
||||
TN.on_design_deletion(TN)
|
||||
for(var/i in techwebs)
|
||||
var/datum/techweb/T = i
|
||||
T.recalculate_nodes(TRUE)
|
||||
|
||||
/datum/controller/subsystem/research/proc/on_node_deletion(datum/techweb_node/TN)
|
||||
for(var/i in techweb_nodes)
|
||||
var/datum/techweb_node/TN2 = techwebs[i]
|
||||
TN2.on_node_deletion(TN)
|
||||
for(var/i in techwebs)
|
||||
var/datum/techweb/T = i
|
||||
T.recalculate_nodes(TRUE)
|
||||
|
||||
/datum/controller/subsystem/research/proc/initialize_all_techweb_nodes(clearall = FALSE)
|
||||
if(islist(techweb_nodes) && clearall)
|
||||
QDEL_LIST(techweb_nodes)
|
||||
if(islist(techweb_nodes_starting && clearall))
|
||||
techweb_nodes_starting.Cut()
|
||||
var/list/returned = list()
|
||||
for(var/path in subtypesof(/datum/techweb_node))
|
||||
var/datum/techweb_node/TN = path
|
||||
if(isnull(initial(TN.id)))
|
||||
continue
|
||||
TN = new path
|
||||
if(returned[initial(TN.id)])
|
||||
stack_trace("WARNING: Techweb node ID clash with ID [initial(TN.id)] detected! Path: [path]")
|
||||
errored_datums[TN] = initial(TN.id)
|
||||
continue
|
||||
returned[initial(TN.id)] = TN
|
||||
if(TN.starting_node)
|
||||
techweb_nodes_starting[TN.id] = TRUE
|
||||
for(var/id in techweb_nodes)
|
||||
var/datum/techweb_node/TN = techweb_nodes[id]
|
||||
TN.Initialize()
|
||||
techweb_nodes = returned
|
||||
if (!verify_techweb_nodes()) //Verify all nodes have ids and such.
|
||||
stack_trace("Invalid techweb nodes detected")
|
||||
calculate_techweb_nodes()
|
||||
calculate_techweb_boost_list()
|
||||
if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly.
|
||||
CRASH("Invalid techweb nodes detected")
|
||||
|
||||
/datum/controller/subsystem/research/proc/initialize_all_techweb_designs(clearall = FALSE)
|
||||
if(islist(techweb_designs) && clearall)
|
||||
QDEL_LIST(techweb_designs)
|
||||
var/list/returned = list()
|
||||
for(var/path in subtypesof(/datum/design))
|
||||
var/datum/design/DN = path
|
||||
if(isnull(initial(DN.id)))
|
||||
stack_trace("WARNING: Design with null ID detected. Build path: [initial(DN.build_path)]")
|
||||
continue
|
||||
else if(initial(DN.id) == DESIGN_ID_IGNORE)
|
||||
continue
|
||||
DN = new path
|
||||
if(returned[initial(DN.id)])
|
||||
stack_trace("WARNING: Design ID clash with ID [initial(DN.id)] detected! Path: [path]")
|
||||
errored_datums[DN] = initial(DN.id)
|
||||
continue
|
||||
returned[initial(DN.id)] = DN
|
||||
techweb_designs = returned
|
||||
verify_techweb_designs()
|
||||
|
||||
/datum/controller/subsystem/research/proc/verify_techweb_nodes()
|
||||
. = TRUE
|
||||
for(var/n in techweb_nodes)
|
||||
var/datum/techweb_node/N = techweb_nodes[n]
|
||||
if(!istype(N))
|
||||
WARNING("Invalid research node with ID [n] detected and removed.")
|
||||
techweb_nodes -= n
|
||||
research_node_id_error(n)
|
||||
. = FALSE
|
||||
for(var/p in N.prereq_ids)
|
||||
var/datum/techweb_node/P = techweb_nodes[p]
|
||||
if(!istype(P))
|
||||
WARNING("Invalid research prerequisite node with ID [p] detected in node [N.display_name]\[[N.id]\] removed.")
|
||||
N.prereq_ids -= p
|
||||
research_node_id_error(p)
|
||||
. = FALSE
|
||||
for(var/d in N.design_ids)
|
||||
var/datum/design/D = techweb_designs[d]
|
||||
if(!istype(D))
|
||||
WARNING("Invalid research design with ID [d] detected in node [N.display_name]\[[N.id]\] removed.")
|
||||
N.design_ids -= d
|
||||
design_id_error(d)
|
||||
. = FALSE
|
||||
for(var/u in N.unlock_ids)
|
||||
var/datum/techweb_node/U = techweb_nodes[u]
|
||||
if(!istype(U))
|
||||
WARNING("Invalid research unlock node with ID [u] detected in node [N.display_name]\[[N.id]\] removed.")
|
||||
N.unlock_ids -= u
|
||||
research_node_id_error(u)
|
||||
. = FALSE
|
||||
for(var/p in N.boost_item_paths)
|
||||
if(!ispath(p))
|
||||
N.boost_item_paths -= p
|
||||
WARNING("[p] is not a valid path.")
|
||||
node_boost_error(N.id, "[p] is not a valid path.")
|
||||
. = FALSE
|
||||
var/list/points = N.boost_item_paths[p]
|
||||
if(islist(points))
|
||||
for(var/i in points)
|
||||
if(!isnum(points[i]))
|
||||
WARNING("[points[i]] is not a valid number.")
|
||||
node_boost_error(N.id, "[points[i]] is not a valid number.")
|
||||
. = FALSE
|
||||
else if(!point_types[i])
|
||||
WARNING("[i] is not a valid point type.")
|
||||
node_boost_error(N.id, "[i] is not a valid point type.")
|
||||
. = FALSE
|
||||
else if(!isnull(points))
|
||||
N.boost_item_paths -= p
|
||||
node_boost_error(N.id, "No valid list.")
|
||||
WARNING("No valid list.")
|
||||
. = FALSE
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/research/proc/verify_techweb_designs()
|
||||
for(var/d in techweb_designs)
|
||||
var/datum/design/D = techweb_designs[d]
|
||||
if(!istype(D))
|
||||
stack_trace("WARNING: Invalid research design with ID [d] detected and removed.")
|
||||
techweb_designs -= d
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/research/proc/research_node_id_error(id)
|
||||
if(invalid_node_ids[id])
|
||||
invalid_node_ids[id]++
|
||||
else
|
||||
invalid_node_ids[id] = 1
|
||||
|
||||
/datum/controller/subsystem/research/proc/design_id_error(id)
|
||||
if(invalid_design_ids[id])
|
||||
invalid_design_ids[id]++
|
||||
else
|
||||
invalid_design_ids[id] = 1
|
||||
|
||||
/datum/controller/subsystem/research/proc/calculate_techweb_nodes()
|
||||
for(var/design_id in techweb_designs)
|
||||
var/datum/design/D = techweb_designs[design_id]
|
||||
D.unlocked_by.Cut()
|
||||
for(var/node_id in techweb_nodes)
|
||||
var/datum/techweb_node/node = techweb_nodes[node_id]
|
||||
node.unlock_ids = list()
|
||||
for(var/i in node.design_ids)
|
||||
var/datum/design/D = techweb_designs[i]
|
||||
node.design_ids[i] = TRUE
|
||||
D.unlocked_by += node.id
|
||||
if(node.hidden)
|
||||
techweb_nodes_hidden[node.id] = TRUE
|
||||
CHECK_TICK
|
||||
generate_techweb_unlock_linking()
|
||||
|
||||
/datum/controller/subsystem/research/proc/generate_techweb_unlock_linking()
|
||||
for(var/node_id in techweb_nodes) //Clear all unlock links to avoid duplication.
|
||||
var/datum/techweb_node/node = techweb_nodes[node_id]
|
||||
node.unlock_ids = list()
|
||||
for(var/node_id in techweb_nodes)
|
||||
var/datum/techweb_node/node = techweb_nodes[node_id]
|
||||
for(var/prereq_id in node.prereq_ids)
|
||||
var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id)
|
||||
prereq_node.unlock_ids[node.id] = node
|
||||
|
||||
/datum/controller/subsystem/research/proc/calculate_techweb_boost_list(clearall = FALSE)
|
||||
if(clearall)
|
||||
techweb_boost_items = list()
|
||||
for(var/node_id in techweb_nodes)
|
||||
var/datum/techweb_node/node = techweb_nodes[node_id]
|
||||
for(var/path in node.boost_item_paths)
|
||||
if(!ispath(path))
|
||||
continue
|
||||
if(length(techweb_boost_items[path]))
|
||||
techweb_boost_items[path][node.id] = node.boost_item_paths[path]
|
||||
else
|
||||
techweb_boost_items[path] = list(node.id = node.boost_item_paths[path])
|
||||
CHECK_TICK
|
||||
@@ -0,0 +1,648 @@
|
||||
#define MAX_TRANSIT_REQUEST_RETRIES 10
|
||||
|
||||
SUBSYSTEM_DEF(shuttle)
|
||||
name = "Shuttle"
|
||||
wait = 10
|
||||
init_order = INIT_ORDER_SHUTTLE
|
||||
flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK
|
||||
runlevels = RUNLEVEL_SETUP | RUNLEVEL_GAME
|
||||
|
||||
var/obj/machinery/shuttle_manipulator/manipulator
|
||||
|
||||
var/list/mobile = list()
|
||||
var/list/stationary = list()
|
||||
var/list/transit = list()
|
||||
|
||||
var/list/transit_requesters = list()
|
||||
var/list/transit_request_failures = list()
|
||||
|
||||
//emergency shuttle stuff
|
||||
var/obj/docking_port/mobile/emergency/emergency
|
||||
var/obj/docking_port/mobile/arrivals/arrivals
|
||||
var/obj/docking_port/mobile/emergency/backup/backup_shuttle
|
||||
var/emergencyCallTime = 6000 //time taken for emergency shuttle to reach the station when called (in deciseconds)
|
||||
var/emergencyDockTime = 1800 //time taken for emergency shuttle to leave again once it has docked (in deciseconds)
|
||||
var/emergencyEscapeTime = 1200 //time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds)
|
||||
var/area/emergencyLastCallLoc
|
||||
var/emergencyCallAmount = 0 //how many times the escape shuttle was called
|
||||
var/emergencyNoEscape
|
||||
var/emergencyNoRecall = FALSE
|
||||
var/list/hostileEnvironments = list() //Things blocking escape shuttle from leaving
|
||||
var/list/tradeBlockade = list() //Things blocking cargo from leaving.
|
||||
var/supplyBlocked = FALSE
|
||||
|
||||
//supply shuttle stuff
|
||||
var/obj/docking_port/mobile/supply/supply
|
||||
var/ordernum = 1 //order number given to next order
|
||||
var/points = 5000 //number of trade-points we have
|
||||
var/centcom_message = "" //Remarks from CentCom on how well you checked the last order.
|
||||
var/list/discoveredPlants = list() //Typepaths for unusual plants we've already sent CentCom, associated with their potencies
|
||||
var/passive_supply_points_per_minute = 750
|
||||
|
||||
var/list/supply_packs = list()
|
||||
var/list/shoppinglist = list()
|
||||
var/list/requestlist = list()
|
||||
var/list/orderhistory = list()
|
||||
|
||||
var/list/hidden_shuttle_turfs = list() //all turfs hidden from navigation computers associated with a list containing the image hiding them and the type of the turf they are pretending to be
|
||||
var/list/hidden_shuttle_turf_images = list() //only the images from the above list
|
||||
|
||||
var/datum/round_event/shuttle_loan/shuttle_loan
|
||||
|
||||
var/shuttle_purchased = FALSE //If the station has purchased a replacement escape shuttle this round
|
||||
var/list/shuttle_purchase_requirements_met = list() //For keeping track of ingame events that would unlock new shuttles, such as defeating a boss or discovering a secret item
|
||||
|
||||
var/lockdown = FALSE //disallow transit after nuke goes off
|
||||
|
||||
var/auto_call = 72000 //CIT CHANGE - time before in deciseconds in which the shuttle is auto called. Default is 2ish hours plus 15 for the shuttle. So total is 3.
|
||||
var/realtimeofstart = 0
|
||||
|
||||
/datum/controller/subsystem/shuttle/Initialize(timeofday)
|
||||
ordernum = rand(1, 9000)
|
||||
|
||||
for(var/pack in subtypesof(/datum/supply_pack))
|
||||
var/datum/supply_pack/P = new pack()
|
||||
if(!P.contains)
|
||||
continue
|
||||
supply_packs[P.type] = P
|
||||
|
||||
initial_load()
|
||||
|
||||
if(!arrivals)
|
||||
WARNING("No /obj/docking_port/mobile/arrivals placed on the map!")
|
||||
if(!emergency)
|
||||
WARNING("No /obj/docking_port/mobile/emergency placed on the map!")
|
||||
if(!backup_shuttle)
|
||||
WARNING("No /obj/docking_port/mobile/emergency/backup placed on the map!")
|
||||
if(!supply)
|
||||
WARNING("No /obj/docking_port/mobile/supply placed on the map!")
|
||||
realtimeofstart = world.realtime
|
||||
auto_call = CONFIG_GET(number/auto_transfer_delay)
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/initial_load()
|
||||
if(!istype(manipulator))
|
||||
CRASH("No shuttle manipulator found.")
|
||||
|
||||
for(var/s in stationary)
|
||||
var/obj/docking_port/stationary/S = s
|
||||
S.load_roundstart()
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/shuttle/fire()
|
||||
for(var/thing in mobile)
|
||||
if(!thing)
|
||||
mobile.Remove(thing)
|
||||
continue
|
||||
var/obj/docking_port/mobile/P = thing
|
||||
P.check()
|
||||
for(var/thing in transit)
|
||||
var/obj/docking_port/stationary/transit/T = thing
|
||||
if(!T.owner)
|
||||
qdel(T, force=TRUE)
|
||||
// This next one removes transit docks/zones that aren't
|
||||
// immediately being used. This will mean that the zone creation
|
||||
// code will be running a lot.
|
||||
var/obj/docking_port/mobile/owner = T.owner
|
||||
if(owner)
|
||||
var/idle = owner.mode == SHUTTLE_IDLE
|
||||
var/not_centcom_evac = owner.launch_status == NOLAUNCH
|
||||
var/not_in_use = (!T.get_docked())
|
||||
if(idle && not_centcom_evac && not_in_use)
|
||||
qdel(T, force=TRUE)
|
||||
CheckAutoEvac()
|
||||
|
||||
//Cargo stuff start
|
||||
var/fire_time_diff = max(0, world.time - last_fire) //Don't want this to be below 0, seriously.
|
||||
var/point_gain = (fire_time_diff / 600) * passive_supply_points_per_minute
|
||||
points += point_gain
|
||||
//Cargo stuff end
|
||||
|
||||
if(!SSmapping.clearing_reserved_turfs)
|
||||
while(transit_requesters.len)
|
||||
var/requester = popleft(transit_requesters)
|
||||
var/success = generate_transit_dock(requester)
|
||||
if(!success) // BACK OF THE QUEUE
|
||||
transit_request_failures[requester]++
|
||||
if(transit_request_failures[requester] < MAX_TRANSIT_REQUEST_RETRIES)
|
||||
transit_requesters += requester
|
||||
else
|
||||
var/obj/docking_port/mobile/M = requester
|
||||
M.transit_failure()
|
||||
if(MC_TICK_CHECK)
|
||||
break
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/CheckAutoEvac()
|
||||
if(emergencyNoEscape || emergencyNoRecall || !emergency || !SSticker.HasRoundStarted())
|
||||
return
|
||||
|
||||
var/threshold = CONFIG_GET(number/emergency_shuttle_autocall_threshold)
|
||||
if(!threshold)
|
||||
return
|
||||
|
||||
var/alive = 0
|
||||
for(var/I in GLOB.player_list)
|
||||
var/mob/M = I
|
||||
if(M.stat != DEAD)
|
||||
++alive
|
||||
|
||||
var/total = GLOB.joined_player_list.len
|
||||
|
||||
if(alive / total <= threshold)
|
||||
var/msg = "Automatically dispatching shuttle due to crew death."
|
||||
message_admins(msg)
|
||||
log_game("[msg] Alive: [alive], Roundstart: [total], Threshold: [threshold]")
|
||||
emergencyNoRecall = TRUE
|
||||
priority_announce("Catastrophic casualties detected: crisis shuttle protocols activated - jamming recall signals across all frequencies.")
|
||||
if(emergency.timeLeft(1) > emergencyCallTime * 0.4)
|
||||
emergency.request(null, set_coefficient = 0.4)
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/block_recall(lockout_timer)
|
||||
emergencyNoRecall = TRUE
|
||||
addtimer(CALLBACK(src, .proc/unblock_recall), lockout_timer)
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/unblock_recall()
|
||||
emergencyNoRecall = FALSE
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/getShuttle(id)
|
||||
for(var/obj/docking_port/mobile/M in mobile)
|
||||
if(M.id == id)
|
||||
return M
|
||||
WARNING("couldn't find shuttle with id: [id]")
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/getDock(id)
|
||||
for(var/obj/docking_port/stationary/S in stationary)
|
||||
if(S.id == id)
|
||||
return S
|
||||
WARNING("couldn't find dock with id: [id]")
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/requestEvac(mob/user, call_reason)
|
||||
if(!emergency)
|
||||
WARNING("requestEvac(): There is no emergency shuttle, but the \
|
||||
shuttle was called. Using the backup shuttle instead.")
|
||||
if(!backup_shuttle)
|
||||
throw EXCEPTION("requestEvac(): There is no emergency shuttle, \
|
||||
or backup shuttle! The game will be unresolvable. This is \
|
||||
possibly a mapping error, more likely a bug with the shuttle \
|
||||
manipulation system, or badminry. It is possible to manually \
|
||||
resolve this problem by loading an emergency shuttle template \
|
||||
manually, and then calling register() on the mobile docking port. \
|
||||
Good luck.")
|
||||
return
|
||||
emergency = backup_shuttle
|
||||
var/srd = CONFIG_GET(number/shuttle_refuel_delay)
|
||||
if(world.time - SSticker.round_start_time < srd)
|
||||
to_chat(user, "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before trying again.")
|
||||
return
|
||||
|
||||
switch(emergency.mode)
|
||||
if(SHUTTLE_RECALL)
|
||||
to_chat(user, "The emergency shuttle may not be called while returning to CentCom.")
|
||||
return
|
||||
if(SHUTTLE_CALL)
|
||||
to_chat(user, "The emergency shuttle is already on its way.")
|
||||
return
|
||||
if(SHUTTLE_DOCKED)
|
||||
to_chat(user, "The emergency shuttle is already here.")
|
||||
return
|
||||
if(SHUTTLE_IGNITING)
|
||||
to_chat(user, "The emergency shuttle is firing its engines to leave.")
|
||||
return
|
||||
if(SHUTTLE_ESCAPE)
|
||||
to_chat(user, "The emergency shuttle is moving away to a safe distance.")
|
||||
return
|
||||
if(SHUTTLE_STRANDED)
|
||||
to_chat(user, "The emergency shuttle has been disabled by CentCom.")
|
||||
return
|
||||
|
||||
call_reason = trim(html_encode(call_reason))
|
||||
|
||||
if(length(call_reason) < CALL_SHUTTLE_REASON_LENGTH && seclevel2num(get_security_level()) > SEC_LEVEL_GREEN)
|
||||
to_chat(user, "You must provide a reason.")
|
||||
return
|
||||
|
||||
var/area/signal_origin = get_area(user)
|
||||
var/emergency_reason = "\nNature of emergency:\n\n[call_reason]"
|
||||
var/security_num = seclevel2num(get_security_level())
|
||||
switch(security_num)
|
||||
if(SEC_LEVEL_RED,SEC_LEVEL_DELTA)
|
||||
emergency.request(null, signal_origin, html_decode(emergency_reason), 1) //There is a serious threat we gotta move no time to give them five minutes.
|
||||
else
|
||||
emergency.request(null, signal_origin, html_decode(emergency_reason), 0)
|
||||
|
||||
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
|
||||
|
||||
if(!frequency)
|
||||
return
|
||||
|
||||
var/datum/signal/status_signal = new(list("command" = "update")) // Start processing shuttle-mode displays to display the timer
|
||||
frequency.post_signal(src, status_signal)
|
||||
|
||||
var/area/A = get_area(user)
|
||||
|
||||
log_game("[key_name(user)] has called the shuttle.")
|
||||
deadchat_broadcast("<span class='deadsay'><span class='name'>[user.real_name]</span> has called the shuttle at <span class='name'>[A.name]</span>.</span>", user)
|
||||
if(call_reason)
|
||||
SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]")
|
||||
log_game("Shuttle call reason: [call_reason]")
|
||||
message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (<A HREF='?_src_=holder;[HrefToken()];trigger_centcom_recall=1'>TRIGGER CENTCOM RECALL</A>)")
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/centcom_recall(old_timer, admiral_message)
|
||||
if(emergency.mode != SHUTTLE_CALL || emergency.timer != old_timer)
|
||||
return
|
||||
emergency.cancel()
|
||||
|
||||
if(!admiral_message)
|
||||
admiral_message = pick(GLOB.admiral_messages)
|
||||
var/intercepttext = "<font size = 3><b>Nanotrasen Update</b>: Request For Shuttle.</font><hr>\
|
||||
To whom it may concern:<br><br>\
|
||||
We have taken note of the situation upon [station_name()] and have come to the \
|
||||
conclusion that it does not warrant the abandonment of the station.<br>\
|
||||
If you do not agree with our opinion we suggest that you open a direct \
|
||||
line with us and explain the nature of your crisis.<br><br>\
|
||||
<i>This message has been automatically generated based upon readings from long \
|
||||
range diagnostic tools. To assure the quality of your request every finalized report \
|
||||
is reviewed by an on-call rear admiral.<br>\
|
||||
<b>Rear Admiral's Notes:</b> \
|
||||
[admiral_message]"
|
||||
print_command_report(intercepttext, announce = TRUE)
|
||||
|
||||
// Called when an emergency shuttle mobile docking port is
|
||||
// destroyed, which will only happen with admin intervention
|
||||
/datum/controller/subsystem/shuttle/proc/emergencyDeregister()
|
||||
// When a new emergency shuttle is created, it will override the
|
||||
// backup shuttle.
|
||||
src.emergency = src.backup_shuttle
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/cancelEvac(mob/user)
|
||||
if(canRecall())
|
||||
emergency.cancel(get_area(user))
|
||||
log_game("[key_name(user)] has recalled the shuttle.")
|
||||
message_admins("[ADMIN_LOOKUPFLW(user)] has recalled the shuttle.")
|
||||
deadchat_broadcast("<span class='deadsay'><span class='name'>[user.real_name]</span> has recalled the shuttle from <span class='name'>[get_area_name(user, TRUE)]</span>.</span>", user)
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/canRecall()
|
||||
if(!emergency || emergency.mode != SHUTTLE_CALL || emergencyNoRecall || SSticker.mode.name == "meteor")
|
||||
return
|
||||
var/security_num = seclevel2num(get_security_level())
|
||||
switch(security_num)
|
||||
if(SEC_LEVEL_GREEN)
|
||||
if(emergency.timeLeft(1) < emergencyCallTime)
|
||||
return
|
||||
if(SEC_LEVEL_BLUE)
|
||||
if(emergency.timeLeft(1) < emergencyCallTime * 0.6)
|
||||
return
|
||||
if(SEC_LEVEL_AMBER)
|
||||
if(emergency.timeLeft(1) < emergencyCallTime * 0.4)
|
||||
return
|
||||
else
|
||||
if(emergency.timeLeft(1) < emergencyCallTime * 0.25)
|
||||
return
|
||||
return 1
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/autoEvac()
|
||||
if (!SSticker.IsRoundInProgress())
|
||||
return
|
||||
|
||||
var/callShuttle = 1
|
||||
|
||||
for(var/thing in GLOB.shuttle_caller_list)
|
||||
if(isAI(thing))
|
||||
var/mob/living/silicon/ai/AI = thing
|
||||
if(AI.deployed_shell && !AI.deployed_shell.client)
|
||||
continue
|
||||
if(AI.stat || !AI.client)
|
||||
continue
|
||||
else if(istype(thing, /obj/machinery/computer/communications))
|
||||
var/obj/machinery/computer/communications/C = thing
|
||||
if(C.stat & BROKEN)
|
||||
continue
|
||||
|
||||
var/turf/T = get_turf(thing)
|
||||
if(T && is_station_level(T.z))
|
||||
callShuttle = 0
|
||||
break
|
||||
|
||||
if(callShuttle)
|
||||
if(EMERGENCY_IDLE_OR_RECALLED)
|
||||
emergency.request(null, set_coefficient = 2.5)
|
||||
log_game("There is no means of calling the shuttle anymore. Shuttle automatically called.")
|
||||
message_admins("All the communications consoles were destroyed and all AIs are inactive. Shuttle called.")
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/registerHostileEnvironment(datum/bad)
|
||||
hostileEnvironments[bad] = TRUE
|
||||
checkHostileEnvironment()
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/clearHostileEnvironment(datum/bad)
|
||||
hostileEnvironments -= bad
|
||||
checkHostileEnvironment()
|
||||
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/registerTradeBlockade(datum/bad)
|
||||
tradeBlockade[bad] = TRUE
|
||||
checkTradeBlockade()
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/clearTradeBlockade(datum/bad)
|
||||
tradeBlockade -= bad
|
||||
checkTradeBlockade()
|
||||
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/checkTradeBlockade()
|
||||
for(var/datum/d in tradeBlockade)
|
||||
if(!istype(d) || QDELETED(d))
|
||||
tradeBlockade -= d
|
||||
supplyBlocked = tradeBlockade.len
|
||||
|
||||
if(supplyBlocked && (supply.mode == SHUTTLE_IGNITING))
|
||||
supply.mode = SHUTTLE_STRANDED
|
||||
supply.timer = null
|
||||
//Make all cargo consoles speak up
|
||||
if(!supplyBlocked && (supply.mode == SHUTTLE_STRANDED))
|
||||
supply.mode = SHUTTLE_DOCKED
|
||||
//Make all cargo consoles speak up
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/checkHostileEnvironment()
|
||||
for(var/datum/d in hostileEnvironments)
|
||||
if(!istype(d) || QDELETED(d))
|
||||
hostileEnvironments -= d
|
||||
emergencyNoEscape = hostileEnvironments.len
|
||||
|
||||
if(emergencyNoEscape && (emergency.mode == SHUTTLE_IGNITING))
|
||||
emergency.mode = SHUTTLE_STRANDED
|
||||
emergency.timer = null
|
||||
emergency.sound_played = FALSE
|
||||
priority_announce("Hostile environment detected. \
|
||||
Departure has been postponed indefinitely pending \
|
||||
conflict resolution.", null, 'sound/misc/notice1.ogg', "Priority")
|
||||
if(!emergencyNoEscape && (emergency.mode == SHUTTLE_STRANDED))
|
||||
emergency.mode = SHUTTLE_DOCKED
|
||||
emergency.setTimer(emergencyDockTime)
|
||||
priority_announce("Hostile environment resolved. \
|
||||
You have 3 minutes to board the Emergency Shuttle.",
|
||||
null, "shuttledock", "Priority")
|
||||
|
||||
//try to move/request to dockHome if possible, otherwise dockAway. Mainly used for admin buttons
|
||||
/datum/controller/subsystem/shuttle/proc/toggleShuttle(shuttleId, dockHome, dockAway, timed)
|
||||
var/obj/docking_port/mobile/M = getShuttle(shuttleId)
|
||||
if(!M)
|
||||
return 1
|
||||
var/obj/docking_port/stationary/dockedAt = M.get_docked()
|
||||
var/destination = dockHome
|
||||
if(dockedAt && dockedAt.id == dockHome)
|
||||
destination = dockAway
|
||||
if(timed)
|
||||
if(M.request(getDock(destination)))
|
||||
return 2
|
||||
else
|
||||
if(M.initiate_docking(getDock(destination)) != DOCKING_SUCCESS)
|
||||
return 2
|
||||
return 0 //dock successful
|
||||
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/moveShuttle(shuttleId, dockId, timed)
|
||||
var/obj/docking_port/mobile/M = getShuttle(shuttleId)
|
||||
var/obj/docking_port/stationary/D = getDock(dockId)
|
||||
|
||||
if(!M)
|
||||
return 1
|
||||
if(timed)
|
||||
if(M.request(D))
|
||||
return 2
|
||||
else
|
||||
if(M.initiate_docking(D) != DOCKING_SUCCESS)
|
||||
return 2
|
||||
return 0 //dock successful
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/request_transit_dock(obj/docking_port/mobile/M)
|
||||
if(!istype(M))
|
||||
throw EXCEPTION("[M] is not a mobile docking port")
|
||||
|
||||
if(M.assigned_transit)
|
||||
return
|
||||
else
|
||||
if(!(M in transit_requesters))
|
||||
transit_requesters += M
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/generate_transit_dock(obj/docking_port/mobile/M)
|
||||
// First, determine the size of the needed zone
|
||||
// Because of shuttle rotation, the "width" of the shuttle is not
|
||||
// always x.
|
||||
var/travel_dir = M.preferred_direction
|
||||
// Remember, the direction is the direction we appear to be
|
||||
// coming from
|
||||
var/dock_angle = dir2angle(M.preferred_direction) + dir2angle(M.port_direction) + 180
|
||||
var/dock_dir = angle2dir(dock_angle)
|
||||
|
||||
var/transit_width = SHUTTLE_TRANSIT_BORDER * 2
|
||||
var/transit_height = SHUTTLE_TRANSIT_BORDER * 2
|
||||
|
||||
// Shuttles travelling on their side have their dimensions swapped
|
||||
// from our perspective
|
||||
switch(dock_dir)
|
||||
if(NORTH, SOUTH)
|
||||
transit_width += M.width
|
||||
transit_height += M.height
|
||||
if(EAST, WEST)
|
||||
transit_width += M.height
|
||||
transit_height += M.width
|
||||
|
||||
/*
|
||||
to_chat(world, "The attempted transit dock will be [transit_width] width, and \)
|
||||
[transit_height] in height. The travel dir is [travel_dir]."
|
||||
*/
|
||||
|
||||
var/transit_path = /turf/open/space/transit
|
||||
var/border_path = /turf/open/space/transit/border
|
||||
switch(travel_dir)
|
||||
if(NORTH)
|
||||
transit_path = /turf/open/space/transit/north
|
||||
border_path = /turf/open/space/transit/border/north
|
||||
if(SOUTH)
|
||||
transit_path = /turf/open/space/transit/south
|
||||
border_path = /turf/open/space/transit/border/south
|
||||
if(EAST)
|
||||
transit_path = /turf/open/space/transit/east
|
||||
border_path = /turf/open/space/transit/border/east
|
||||
if(WEST)
|
||||
transit_path = /turf/open/space/transit/west
|
||||
border_path = /turf/open/space/transit/border/west
|
||||
|
||||
var/datum/turf_reservation/proposal = SSmapping.RequestBlockReservation(transit_width, transit_height, null, /datum/turf_reservation/transit, transit_path, border_path)
|
||||
|
||||
if(!istype(proposal))
|
||||
return FALSE
|
||||
|
||||
var/turf/bottomleft = locate(proposal.bottom_left_coords[1], proposal.bottom_left_coords[2], proposal.bottom_left_coords[3])
|
||||
// Then create a transit docking port in the middle
|
||||
var/coords = M.return_coords(0, 0, dock_dir)
|
||||
/* 0------2
|
||||
| |
|
||||
| |
|
||||
| x |
|
||||
3------1
|
||||
*/
|
||||
|
||||
var/x0 = coords[1]
|
||||
var/y0 = coords[2]
|
||||
var/x1 = coords[3]
|
||||
var/y1 = coords[4]
|
||||
// Then we want the point closest to -infinity,-infinity
|
||||
var/x2 = min(x0, x1)
|
||||
var/y2 = min(y0, y1)
|
||||
|
||||
// Then invert the numbers
|
||||
var/transit_x = bottomleft.x + SHUTTLE_TRANSIT_BORDER + abs(x2)
|
||||
var/transit_y = bottomleft.y + SHUTTLE_TRANSIT_BORDER + abs(y2)
|
||||
|
||||
var/turf/midpoint = locate(transit_x, transit_y, bottomleft.z)
|
||||
if(!midpoint)
|
||||
return FALSE
|
||||
var/area/shuttle/transit/A = new()
|
||||
A.parallax_movedir = travel_dir
|
||||
A.contents = proposal.reserved_turfs
|
||||
var/obj/docking_port/stationary/transit/new_transit_dock = new(midpoint)
|
||||
new_transit_dock.reserved_area = proposal
|
||||
new_transit_dock.name = "Transit for [M.id]/[M.name]"
|
||||
new_transit_dock.owner = M
|
||||
new_transit_dock.assigned_area = A
|
||||
|
||||
// Add 180, because ports point inwards, rather than outwards
|
||||
new_transit_dock.setDir(angle2dir(dock_angle))
|
||||
|
||||
M.assigned_transit = new_transit_dock
|
||||
return new_transit_dock
|
||||
|
||||
/datum/controller/subsystem/shuttle/Recover()
|
||||
if (istype(SSshuttle.mobile))
|
||||
mobile = SSshuttle.mobile
|
||||
if (istype(SSshuttle.stationary))
|
||||
stationary = SSshuttle.stationary
|
||||
if (istype(SSshuttle.transit))
|
||||
transit = SSshuttle.transit
|
||||
if (istype(SSshuttle.transit_requesters))
|
||||
transit_requesters = SSshuttle.transit_requesters
|
||||
if (istype(SSshuttle.transit_request_failures))
|
||||
transit_request_failures = SSshuttle.transit_request_failures
|
||||
|
||||
if (istype(SSshuttle.emergency))
|
||||
emergency = SSshuttle.emergency
|
||||
if (istype(SSshuttle.arrivals))
|
||||
arrivals = SSshuttle.arrivals
|
||||
if (istype(SSshuttle.backup_shuttle))
|
||||
backup_shuttle = SSshuttle.backup_shuttle
|
||||
|
||||
if (istype(SSshuttle.emergencyLastCallLoc))
|
||||
emergencyLastCallLoc = SSshuttle.emergencyLastCallLoc
|
||||
|
||||
if (istype(SSshuttle.hostileEnvironments))
|
||||
hostileEnvironments = SSshuttle.hostileEnvironments
|
||||
|
||||
if (istype(SSshuttle.supply))
|
||||
supply = SSshuttle.supply
|
||||
|
||||
if (istype(SSshuttle.discoveredPlants))
|
||||
discoveredPlants = SSshuttle.discoveredPlants
|
||||
|
||||
if (istype(SSshuttle.shoppinglist))
|
||||
shoppinglist = SSshuttle.shoppinglist
|
||||
if (istype(SSshuttle.requestlist))
|
||||
requestlist = SSshuttle.requestlist
|
||||
if (istype(SSshuttle.orderhistory))
|
||||
orderhistory = SSshuttle.orderhistory
|
||||
|
||||
if (istype(SSshuttle.shuttle_loan))
|
||||
shuttle_loan = SSshuttle.shuttle_loan
|
||||
|
||||
if (istype(SSshuttle.shuttle_purchase_requirements_met))
|
||||
shuttle_purchase_requirements_met = SSshuttle.shuttle_purchase_requirements_met
|
||||
|
||||
centcom_message = SSshuttle.centcom_message
|
||||
ordernum = SSshuttle.ordernum
|
||||
points = SSshuttle.points
|
||||
emergencyNoEscape = SSshuttle.emergencyNoEscape
|
||||
emergencyCallAmount = SSshuttle.emergencyCallAmount
|
||||
shuttle_purchased = SSshuttle.shuttle_purchased
|
||||
lockdown = SSshuttle.lockdown
|
||||
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/is_in_shuttle_bounds(atom/A)
|
||||
var/area/current = get_area(A)
|
||||
if(istype(current, /area/shuttle) && !istype(current, /area/shuttle/transit))
|
||||
return TRUE
|
||||
for(var/obj/docking_port/mobile/M in mobile)
|
||||
if(M.is_in_shuttle_bounds(A))
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/get_containing_shuttle(atom/A)
|
||||
var/list/mobile_cache = mobile
|
||||
for(var/i in 1 to mobile_cache.len)
|
||||
var/obj/docking_port/port = mobile_cache[i]
|
||||
if(port.is_in_shuttle_bounds(A))
|
||||
return port
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/get_containing_dock(atom/A)
|
||||
. = list()
|
||||
var/list/stationary_cache = stationary
|
||||
for(var/i in 1 to stationary_cache.len)
|
||||
var/obj/docking_port/port = stationary_cache[i]
|
||||
if(port.is_in_shuttle_bounds(A))
|
||||
. += port
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/get_dock_overlap(x0, y0, x1, y1, z)
|
||||
. = list()
|
||||
var/list/stationary_cache = stationary
|
||||
for(var/i in 1 to stationary_cache.len)
|
||||
var/obj/docking_port/port = stationary_cache[i]
|
||||
if(!port || port.z != z)
|
||||
continue
|
||||
var/list/bounds = port.return_coords()
|
||||
var/list/overlap = get_overlap(x0, y0, x1, y1, bounds[1], bounds[2], bounds[3], bounds[4])
|
||||
var/list/xs = overlap[1]
|
||||
var/list/ys = overlap[2]
|
||||
if(xs.len && ys.len)
|
||||
.[port] = overlap
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/update_hidden_docking_ports(list/remove_turfs, list/add_turfs)
|
||||
var/list/remove_images = list()
|
||||
var/list/add_images = list()
|
||||
|
||||
if(remove_turfs)
|
||||
for(var/T in remove_turfs)
|
||||
var/list/L = hidden_shuttle_turfs[T]
|
||||
if(L)
|
||||
remove_images += L[1]
|
||||
hidden_shuttle_turfs -= remove_turfs
|
||||
|
||||
if(add_turfs)
|
||||
for(var/V in add_turfs)
|
||||
var/turf/T = V
|
||||
var/image/I
|
||||
if(remove_images.len)
|
||||
//we can just reuse any images we are about to delete instead of making new ones
|
||||
I = remove_images[1]
|
||||
remove_images.Cut(1, 2)
|
||||
I.loc = T
|
||||
else
|
||||
I = image(loc = T)
|
||||
add_images += I
|
||||
I.appearance = T.appearance
|
||||
I.override = TRUE
|
||||
hidden_shuttle_turfs[T] = list(I, T.type)
|
||||
|
||||
hidden_shuttle_turf_images -= remove_images
|
||||
hidden_shuttle_turf_images += add_images
|
||||
|
||||
for(var/V in GLOB.navigation_computers)
|
||||
var/obj/machinery/computer/camera_advanced/shuttle_docker/C = V
|
||||
C.update_hidden_docking_ports(remove_images, add_images)
|
||||
|
||||
QDEL_LIST(remove_images)
|
||||
|
||||
/datum/controller/subsystem/shuttle/proc/autoEnd() //CIT CHANGE - allows shift to end after 2 hours have passed.
|
||||
if((world.realtime - SSshuttle.realtimeofstart) > auto_call && EMERGENCY_IDLE_OR_RECALLED) //2 hours
|
||||
SSshuttle.emergency.request(silent = TRUE)
|
||||
priority_announce("The shift has come to an end and the shuttle called. [seclevel2num(get_security_level()) == SEC_LEVEL_RED ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [emergency.timeLeft(600)] minutes.", null, "shuttlecalled", "Priority")
|
||||
log_game("Round time limit reached. Shuttle has been auto-called.")
|
||||
message_admins("Round time limit reached. Shuttle called.")
|
||||
emergencyNoRecall = TRUE
|
||||
@@ -0,0 +1,167 @@
|
||||
#define MAX_THROWING_DIST 1280 // 5 z-levels on default width
|
||||
#define MAX_TICKS_TO_MAKE_UP 3 //how many missed ticks will we attempt to make up for this run.
|
||||
|
||||
SUBSYSTEM_DEF(throwing)
|
||||
name = "Throwing"
|
||||
priority = FIRE_PRIORITY_THROWING
|
||||
wait = 1
|
||||
flags = SS_NO_INIT|SS_KEEP_TIMING|SS_TICKER
|
||||
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
|
||||
|
||||
var/list/currentrun
|
||||
var/list/processing = list()
|
||||
|
||||
/datum/controller/subsystem/throwing/stat_entry()
|
||||
..("P:[processing.len]")
|
||||
|
||||
|
||||
/datum/controller/subsystem/throwing/fire(resumed = 0)
|
||||
if (!resumed)
|
||||
src.currentrun = processing.Copy()
|
||||
|
||||
//cache for sanic speed (lists are references anyways)
|
||||
var/list/currentrun = src.currentrun
|
||||
|
||||
while(length(currentrun))
|
||||
var/atom/movable/AM = currentrun[currentrun.len]
|
||||
var/datum/thrownthing/TT = currentrun[AM]
|
||||
currentrun.len--
|
||||
if (QDELETED(AM) || QDELETED(TT))
|
||||
processing -= AM
|
||||
if (MC_TICK_CHECK)
|
||||
return
|
||||
continue
|
||||
|
||||
TT.tick()
|
||||
|
||||
if (MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
currentrun = null
|
||||
|
||||
/datum/thrownthing
|
||||
var/atom/movable/thrownthing
|
||||
var/atom/target
|
||||
var/turf/target_turf
|
||||
var/target_zone
|
||||
var/init_dir
|
||||
var/maxrange
|
||||
var/speed
|
||||
var/mob/thrower
|
||||
var/diagonals_first
|
||||
var/dist_travelled = 0
|
||||
var/start_time
|
||||
var/dist_x
|
||||
var/dist_y
|
||||
var/dx
|
||||
var/dy
|
||||
var/pure_diagonal
|
||||
var/diagonal_error
|
||||
var/datum/callback/callback
|
||||
var/paused = FALSE
|
||||
var/delayed_time = 0
|
||||
var/last_move = 0
|
||||
|
||||
/datum/thrownthing/Destroy()
|
||||
SSthrowing.processing -= thrownthing
|
||||
thrownthing.throwing = null
|
||||
thrownthing = null
|
||||
target = null
|
||||
thrower = null
|
||||
callback = null
|
||||
return ..()
|
||||
|
||||
/datum/thrownthing/proc/tick()
|
||||
var/atom/movable/AM = thrownthing
|
||||
if (!isturf(AM.loc) || !AM.throwing)
|
||||
finalize()
|
||||
return
|
||||
|
||||
if(paused)
|
||||
delayed_time += world.time - last_move
|
||||
return
|
||||
|
||||
if (dist_travelled && hitcheck()) //to catch sneaky things moving on our tile while we slept
|
||||
finalize()
|
||||
return
|
||||
|
||||
var/atom/step
|
||||
|
||||
last_move = world.time
|
||||
|
||||
//calculate how many tiles to move, making up for any missed ticks.
|
||||
var/tilestomove = CEILING(min(((((world.time+world.tick_lag) - start_time + delayed_time) * speed) - (dist_travelled ? dist_travelled : -1)), speed*MAX_TICKS_TO_MAKE_UP) * (world.tick_lag * SSthrowing.wait), 1)
|
||||
while (tilestomove-- > 0)
|
||||
if ((dist_travelled >= maxrange || AM.loc == target_turf) && AM.has_gravity(AM.loc))
|
||||
finalize()
|
||||
return
|
||||
|
||||
if (dist_travelled <= max(dist_x, dist_y)) //if we haven't reached the target yet we home in on it, otherwise we use the initial direction
|
||||
step = get_step(AM, get_dir(AM, target_turf))
|
||||
else
|
||||
step = get_step(AM, init_dir)
|
||||
|
||||
if (!pure_diagonal && !diagonals_first) // not a purely diagonal trajectory and we don't want all diagonal moves to be done first
|
||||
if (diagonal_error >= 0 && max(dist_x,dist_y) - dist_travelled != 1) //we do a step forward unless we're right before the target
|
||||
step = get_step(AM, dx)
|
||||
diagonal_error += (diagonal_error < 0) ? dist_x/2 : -dist_y
|
||||
|
||||
if (!step) // going off the edge of the map makes get_step return null, don't let things go off the edge
|
||||
finalize()
|
||||
return
|
||||
|
||||
AM.Move(step, get_dir(AM, step))
|
||||
|
||||
if (!AM.throwing) // we hit something during our move
|
||||
finalize(hit = TRUE)
|
||||
return
|
||||
|
||||
dist_travelled++
|
||||
|
||||
if (dist_travelled > MAX_THROWING_DIST)
|
||||
finalize()
|
||||
return
|
||||
|
||||
/datum/thrownthing/proc/finalize(hit = FALSE, target=null)
|
||||
set waitfor = FALSE
|
||||
//done throwing, either because it hit something or it finished moving
|
||||
if(!thrownthing)
|
||||
return
|
||||
thrownthing.throwing = null
|
||||
if (!hit)
|
||||
for (var/thing in get_turf(thrownthing)) //looking for our target on the turf we land on.
|
||||
var/atom/A = thing
|
||||
if (A == target)
|
||||
hit = TRUE
|
||||
thrownthing.throw_impact(A, src)
|
||||
break
|
||||
if (!hit)
|
||||
thrownthing.throw_impact(get_turf(thrownthing), src) // we haven't hit something yet and we still must, let's hit the ground.
|
||||
thrownthing.newtonian_move(init_dir)
|
||||
else
|
||||
thrownthing.newtonian_move(init_dir)
|
||||
|
||||
if(target)
|
||||
thrownthing.throw_impact(target, src)
|
||||
|
||||
if (callback)
|
||||
callback.Invoke()
|
||||
|
||||
if(!thrownthing.zfalling) // I don't think you can zfall while thrown but hey, just in case.
|
||||
var/turf/T = get_turf(thrownthing)
|
||||
if(T && thrownthing.has_gravity(T))
|
||||
T.zFall(thrownthing)
|
||||
|
||||
qdel(src)
|
||||
|
||||
/datum/thrownthing/proc/hit_atom(atom/A)
|
||||
finalize(hit=TRUE, target=A)
|
||||
|
||||
/datum/thrownthing/proc/hitcheck()
|
||||
for (var/thing in get_turf(thrownthing))
|
||||
var/atom/movable/AM = thing
|
||||
if (AM == thrownthing || (AM == thrower && !ismob(thrownthing)))
|
||||
continue
|
||||
if (AM.density && !(AM.pass_flags & LETPASSTHROW) && !(AM.flags_1 & ON_BORDER_1))
|
||||
finalize(hit=TRUE, target=AM)
|
||||
return TRUE
|
||||
Executable
+688
@@ -0,0 +1,688 @@
|
||||
#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
|
||||
|
||||
var/current_state = GAME_STATE_STARTUP //state of current round (used by process()) Use the defines GAME_STATE_* !
|
||||
var/force_ending = 0 //Round was ended by admin intervention
|
||||
// If true, there is no lobby phase, the game starts immediately.
|
||||
var/start_immediately = FALSE
|
||||
var/setup_done = FALSE //All game setup done including mode post setup and
|
||||
|
||||
var/hide_mode = 0
|
||||
var/datum/game_mode/mode = null
|
||||
var/event_time = null
|
||||
var/event = 0
|
||||
|
||||
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/list/syndicate_coalition = list() //list of traitor-compatible factions
|
||||
var/list/factions = list() //list of all factions
|
||||
var/list/availablefactions = list() //list of factions with openings
|
||||
var/list/scripture_states = list(SCRIPTURE_DRIVER = TRUE, \
|
||||
SCRIPTURE_SCRIPT = FALSE, \
|
||||
SCRIPTURE_APPLICATION = FALSE) //list of clockcult scripture states for announcements
|
||||
|
||||
var/delay_end = 0 //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/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/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.
|
||||
|
||||
var/totalPlayers = 0 //used for pregame stats on statpanel
|
||||
var/totalPlayersReady = 0 //used for pregame stats on statpanel
|
||||
|
||||
var/queue_delay = 0
|
||||
var/list/queued_players = list() //used for join queues when the server exceeds the hard population cap
|
||||
|
||||
var/maprotatechecked = 0
|
||||
|
||||
var/news_report
|
||||
|
||||
var/late_join_disabled
|
||||
|
||||
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"
|
||||
|
||||
var/modevoted = FALSE //Have we sent a vote for the gamemode?
|
||||
|
||||
/datum/controller/subsystem/ticker/Initialize(timeofday)
|
||||
load_mode()
|
||||
|
||||
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
|
||||
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(isemptylist(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 ..()
|
||||
|
||||
/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 class='boldnotice'>Welcome to [station_name()]!</span>")
|
||||
if(CONFIG_GET(flag/irc_announce_new_game))
|
||||
world.TgsTargetedChatBroadcast("New round starting on [SSmapping.config.map_name]!", FALSE)
|
||||
current_state = GAME_STATE_PREGAME
|
||||
//Everyone who wants to be an observer is now spawned
|
||||
create_observers()
|
||||
fire()
|
||||
if(GAME_STATE_PREGAME)
|
||||
//lobby stats for statpanels
|
||||
if(isnull(timeLeft))
|
||||
timeLeft = max(0,start_at - world.time)
|
||||
totalPlayers = 0
|
||||
totalPlayersReady = 0
|
||||
for(var/mob/dead/new_player/player in GLOB.player_list)
|
||||
++totalPlayers
|
||||
if(player.ready == PLAYER_READY_TO_PLAY)
|
||||
++totalPlayersReady
|
||||
|
||||
if(start_immediately)
|
||||
timeLeft = 0
|
||||
|
||||
if(!modevoted)
|
||||
send_gamemode_vote()
|
||||
//countdown
|
||||
if(timeLeft < 0)
|
||||
return
|
||||
timeLeft -= wait
|
||||
|
||||
if(timeLeft <= 300 && !tipped)
|
||||
send_tip_of_the_round()
|
||||
tipped = TRUE
|
||||
|
||||
if(timeLeft <= 0)
|
||||
current_state = GAME_STATE_SETTING_UP
|
||||
Master.SetRunLevel(RUNLEVEL_SETUP)
|
||||
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)
|
||||
|
||||
if(GAME_STATE_PLAYING)
|
||||
mode.process(wait * 0.1)
|
||||
check_queue()
|
||||
check_maprotate()
|
||||
scripture_states = scripture_unlock_alert(scripture_states)
|
||||
SSshuttle.autoEnd()
|
||||
|
||||
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)
|
||||
Master.SetRunLevel(RUNLEVEL_POSTGAME)
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/setup()
|
||||
to_chat(world, "<span class='boldannounce'>Starting game...</span>")
|
||||
var/init_start = world.timeofday
|
||||
//Create and announce mode
|
||||
var/list/datum/game_mode/runnable_modes
|
||||
if(GLOB.master_mode == "random" || GLOB.master_mode == "secret")
|
||||
runnable_modes = config.get_runnable_modes()
|
||||
|
||||
if(GLOB.master_mode == "secret")
|
||||
hide_mode = 1
|
||||
if(GLOB.secret_force_mode != "secret")
|
||||
var/datum/game_mode/smode = config.pick_mode(GLOB.secret_force_mode)
|
||||
if(!smode.can_start())
|
||||
message_admins("<span class='notice'>Unable to force secret [GLOB.secret_force_mode]. [smode.required_players] players and [smode.required_enemies] eligible antagonists needed.</span>")
|
||||
else
|
||||
mode = smode
|
||||
|
||||
if(!mode)
|
||||
if(!runnable_modes.len)
|
||||
to_chat(world, "<B>Unable to choose playable game mode.</B> Reverting to pre-game lobby.")
|
||||
return 0
|
||||
mode = pickweight(runnable_modes)
|
||||
if(!mode) //too few roundtypes all run too recently
|
||||
mode = pick(runnable_modes)
|
||||
|
||||
else
|
||||
mode = config.pick_mode(GLOB.master_mode)
|
||||
if(!mode.can_start())
|
||||
to_chat(world, "<B>Unable to start [mode.name].</B> Not enough players, [mode.required_players] players and [mode.required_enemies] eligible antagonists needed. Reverting to pre-game lobby.")
|
||||
qdel(mode)
|
||||
mode = null
|
||||
SSjob.ResetOccupations()
|
||||
return 0
|
||||
|
||||
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(mode.required_jobs) //Distribute jobs
|
||||
CHECK_TICK
|
||||
|
||||
if(!GLOB.Debug2)
|
||||
if(!can_continue)
|
||||
log_game("[mode.name] failed pre_setup, cause: [mode.setup_error]")
|
||||
send2irc("SSticker", "[mode.name] failed pre_setup, cause: [mode.setup_error]")
|
||||
message_admins("<span class='notice'>[mode.name] failed pre_setup, cause: [mode.setup_error]</span>")
|
||||
QDEL_NULL(mode)
|
||||
to_chat(world, "<B>Error setting up [GLOB.master_mode].</B> Reverting to pre-game lobby.")
|
||||
SSjob.ResetOccupations()
|
||||
return 0
|
||||
else
|
||||
message_admins("<span class='notice'>DEBUG: Bypassing prestart checks...</span>")
|
||||
|
||||
CHECK_TICK
|
||||
/*if(hide_mode) CIT CHANGE - comments this section out to obfuscate gamemodes. Quit self-antagging during extended just because "hurrrrr no antaggs!!!!!! i giv sec thing 2 do!!!!!!!!!" it's bullshit and everyone hates it
|
||||
var/list/modes = new
|
||||
for (var/datum/game_mode/M in runnable_modes)
|
||||
modes += M.name
|
||||
modes = sortList(modes)
|
||||
to_chat(world, "<b>The gamemode is: secret!\nPossibilities:</B> [english_list(modes)]")
|
||||
else
|
||||
mode.announce()*/
|
||||
|
||||
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)
|
||||
|
||||
log_world("Game start took [(world.timeofday - init_start)/10]s")
|
||||
round_start_time = world.time
|
||||
SSdbcore.SetRoundStart()
|
||||
|
||||
to_chat(world, "<span class='notice'><B>Welcome to [station_name()], enjoy your stay!</B></span>")
|
||||
SEND_SOUND(world, sound(get_announcer_sound("welcome")))
|
||||
|
||||
current_state = GAME_STATE_PLAYING
|
||||
Master.SetRunLevel(RUNLEVEL_GAME)
|
||||
|
||||
if(SSevents.holidays)
|
||||
to_chat(world, "<span class='notice'>and...</span>")
|
||||
for(var/holidayname in SSevents.holidays)
|
||||
var/datum/holiday/holiday = SSevents.holidays[holidayname]
|
||||
to_chat(world, "<h4>[holiday.greet()]</h4>")
|
||||
|
||||
PostSetup()
|
||||
SSshuttle.realtimeofstart = world.realtime
|
||||
|
||||
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"]
|
||||
send2irc("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]:" : "of"] [hide_mode ? "secret":"[mode.name]"] 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!")
|
||||
|
||||
|
||||
//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
|
||||
var/turf/epi = bomb.loc
|
||||
qdel(bomb)
|
||||
if(epi)
|
||||
explosion(epi, 0, 256, 512, 0, TRUE, TRUE, 0, TRUE)
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/create_characters()
|
||||
for(var/mob/dead/new_player/player in GLOB.player_list)
|
||||
if(player.ready == PLAYER_READY_TO_PLAY && player.mind)
|
||||
GLOB.joined_player_list += player.ckey
|
||||
player.create_character(FALSE)
|
||||
else
|
||||
player.new_player_panel()
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/collect_minds()
|
||||
for(var/mob/dead/new_player/P in GLOB.player_list)
|
||||
if(P.new_character && P.new_character.mind)
|
||||
SSticker.minds += P.new_character.mind
|
||||
CHECK_TICK
|
||||
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/equip_characters()
|
||||
var/captainless=1
|
||||
for(var/mob/dead/new_player/N in GLOB.player_list)
|
||||
var/mob/living/carbon/human/player = N.new_character
|
||||
if(istype(player) && player.mind && player.mind.assigned_role)
|
||||
if(player.mind.assigned_role == "Captain")
|
||||
captainless=0
|
||||
if(player.mind.assigned_role != player.mind.special_role)
|
||||
SSjob.EquipRank(N, player.mind.assigned_role, 0)
|
||||
if(CONFIG_GET(flag/roundstart_traits) && ishuman(N.new_character))
|
||||
SSquirks.AssignQuirks(N.new_character, N.client, TRUE, TRUE, SSjob.GetJob(player.mind.assigned_role), FALSE, N)
|
||||
CHECK_TICK
|
||||
if(captainless)
|
||||
for(var/mob/dead/new_player/N in GLOB.player_list)
|
||||
if(N.new_character)
|
||||
to_chat(N, "Captainship not forced on anyone.")
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/transfer_characters()
|
||||
var/list/livings = list()
|
||||
for(var/mob/dead/new_player/player in GLOB.mob_list)
|
||||
var/mob/living = player.transfer_character()
|
||||
if(living)
|
||||
qdel(player)
|
||||
living.notransform = TRUE
|
||||
if(living.client)
|
||||
var/obj/screen/splash/S = new(living.client, TRUE)
|
||||
S.Fade(TRUE)
|
||||
livings += living
|
||||
if(livings.len)
|
||||
addtimer(CALLBACK(src, .proc/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/send_tip_of_the_round()
|
||||
var/m
|
||||
if(selected_tip)
|
||||
m = selected_tip
|
||||
else
|
||||
var/list/randomtips = world.file2list("strings/tips.txt")
|
||||
var/list/memetips = world.file2list("strings/sillytips.txt")
|
||||
if(randomtips.len && prob(95))
|
||||
m = pick(randomtips)
|
||||
else if(memetips.len)
|
||||
m = pick(memetips)
|
||||
|
||||
if(m)
|
||||
to_chat(world, "<span class='purple'><b>Tip of the round: </b>[html_encode(m)]</span>")
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/check_queue()
|
||||
var/hpc = CONFIG_GET(number/hard_popcap)
|
||||
if(!queued_players.len || !hpc)
|
||||
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
|
||||
if(living_player_count() < hpc)
|
||||
if(next_in_line && next_in_line.client)
|
||||
to_chat(next_in_line, "<span class='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></span>")
|
||||
SEND_SOUND(next_in_line, sound('sound/misc/notice1.ogg'))
|
||||
next_in_line.LateChoices()
|
||||
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 class='danger'>No response received. You have been removed from the line.</span>")
|
||||
queued_players -= next_in_line
|
||||
queue_delay = 0
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/check_maprotate()
|
||||
if (!CONFIG_GET(flag/maprotation))
|
||||
return
|
||||
if (SSshuttle.emergency && SSshuttle.emergency.mode != SHUTTLE_ESCAPE || SSshuttle.canRecall())
|
||||
return
|
||||
if (maprotatechecked)
|
||||
return
|
||||
|
||||
maprotatechecked = 1
|
||||
|
||||
//map rotate chance defaults to 75% of the length of the round (in minutes)
|
||||
if (!prob((world.time/600)*CONFIG_GET(number/maprotatechancedelta)) && CONFIG_GET(flag/tgstyle_maprotation))
|
||||
return
|
||||
if(CONFIG_GET(flag/tgstyle_maprotation))
|
||||
INVOKE_ASYNC(SSmapping, /datum/controller/subsystem/mapping/.proc/maprotate)
|
||||
else
|
||||
SSvote.initiate_vote("map","server",TRUE)
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/HasRoundStarted()
|
||||
return current_state >= GAME_STATE_PLAYING
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/IsRoundInProgress()
|
||||
return current_state == GAME_STATE_PLAYING
|
||||
|
||||
/proc/send_gamemode_vote() //CIT CHANGE - adds roundstart gamemode votes
|
||||
if(SSticker.current_state == GAME_STATE_PREGAME)
|
||||
if(SSticker.timeLeft < 900)
|
||||
SSticker.timeLeft = 900
|
||||
SSticker.modevoted = TRUE
|
||||
var/dynamic = CONFIG_GET(flag/dynamic_voting)
|
||||
SSvote.initiate_vote(dynamic ? "dynamic" : "roundtype","server",TRUE)
|
||||
|
||||
/datum/controller/subsystem/ticker/Recover()
|
||||
current_state = SSticker.current_state
|
||||
force_ending = SSticker.force_ending
|
||||
hide_mode = SSticker.hide_mode
|
||||
mode = SSticker.mode
|
||||
event_time = SSticker.event_time
|
||||
event = SSticker.event
|
||||
|
||||
login_music = SSticker.login_music
|
||||
round_end_sound = SSticker.round_end_sound
|
||||
|
||||
minds = SSticker.minds
|
||||
|
||||
syndicate_coalition = SSticker.syndicate_coalition
|
||||
factions = SSticker.factions
|
||||
availablefactions = SSticker.availablefactions
|
||||
|
||||
delay_end = SSticker.delay_end
|
||||
|
||||
triai = SSticker.triai
|
||||
tipped = SSticker.tipped
|
||||
selected_tip = SSticker.selected_tip
|
||||
|
||||
timeLeft = SSticker.timeLeft
|
||||
|
||||
totalPlayers = SSticker.totalPlayers
|
||||
totalPlayersReady = SSticker.totalPlayersReady
|
||||
|
||||
queue_delay = SSticker.queue_delay
|
||||
queued_players = SSticker.queued_players
|
||||
maprotatechecked = SSticker.maprotatechecked
|
||||
round_start_time = SSticker.round_start_time
|
||||
|
||||
queue_delay = SSticker.queue_delay
|
||||
queued_players = SSticker.queued_players
|
||||
maprotatechecked = SSticker.maprotatechecked
|
||||
|
||||
modevoted = SSticker.modevoted
|
||||
|
||||
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"
|
||||
switch(news_report)
|
||||
if(NUKE_SYNDICATE_BASE)
|
||||
news_message = "In a daring raid, the heroic crew of [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 [station_name()] are, in fact, a hoax. Have a secure day!"
|
||||
if(STATION_EVACUATED)
|
||||
news_message = "The crew of [station_name()] has been evacuated amid unconfirmed reports of enemy activity."
|
||||
if(BLOB_WIN)
|
||||
news_message = "[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 = "[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 = "[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 [station_name()]."
|
||||
if(CULT_FAILURE)
|
||||
news_message = "Following the dismantling of a restricted cult aboard [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 [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 [station_name()], detonating a nuclear weapon in empty space nearby."
|
||||
if(OPERATIVES_KILLED)
|
||||
news_message = "Repairs to [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 [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 [station_name()] there will be no wage increases for workers."
|
||||
if(REVS_LOSE)
|
||||
news_message = "[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 [station_name()]."
|
||||
if(STATION_NUKED)
|
||||
news_message = "[station_name()] activated its self destruct device for unknown reasons. Attempts to clone the Captain so he can be arrested and executed are underway."
|
||||
if(CLOCK_SUMMON)
|
||||
news_message = "The garbled messages about hailing a mouse and strange energy readings from [station_name()] have been discovered to be an ill-advised, if thorough, prank by a clown."
|
||||
if(CLOCK_SILICONS)
|
||||
news_message = "The project started by [station_name()] to upgrade their silicon units with advanced equipment have been largely successful, though they have thus far refused to release schematics in a violation of company policy."
|
||||
if(CLOCK_PROSELYTIZATION)
|
||||
news_message = "The burst of energy released near [station_name()] has been confirmed as merely a test of a new weapon. However, due to an unexpected mechanical error, their communications system has been knocked offline."
|
||||
if(SHUTTLE_HIJACK)
|
||||
news_message = "During routine evacuation procedures, the emergency shuttle of [station_name()] had its navigation protocols corrupted and went off course, but was recovered shortly after."
|
||||
if(GANG_VICTORY)
|
||||
news_message = "Company officials reaffirmed that sudden deployments of special forces are not in any way connected to rumors of [station_name()] being covered in graffiti."
|
||||
|
||||
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"]]\"" : ""]"
|
||||
else
|
||||
news_message += " NT Sanctioned Psykers proudly confirm reports that nobody died this shift!"
|
||||
|
||||
if(news_message)
|
||||
send2otherserver(news_source, news_message,"News_Report")
|
||||
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."
|
||||
|
||||
/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
|
||||
|
||||
//Everyone who wanted to be an observer gets made one now
|
||||
/datum/controller/subsystem/ticker/proc/create_observers()
|
||||
for(var/mob/dead/new_player/player in GLOB.player_list)
|
||||
if(player.ready == PLAYER_READY_TO_OBSERVE && player.mind)
|
||||
//Break chain since this has a sleep input in it
|
||||
addtimer(CALLBACK(player, /mob/dead/new_player.proc/make_me_an_observer), 1)
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/load_mode()
|
||||
var/mode = trim(file2text("data/mode.txt"))
|
||||
if(mode)
|
||||
GLOB.master_mode = mode
|
||||
else
|
||||
GLOB.master_mode = "extended"
|
||||
log_game("Saved mode is '[GLOB.master_mode]'")
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/save_mode(the_mode)
|
||||
var/F = file("data/mode.txt")
|
||||
fdel(F)
|
||||
WRITE_FILE(F, the_mode)
|
||||
|
||||
/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 class='boldannounce'>An admin has delayed the round end.</span>")
|
||||
return
|
||||
|
||||
to_chat(world, "<span class='boldannounce'>Rebooting World in [DisplayTimeText(delay)]. [reason]</span>")
|
||||
|
||||
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 class='boldannounce'>Reboot was cancelled by an admin.</span>")
|
||||
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 class='info'>Round statistics and logs can be viewed <a href=\"[statspage][GLOB.round_id]\">at this website!</a></span>")
|
||||
else if(gamelogloc)
|
||||
to_chat(world, "<span class='info'>Round logs can be located <a href=\"[gamelogloc]\">at this website!</a></span>")
|
||||
|
||||
log_game("<span class='boldannounce'>Rebooting World. [reason]</span>")
|
||||
|
||||
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 = pick(\
|
||||
'sound/roundend/newroundsexy.ogg',
|
||||
'sound/roundend/apcdestroyed.ogg',
|
||||
'sound/roundend/bangindonk.ogg',
|
||||
'sound/roundend/leavingtg.ogg',
|
||||
'sound/roundend/its_only_game.ogg',
|
||||
'sound/roundend/yeehaw.ogg',
|
||||
'sound/roundend/disappointed.ogg',
|
||||
'sound/roundend/gondolabridge.ogg',
|
||||
'sound/roundend/haveabeautifultime.ogg'\
|
||||
)
|
||||
|
||||
SEND_SOUND(world, sound(round_end_sound))
|
||||
text2file(login_music, "data/last_round_lobby_music.txt")
|
||||
@@ -0,0 +1,206 @@
|
||||
SUBSYSTEM_DEF(traumas)
|
||||
name = "Traumas"
|
||||
flags = SS_NO_FIRE
|
||||
var/list/phobia_types
|
||||
var/list/phobia_words
|
||||
var/list/phobia_mobs
|
||||
var/list/phobia_objs
|
||||
var/list/phobia_turfs
|
||||
var/list/phobia_species
|
||||
|
||||
#define PHOBIA_FILE "phobia.json"
|
||||
|
||||
/datum/controller/subsystem/traumas/Initialize()
|
||||
//phobia types is to pull from randomly for brain traumas, e.g. conspiracies is for special assignment only
|
||||
phobia_types = list("spiders", "space", "security", "clowns", "greytide", "lizards",
|
||||
"skeletons", "snakes", "robots", "doctors", "authority", "the supernatural",
|
||||
"aliens", "strangers", "birds", "falling", "anime", "mimes", "cats", "syndicate",
|
||||
"eye"
|
||||
)
|
||||
|
||||
phobia_words = list("spiders" = strings(PHOBIA_FILE, "spiders"),
|
||||
"space" = strings(PHOBIA_FILE, "space"),
|
||||
"security" = strings(PHOBIA_FILE, "security"),
|
||||
"clowns" = strings(PHOBIA_FILE, "clowns"),
|
||||
"greytide" = strings(PHOBIA_FILE, "greytide"),
|
||||
"lizards" = strings(PHOBIA_FILE, "lizards"),
|
||||
"skeletons" = strings(PHOBIA_FILE, "skeletons"),
|
||||
"snakes" = strings(PHOBIA_FILE, "snakes"),
|
||||
"robots" = strings(PHOBIA_FILE, "robots"),
|
||||
"doctors" = strings(PHOBIA_FILE, "doctors"),
|
||||
"authority" = strings(PHOBIA_FILE, "authority"),
|
||||
"the supernatural" = strings(PHOBIA_FILE, "the supernatural"),
|
||||
"aliens" = strings(PHOBIA_FILE, "aliens"),
|
||||
"strangers" = strings(PHOBIA_FILE, "strangers"),
|
||||
"conspiracies" = strings(PHOBIA_FILE, "conspiracies"),
|
||||
"birds" = strings(PHOBIA_FILE, "birds"),
|
||||
"falling" = strings(PHOBIA_FILE, "falling"),
|
||||
"anime" = strings(PHOBIA_FILE, "anime"),
|
||||
"mimes" = strings(PHOBIA_FILE, "mimes"),
|
||||
"cats" = strings(PHOBIA_FILE, "cats"),
|
||||
"syndicate"= strings(PHOBIA_FILE, "syndicate"),
|
||||
"eye" = strings(PHOBIA_FILE, "eye")
|
||||
)
|
||||
|
||||
phobia_mobs = list("spiders" = typecacheof(list(/mob/living/simple_animal/hostile/poison/giant_spider)),
|
||||
"security" = typecacheof(list(/mob/living/simple_animal/bot/secbot, /mob/living/simple_animal/bot/ed209)),
|
||||
"lizards" = typecacheof(list(/mob/living/simple_animal/hostile/lizard)),
|
||||
"skeletons" = typecacheof(list(/mob/living/simple_animal/hostile/skeleton)),
|
||||
"snakes" = typecacheof(list(/mob/living/simple_animal/hostile/retaliate/poison/snake)),
|
||||
"robots" = typecacheof(list(/mob/living/silicon/robot, /mob/living/silicon/ai,
|
||||
/mob/living/simple_animal/drone, /mob/living/simple_animal/bot, /mob/living/simple_animal/hostile/swarmer, /mob/living/simple_animal/bot/honkbot)),
|
||||
"doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)),
|
||||
"the supernatural" = typecacheof(list(/mob/living/simple_animal/hostile/construct,
|
||||
/mob/living/simple_animal/hostile/clockwork, /mob/living/simple_animal/drone/cogscarab,
|
||||
/mob/living/simple_animal/revenant, /mob/living/simple_animal/shade)),
|
||||
"aliens" = typecacheof(list(/mob/living/carbon/alien, /mob/living/simple_animal/slime)),
|
||||
"conspiracies" = typecacheof(list(/mob/living/simple_animal/bot/secbot, /mob/living/simple_animal/bot/ed209, /mob/living/simple_animal/drone,
|
||||
/mob/living/simple_animal/pet/penguin)),
|
||||
"birds" = typecacheof(list(/mob/living/simple_animal/parrot, /mob/living/simple_animal/chick, /mob/living/simple_animal/chicken,
|
||||
/mob/living/simple_animal/pet/penguin)),
|
||||
"anime" = typecacheof(list(/mob/living/simple_animal/hostile/guardian)),
|
||||
"cats"= typecacheof(list(/mob/living/simple_animal/mouse, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/hostile/cat_butcherer)),
|
||||
"syndicate" = typecacheof(list(/mob/living/simple_animal/hostile/syndicate, /mob/living/simple_animal/hostile/viscerator, /mob/living/simple_animal/hostile/carp/cayenne, /mob/living/silicon/robot/modules/syndicate)),
|
||||
"eye" = typecacheof(list(/mob/living/simple_animal/hostile/asteroid/basilisk/watcher, /mob/living/simple_animal/hostile/carp/eyeball))
|
||||
)
|
||||
|
||||
|
||||
phobia_objs = list("snakes" = typecacheof(list(/obj/item/rod_of_asclepius)),
|
||||
|
||||
"spiders" = typecacheof(list(/obj/structure/spider)),
|
||||
|
||||
"security" = typecacheof(list(/obj/item/clothing/under/rank/security, /obj/item/clothing/under/rank/warden,
|
||||
/obj/item/clothing/under/rank/head_of_security, /obj/item/clothing/under/rank/det,
|
||||
/obj/item/melee/baton, /obj/item/gun/energy/taser, /obj/item/restraints/handcuffs,
|
||||
/obj/machinery/door/airlock/security, /obj/effect/hallucination/simple/securitron)),
|
||||
|
||||
"clowns" = typecacheof(list(/obj/item/clothing/under/rank/clown, /obj/item/clothing/shoes/clown_shoes,
|
||||
/obj/item/clothing/mask/gas/clown_hat, /obj/item/instrument/bikehorn,
|
||||
/obj/item/pda/clown, /obj/item/grown/bananapeel)),
|
||||
|
||||
"greytide" = typecacheof(list(/obj/item/clothing/under/color/grey, /obj/item/melee/baton/cattleprod,
|
||||
/obj/item/twohanded/spear, /obj/item/clothing/mask/gas)),
|
||||
|
||||
"lizards" = typecacheof(list(/obj/item/toy/plush/lizardplushie, /obj/item/reagent_containers/food/snacks/kebab/tail,
|
||||
/obj/item/organ/tail/lizard, /obj/item/reagent_containers/food/drinks/bottle/lizardwine)),
|
||||
|
||||
"skeletons" = typecacheof(list(/obj/item/organ/tongue/bone, /obj/item/clothing/suit/armor/bone, /obj/item/stack/sheet/bone,
|
||||
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton,
|
||||
/obj/effect/decal/remains/human)),
|
||||
"conspiracies" = typecacheof(list(/obj/item/clothing/under/rank/captain, /obj/item/clothing/under/rank/head_of_security,
|
||||
/obj/item/clothing/under/rank/chief_engineer, /obj/item/clothing/under/rank/chief_medical_officer,
|
||||
/obj/item/clothing/under/rank/head_of_personnel, /obj/item/clothing/under/rank/research_director,
|
||||
/obj/item/clothing/under/rank/head_of_security/grey, /obj/item/clothing/under/rank/head_of_security/alt,
|
||||
/obj/item/clothing/under/rank/research_director/alt, /obj/item/clothing/under/rank/research_director/turtleneck,
|
||||
/obj/item/clothing/under/captainparade, /obj/item/clothing/under/hosparademale, /obj/item/clothing/under/hosparadefem,
|
||||
/obj/item/clothing/head/helmet/abductor, /obj/item/clothing/suit/armor/abductor/vest, /obj/item/abductor_baton,
|
||||
/obj/item/storage/belt/military/abductor, /obj/item/gun/energy/alien, /obj/item/abductor/silencer,
|
||||
/obj/item/abductor/gizmo, /obj/item/clothing/under/rank/centcom_officer,
|
||||
/obj/item/clothing/suit/space/hardsuit/ert, /obj/item/clothing/suit/space/hardsuit/ert/sec,
|
||||
/obj/item/clothing/suit/space/hardsuit/ert/engi, /obj/item/clothing/suit/space/hardsuit/ert/med,
|
||||
/obj/item/clothing/suit/space/hardsuit/deathsquad, /obj/item/clothing/head/helmet/space/hardsuit/deathsquad,
|
||||
/obj/machinery/door/airlock/centcom)),
|
||||
"robots" = typecacheof(list(/obj/machinery/computer/upload, /obj/item/aiModule/, /obj/machinery/recharge_station,
|
||||
/obj/item/aicard, /obj/item/deactivated_swarmer, /obj/effect/mob_spawn/swarmer)),
|
||||
|
||||
"doctors" = typecacheof(list(/obj/item/clothing/under/rank/medical, /obj/item/clothing/under/rank/chemist,
|
||||
/obj/item/clothing/under/rank/nursesuit, /obj/item/clothing/under/rank/chief_medical_officer,
|
||||
/obj/item/reagent_containers/syringe, /obj/item/reagent_containers/pill/, /obj/item/reagent_containers/hypospray,
|
||||
/obj/item/storage/firstaid, /obj/item/storage/pill_bottle, /obj/item/healthanalyzer,
|
||||
/obj/structure/sign/departments/medbay, /obj/machinery/door/airlock/medical, /obj/machinery/sleeper,
|
||||
/obj/machinery/dna_scannernew, /obj/machinery/atmospherics/components/unary/cryo_cell, /obj/item/surgical_drapes,
|
||||
/obj/item/retractor, /obj/item/hemostat, /obj/item/cautery, /obj/item/surgicaldrill, /obj/item/scalpel, /obj/item/circular_saw,
|
||||
/obj/item/clothing/suit/bio_suit/plaguedoctorsuit, /obj/item/clothing/head/plaguedoctorhat, /obj/item/clothing/mask/gas/plaguedoctor)),
|
||||
|
||||
"authority" = typecacheof(list(/obj/item/clothing/under/rank/captain, /obj/item/clothing/under/rank/head_of_personnel,
|
||||
/obj/item/clothing/under/rank/head_of_security, /obj/item/clothing/under/rank/research_director,
|
||||
/obj/item/clothing/under/rank/chief_medical_officer, /obj/item/clothing/under/rank/chief_engineer,
|
||||
/obj/item/clothing/under/rank/centcom_officer, /obj/item/clothing/under/rank/centcom_commander,
|
||||
/obj/item/melee/classic_baton/telescopic, /obj/item/card/id/silver, /obj/item/card/id/gold,
|
||||
/obj/item/card/id/captains_spare, /obj/item/card/id/centcom, /obj/machinery/door/airlock/command)),
|
||||
|
||||
"the supernatural" = typecacheof(list(/obj/structure/destructible/cult, /obj/item/tome,
|
||||
/obj/item/melee/cultblade, /obj/item/twohanded/required/cult_bastard, /obj/item/restraints/legcuffs/bola/cult,
|
||||
/obj/item/clothing/suit/cultrobes, /obj/item/clothing/suit/space/hardsuit/cult,
|
||||
/obj/item/clothing/suit/hooded/cultrobes, /obj/item/clothing/head/hooded/cult_hoodie, /obj/effect/rune,
|
||||
/obj/item/stack/sheet/runed_metal, /obj/machinery/door/airlock/cult, /obj/singularity/narsie,
|
||||
/obj/item/soulstone,
|
||||
/obj/structure/destructible/clockwork, /obj/item/clockwork, /obj/item/clothing/suit/armor/clockwork,
|
||||
/obj/item/clothing/glasses/judicial_visor, /obj/effect/clockwork/sigil/, /obj/item/stack/tile/brass,
|
||||
/obj/machinery/door/airlock/clockwork,
|
||||
/obj/item/clothing/suit/wizrobe, /obj/item/clothing/head/wizard, /obj/item/spellbook, /obj/item/staff,
|
||||
/obj/item/clothing/suit/space/hardsuit/shielded/wizard, /obj/item/clothing/suit/space/hardsuit/wizard,
|
||||
/obj/item/gun/magic/staff, /obj/item/gun/magic/wand,
|
||||
/obj/item/nullrod, /obj/item/clothing/under/rank/chaplain)),
|
||||
|
||||
"aliens" = typecacheof(list(/obj/item/clothing/mask/facehugger, /obj/item/organ/body_egg/alien_embryo,
|
||||
/obj/structure/alien, /obj/item/toy/toy_xeno,
|
||||
/obj/item/clothing/suit/armor/abductor, /obj/item/abductor, /obj/item/gun/energy/alien,
|
||||
/obj/item/abductor_baton, /obj/item/radio/headset/abductor, /obj/item/scalpel/alien, /obj/item/hemostat/alien,
|
||||
/obj/item/retractor/alien, /obj/item/circular_saw/alien, /obj/item/surgicaldrill/alien, /obj/item/cautery/alien,
|
||||
/obj/item/clothing/head/helmet/abductor, /obj/structure/bed/abductor, /obj/structure/table_frame/abductor,
|
||||
/obj/structure/table/abductor, /obj/structure/table/optable/abductor, /obj/structure/closet/abductor, /obj/item/organ/heart/gland,
|
||||
/obj/machinery/abductor, /obj/item/crowbar/abductor, /obj/item/screwdriver/abductor, /obj/item/weldingtool/abductor,
|
||||
/obj/item/wirecutters/abductor, /obj/item/wrench/abductor, /obj/item/stack/sheet/mineral/abductor)),
|
||||
|
||||
"birds" = typecacheof(list(/obj/item/clothing/mask/gas/plaguedoctor, /obj/item/reagent_containers/food/snacks/cracker,
|
||||
/obj/item/clothing/suit/chickensuit, /obj/item/clothing/head/chicken,
|
||||
/obj/item/clothing/suit/toggle/owlwings, /obj/item/clothing/under/owl, /obj/item/clothing/mask/gas/owl_mask,
|
||||
/obj/item/clothing/under/griffin, /obj/item/clothing/shoes/griffin, /obj/item/clothing/head/griffin,
|
||||
/obj/item/clothing/head/helmet/space/freedom, /obj/item/clothing/suit/space/freedom)),
|
||||
|
||||
"anime" = typecacheof(list(/obj/item/clothing/under/schoolgirl, /obj/item/katana, /obj/item/reagent_containers/food/snacks/sashimi, /obj/item/reagent_containers/food/snacks/chawanmushi,
|
||||
/obj/item/reagent_containers/food/drinks/bottle/sake, /obj/item/throwing_star, /obj/item/clothing/head/kitty/genuine, /obj/item/clothing/suit/space/space_ninja,
|
||||
/obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/twohanded/vibro_weapon,
|
||||
/obj/item/nullrod/scythe/vibro, /obj/item/energy_katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, /obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe)),
|
||||
|
||||
"mimes" = typecacheof(list(/obj/item/pda/mime, /obj/item/clothing/under/rank/mime, /obj/item/clothing/mask/gas/mime,
|
||||
/obj/item/clothing/head/frenchberet, /obj/item/clothing/suit/suspenders, /obj/item/reagent_containers/food/drinks/bottle/bottleofnothing,
|
||||
/obj/item/storage/backpack/mime, /obj/item/reagent_containers/food/snacks/grown/banana/mime,
|
||||
/obj/item/grown/bananapeel/mimanapeel, /obj/item/cartridge/virus/mime, /obj/item/clothing/shoes/sneakers/mime,
|
||||
/obj/item/bedsheet/mime, /obj/item/reagent_containers/food/snacks/burger/mime, /obj/item/clothing/head/beret, /obj/item/clothing/mask/gas/sexymime,
|
||||
/obj/item/clothing/under/sexymime, /obj/item/toy/figure/mime, /obj/item/toy/crayon/mime, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced, /obj/mecha/combat/reticence)),
|
||||
|
||||
"cats" = typecacheof(list(/obj/item/organ/ears/cat, /obj/item/organ/tail/cat, /obj/item/laser_pointer, /obj/item/toy/cattoy, /obj/item/clothing/head/kitty,
|
||||
/obj/item/clothing/head/collectable/kitty, /obj/item/melee/chainofcommand/tailwhip/kitty, /obj/item/stack/sheet/animalhide/cat)),
|
||||
|
||||
"syndicate" = typecacheof(list(/obj/item/stack/tile/mineral/plastitanium, /obj/machinery/computer/shuttle/syndicate, /obj/machinery/computer/shuttle/syndicate/recall, /obj/machinery/computer/shuttle/syndicate/drop_pod, /obj/machinery/computer/camera_advanced/shuttle_docker/syndicate, /obj/machinery/recharge_station,
|
||||
/obj/machinery/porta_turret/syndicate, /obj/structure/closet/syndicate, /obj/machinery/suit_storage_unit/syndicate, /obj/item/clothing/under/syndicate, /obj/item/folder/syndicate, /obj/item/documents/syndicate, /obj/item/clothing/glasses/phantomthief/syndicate, /obj/item/antag_spawner/nuke_ops, /obj/item/storage/box/syndicate,
|
||||
/obj/structure/fluff/empty_sleeper/syndicate, /obj/item/implant/radio/syndicate, /obj/item/clothing/head/helmet/space/syndicate, /obj/machinery/nuclearbomb/syndicate, /obj/item/grenade/syndieminibomb, /obj/item/storage/backpack/duffelbag/syndie, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver,
|
||||
/obj/item/gun/ballistic/automatic/shotgun/bulldog, /obj/item/gun/ballistic/automatic/c20r, /obj/item/gun/ballistic/automatic/m90, /obj/item/gun/ballistic/automatic/l6_saw, /obj/item/storage/belt/grenade/full, /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate, /obj/item/gun/energy/kinetic_accelerator/crossbow,
|
||||
/obj/item/melee/transforming/energy/sword/saber, /obj/item/twohanded/dualsaber, /obj/item/melee/powerfist, /obj/item/storage/box/syndie_kit, /obj/item/grenade/spawnergrenade/manhacks, /obj/item/grenade/chem_grenade/bioterrorfoam, /obj/item/reagent_containers/spray/chemsprayer/bioterror, /obj/item/ammo_box/magazine/m10mm,
|
||||
/obj/item/ammo_box/magazine/pistolm9mm, /obj/item/ammo_box/a357, /obj/item/ammo_box/magazine/m12g, /obj/item/ammo_box/magazine/mm195x129, /obj/item/antag_spawner/nuke_ops, /obj/mecha/combat/gygax/dark, /obj/mecha/combat/marauder/mauler, /obj/item/soap/syndie, /obj/item/gun/syringe/syndicate, /obj/item/cartridge/virus/syndicate,
|
||||
/obj/item/cartridge/virus/frame, /obj/item/chameleon, /obj/item/storage/box/syndie_kit/cutouts, /obj/item/clothing/suit/space/hardsuit/syndi, /obj/item/card/emag, /obj/item/storage/toolbox/syndicate, /obj/item/storage/book/bible/syndicate, /obj/item/encryptionkey/binary, /obj/item/encryptionkey/syndicate, /obj/item/aiModule/syndicate,
|
||||
/obj/item/clothing/shoes/magboots/syndie, /obj/item/powersink, /obj/item/sbeacondrop, /obj/item/sbeacondrop/bomb, /obj/item/syndicatedetonator, /obj/item/shield/energy, /obj/item/assault_pod, /obj/item/slimepotion/slime/sentience/nuclear, /obj/item/stack/telecrystal, /obj/item/jammer, /obj/item/codespeak_manual/unlimited,
|
||||
/obj/item/toy/cards/deck/syndicate, /obj/item/storage/secure/briefcase/syndie, /obj/item/storage/fancy/cigarettes/cigpack_syndicate, /obj/item/toy/syndicateballoon, /obj/item/clothing/gloves/rapid, /obj/item/paper/fluff/ruins/thederelict/syndie_mission, /obj/item/organ/cyberimp/eyes/hud/security/syndicate, /obj/item/clothing/head/HoS/syndicate,
|
||||
/obj/machinery/computer/pod/old/syndicate, /obj/machinery/vending/medical/syndicate_access, /obj/item/mmi/syndie, /obj/item/target/syndicate, /obj/machinery/vending/cigarette/syndicate, /obj/item/robot_module/syndicate, /obj/item/clothing/mask/gas/syndicate, /obj/machinery/power/singularity_beacon/syndicate, /obj/item/clothing/head/syndicatefake,
|
||||
/obj/item/radio/headset/syndicate, /obj/item/gun/ballistic/automatic/pistol/antitank/syndicate, /obj/item/pda/syndicate, /obj/item/clothing/suit/armor/vest/capcarapace/syndicate, /obj/item/gun/ballistic/automatic/flechette, /obj/item/ammo_box/magazine/flechette, /obj/item/clothing/suit/toggle/lawyer/black/syndie, /obj/item/melee/transforming/energy/sword/cx/traitor,
|
||||
/obj/structure/sign/poster/contraband/syndicate_pistol, /obj/structure/sign/poster/contraband/syndicate_recruitment, /obj/item/bedsheet/syndie, /obj/item/borg/upgrade/syndicate, /obj/item/tank/jetpack/oxygen/harness, /obj/item/firing_pin/implant/pindicate, /obj/item/reagent_containers/glass/bottle/traitor, /obj/item/storage/belt/military,
|
||||
/obj/item/twohanded/shockpaddles/syndicate, /obj/item/clothing/mask/cigarette/syndicate, /obj/item/toy/plush/nukeplushie)),
|
||||
|
||||
"eye" = typecacheof(list(/obj/item/organ/eyes, /obj/item/reagent_containers/syringe))
|
||||
)
|
||||
|
||||
phobia_turfs = list("space" = typecacheof(list(/turf/open/space, /turf/open/floor/holofloor/space, /turf/open/floor/fakespace)),
|
||||
"the supernatural" = typecacheof(list(/turf/open/floor/clockwork, /turf/closed/wall/clockwork,
|
||||
/turf/open/floor/plasteel/cult, /turf/closed/wall/mineral/cult)),
|
||||
"aliens" = typecacheof(list(/turf/open/floor/plating/abductor, /turf/open/floor/plating/abductor2,
|
||||
/turf/open/floor/mineral/abductor, /turf/closed/wall/mineral/abductor)),
|
||||
"falling" = typecacheof(list(/turf/open/chasm, /turf/open/floor/fakepit)),
|
||||
"syndicate" = typecacheof(list(/turf/closed/wall/mineral/plastitanium, /turf/open/floor/mineral/plastitanium, /turf/open/floor/plasteel/shuttle/red/syndicate))
|
||||
)
|
||||
|
||||
phobia_species = list("lizards" = typecacheof(list(/datum/species/lizard)),
|
||||
"skeletons" = typecacheof(list(/datum/species/skeleton, /datum/species/plasmaman)),
|
||||
"conspiracies" = typecacheof(list(/datum/species/abductor, /datum/species/lizard, /datum/species/synth, /datum/species/corporate)),
|
||||
"robots" = typecacheof(list(/datum/species/android, /datum/species/synth)),
|
||||
"the supernatural" = typecacheof(list(/datum/species/golem/clockwork, /datum/species/golem/runic)),
|
||||
"aliens" = typecacheof(list(/datum/species/abductor, /datum/species/jelly, /datum/species/pod, /datum/species/shadow)),
|
||||
"anime" = typecacheof(list(/datum/species/human/felinid)),
|
||||
"cats" = typecacheof(list(/datum/species/human/felinid)),
|
||||
"syndicate" = typecacheof(list(/datum/species/corporate, /datum/species/zombie/infectious))
|
||||
)
|
||||
|
||||
return ..()
|
||||
|
||||
#undef PHOBIA_FILE
|
||||
@@ -0,0 +1,73 @@
|
||||
SUBSYSTEM_DEF(vis_overlays)
|
||||
name = "Vis contents overlays"
|
||||
wait = 1 MINUTES
|
||||
priority = FIRE_PRIORITY_VIS
|
||||
init_order = INIT_ORDER_VIS
|
||||
|
||||
var/list/vis_overlay_cache
|
||||
var/list/currentrun
|
||||
|
||||
/datum/controller/subsystem/vis_overlays/Initialize()
|
||||
vis_overlay_cache = list()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/vis_overlays/fire(resumed = FALSE)
|
||||
if(!resumed)
|
||||
currentrun = vis_overlay_cache.Copy()
|
||||
var/list/current_run = currentrun
|
||||
|
||||
while(current_run.len)
|
||||
var/key = current_run[current_run.len]
|
||||
var/obj/effect/overlay/vis/overlay = current_run[key]
|
||||
current_run.len--
|
||||
if(!overlay.unused && !length(overlay.vis_locs))
|
||||
overlay.unused = world.time
|
||||
else if(overlay.unused && overlay.unused + overlay.cache_expiration < world.time)
|
||||
vis_overlay_cache -= key
|
||||
qdel(overlay)
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
//the "thing" var can be anything with vis_contents which includes images
|
||||
/datum/controller/subsystem/vis_overlays/proc/add_vis_overlay(atom/movable/thing, icon, iconstate, layer, plane, dir, alpha=255)
|
||||
. = "[icon]|[iconstate]|[layer]|[plane]|[dir]|[alpha]"
|
||||
var/obj/effect/overlay/vis/overlay = vis_overlay_cache[.]
|
||||
if(!overlay)
|
||||
overlay = new
|
||||
overlay.icon = icon
|
||||
overlay.icon_state = iconstate
|
||||
overlay.layer = layer
|
||||
overlay.plane = plane
|
||||
overlay.dir = dir
|
||||
overlay.alpha = alpha
|
||||
vis_overlay_cache[.] = overlay
|
||||
else
|
||||
overlay.unused = 0
|
||||
thing.vis_contents += overlay
|
||||
|
||||
if(!isatom(thing)) // Automatic rotation is not supported on non atoms
|
||||
return
|
||||
|
||||
if(!thing.managed_vis_overlays)
|
||||
thing.managed_vis_overlays = list(overlay)
|
||||
RegisterSignal(thing, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_vis_overlay)
|
||||
else
|
||||
thing.managed_vis_overlays += overlay
|
||||
|
||||
/datum/controller/subsystem/vis_overlays/proc/remove_vis_overlay(atom/movable/thing, list/overlays)
|
||||
thing.vis_contents -= overlays
|
||||
if(!isatom(thing))
|
||||
return
|
||||
thing.managed_vis_overlays -= overlays
|
||||
if(!length(thing.managed_vis_overlays))
|
||||
thing.managed_vis_overlays = null
|
||||
UnregisterSignal(thing, COMSIG_ATOM_DIR_CHANGE)
|
||||
|
||||
/datum/controller/subsystem/vis_overlays/proc/rotate_vis_overlay(atom/thing, old_dir, new_dir)
|
||||
var/rotation = dir2angle(old_dir) - dir2angle(new_dir)
|
||||
var/list/overlays_to_remove = list()
|
||||
for(var/i in thing.managed_vis_overlays)
|
||||
var/obj/effect/overlay/vis/overlay = i
|
||||
add_vis_overlay(thing, overlay.icon, overlay.icon_state, overlay.layer, overlay.plane, turn(overlay.dir, rotation))
|
||||
overlays_to_remove += overlay
|
||||
remove_vis_overlay(thing, overlays_to_remove)
|
||||
@@ -0,0 +1,416 @@
|
||||
SUBSYSTEM_DEF(vote)
|
||||
name = "Vote"
|
||||
wait = 10
|
||||
|
||||
flags = SS_KEEP_TIMING|SS_NO_INIT
|
||||
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
||||
|
||||
var/initiator = null
|
||||
var/started_time = null
|
||||
var/time_remaining = 0
|
||||
var/mode = null
|
||||
var/question = null
|
||||
var/list/choices = list()
|
||||
var/list/voted = list()
|
||||
var/list/voting = list()
|
||||
var/list/generated_actions = list()
|
||||
|
||||
var/obfuscated = FALSE//CIT CHANGE - adds obfuscated/admin-only votes
|
||||
|
||||
var/list/stored_gamemode_votes = list() //Basically the last voted gamemode is stored here for end-of-round use.
|
||||
|
||||
/datum/controller/subsystem/vote/fire() //called by master_controller
|
||||
if(mode)
|
||||
time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10)
|
||||
|
||||
if(time_remaining < 0)
|
||||
result()
|
||||
for(var/client/C in voting)
|
||||
C << browse(null, "window=vote;can_close=0")
|
||||
reset()
|
||||
else
|
||||
var/datum/browser/client_popup
|
||||
for(var/client/C in voting)
|
||||
client_popup = new(C, "vote", "Voting Panel")
|
||||
client_popup.set_window_options("can_close=0")
|
||||
client_popup.set_content(interface(C))
|
||||
client_popup.open(0)
|
||||
|
||||
|
||||
/datum/controller/subsystem/vote/proc/reset()
|
||||
initiator = null
|
||||
time_remaining = 0
|
||||
mode = null
|
||||
question = null
|
||||
choices.Cut()
|
||||
voted.Cut()
|
||||
voting.Cut()
|
||||
obfuscated = FALSE //CIT CHANGE - obfuscated votes
|
||||
remove_action_buttons()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/get_result()
|
||||
//get the highest number of votes
|
||||
var/greatest_votes = 0
|
||||
var/total_votes = 0
|
||||
for(var/option in choices)
|
||||
var/votes = choices[option]
|
||||
total_votes += votes
|
||||
if(votes > greatest_votes)
|
||||
greatest_votes = votes
|
||||
//default-vote for everyone who didn't vote
|
||||
if(!CONFIG_GET(flag/default_no_vote) && choices.len)
|
||||
var/list/non_voters = GLOB.directory.Copy()
|
||||
non_voters -= voted
|
||||
for (var/non_voter_ckey in non_voters)
|
||||
var/client/C = non_voters[non_voter_ckey]
|
||||
if (!C || C.is_afk())
|
||||
non_voters -= non_voter_ckey
|
||||
if(non_voters.len > 0)
|
||||
if(mode == "restart")
|
||||
choices["Continue Playing"] += non_voters.len
|
||||
if(choices["Continue Playing"] >= greatest_votes)
|
||||
greatest_votes = choices["Continue Playing"]
|
||||
else if(mode == "gamemode")
|
||||
if(GLOB.master_mode in choices)
|
||||
choices[GLOB.master_mode] += non_voters.len
|
||||
if(choices[GLOB.master_mode] >= greatest_votes)
|
||||
greatest_votes = choices[GLOB.master_mode]
|
||||
//get all options with that many votes and return them in a list
|
||||
. = list()
|
||||
if(greatest_votes)
|
||||
for(var/option in choices)
|
||||
if(choices[option] == greatest_votes)
|
||||
. += option
|
||||
return .
|
||||
|
||||
/datum/controller/subsystem/vote/proc/announce_result()
|
||||
var/list/winners = get_result()
|
||||
var/text
|
||||
var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic"
|
||||
if(winners.len > 0)
|
||||
if(question)
|
||||
text += "<b>[question]</b>"
|
||||
else
|
||||
text += "<b>[capitalize(mode)] Vote</b>"
|
||||
if(was_roundtype_vote)
|
||||
stored_gamemode_votes = list()
|
||||
for(var/i=1,i<=choices.len,i++)
|
||||
var/votes = choices[choices[i]]
|
||||
if(!votes)
|
||||
votes = 0
|
||||
if(was_roundtype_vote)
|
||||
stored_gamemode_votes[choices[i]] = votes
|
||||
text += "\n<b>[choices[i]]:</b> [obfuscated ? "???" : votes]" //CIT CHANGE - adds obfuscated votes
|
||||
if(mode != "custom")
|
||||
if(winners.len > 1 && !obfuscated) //CIT CHANGE - adds obfuscated votes
|
||||
text = "\n<b>Vote Tied Between:</b>"
|
||||
for(var/option in winners)
|
||||
text += "\n\t[option]"
|
||||
. = pick(winners)
|
||||
text += "\n<b>Vote Result: [obfuscated ? "???" : .]</b>" //CIT CHANGE - adds obfuscated votes
|
||||
else
|
||||
text += "\n<b>Did not vote:</b> [GLOB.clients.len-voted.len]"
|
||||
else
|
||||
text += "<b>Vote Result: Inconclusive - No Votes!</b>"
|
||||
log_vote(text)
|
||||
remove_action_buttons()
|
||||
to_chat(world, "\n<font color='purple'>[text]</font>")
|
||||
if(obfuscated) //CIT CHANGE - adds obfuscated votes. this messages admins with the vote's true results
|
||||
var/admintext = "Obfuscated results"
|
||||
for(var/i=1,i<=choices.len,i++)
|
||||
var/votes = choices[choices[i]]
|
||||
admintext += "\n<b>[choices[i]]:</b> [votes]"
|
||||
message_admins(admintext)
|
||||
return .
|
||||
|
||||
#define PEACE "calm"
|
||||
#define CHAOS "chaotic"
|
||||
|
||||
/datum/controller/subsystem/vote/proc/result()
|
||||
. = announce_result()
|
||||
var/restart = 0
|
||||
if(.)
|
||||
switch(mode)
|
||||
if("roundtype") //CIT CHANGE - adds the roundstart extended/secret vote
|
||||
if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started.
|
||||
return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.")
|
||||
GLOB.master_mode = .
|
||||
SSticker.save_mode(.)
|
||||
message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]")
|
||||
log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].")
|
||||
if("restart")
|
||||
if(. == "Restart Round")
|
||||
restart = 1
|
||||
if("gamemode")
|
||||
if(GLOB.master_mode != .)
|
||||
SSticker.save_mode(.)
|
||||
if(SSticker.HasRoundStarted())
|
||||
restart = 1
|
||||
else
|
||||
GLOB.master_mode = .
|
||||
if("dynamic")
|
||||
if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started.
|
||||
return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.")
|
||||
GLOB.master_mode = "dynamic"
|
||||
var/mean = 0
|
||||
var/voters = 0
|
||||
for(var/client/c in GLOB.clients)
|
||||
var/vote = c.prefs.preferred_chaos
|
||||
if(vote)
|
||||
voters += 1
|
||||
switch(vote)
|
||||
if(CHAOS_NONE)
|
||||
mean -= 0.1
|
||||
if(CHAOS_LOW)
|
||||
mean -= 0.05
|
||||
if(CHAOS_HIGH)
|
||||
mean += 0.05
|
||||
if(CHAOS_MAX)
|
||||
mean += 0.1
|
||||
mean/=voters
|
||||
if(voted.len != 0)
|
||||
mean += (choices[PEACE]*-1+choices[CHAOS])/voted.len
|
||||
GLOB.dynamic_curve_centre = mean*20
|
||||
GLOB.dynamic_curve_width = CLAMP(2-abs(mean*5),0.5,4)
|
||||
to_chat(world,"<span class='boldannounce'>Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width].</span>")
|
||||
log_admin("Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width]")
|
||||
if("map")
|
||||
var/datum/map_config/VM = config.maplist[.]
|
||||
message_admins("The map has been voted for and will change to: [VM.map_name]")
|
||||
log_admin("The map has been voted for and will change to: [VM.map_name]")
|
||||
if(SSmapping.changemap(config.maplist[.]))
|
||||
to_chat(world, "<span class='boldannounce'>The map vote has chosen [VM.map_name] for next round!</span>")
|
||||
if(restart)
|
||||
var/active_admins = 0
|
||||
for(var/client/C in GLOB.admins)
|
||||
if(!C.is_afk() && check_rights_for(C, R_SERVER))
|
||||
active_admins = 1
|
||||
break
|
||||
if(!active_admins)
|
||||
SSticker.Reboot("Restart vote successful.", "restart vote")
|
||||
else
|
||||
to_chat(world, "<span style='boldannounce'>Notice:Restart vote will not restart the server automatically because there are active admins on.</span>")
|
||||
message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.")
|
||||
|
||||
return .
|
||||
|
||||
/datum/controller/subsystem/vote/proc/submit_vote(vote)
|
||||
if(mode)
|
||||
if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder)
|
||||
return 0
|
||||
if(!(usr.ckey in voted))
|
||||
if(vote && 1<=vote && vote<=choices.len)
|
||||
voted += usr.ckey
|
||||
voted[usr.ckey] = vote
|
||||
choices[choices[vote]]++ //check this
|
||||
return vote
|
||||
else if(vote && 1<=vote && vote<=choices.len)
|
||||
choices[choices[voted[usr.ckey]]]--
|
||||
voted[usr.ckey] = vote
|
||||
choices[choices[vote]]++
|
||||
return vote
|
||||
return 0
|
||||
|
||||
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes
|
||||
if(!mode)
|
||||
if(started_time)
|
||||
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
|
||||
if(mode)
|
||||
to_chat(usr, "<span class='warning'>There is already a vote in progress! please wait for it to finish.</span>")
|
||||
return 0
|
||||
|
||||
var/admin = FALSE
|
||||
var/ckey = ckey(initiator_key)
|
||||
if(GLOB.admin_datums[ckey])
|
||||
admin = TRUE
|
||||
|
||||
if(next_allowed_time > world.time && !admin)
|
||||
to_chat(usr, "<span class='warning'>A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!</span>")
|
||||
return 0
|
||||
|
||||
reset()
|
||||
obfuscated = hideresults //CIT CHANGE - adds obfuscated votes
|
||||
switch(vote_type)
|
||||
if("restart")
|
||||
choices.Add("Restart Round","Continue Playing")
|
||||
if("gamemode")
|
||||
choices.Add(config.votable_modes)
|
||||
if("map")
|
||||
var/players = GLOB.clients.len
|
||||
var/list/lastmaps = SSpersistence.saved_maps?.len ? list("[SSmapping.config.map_name]") | SSpersistence.saved_maps : list("[SSmapping.config.map_name]")
|
||||
for(var/M in config.maplist) //This is a typeless loop due to the finnicky nature of keyed lists in this kind of context
|
||||
var/datum/map_config/targetmap = config.maplist[M]
|
||||
if(!istype(targetmap))
|
||||
continue
|
||||
if(!targetmap.voteweight)
|
||||
continue
|
||||
if((targetmap.config_min_users && players < targetmap.config_min_users) || (targetmap.config_max_users && players > targetmap.config_max_users))
|
||||
continue
|
||||
if(targetmap.max_round_search_span && count_occurences_of_value(lastmaps, M, targetmap.max_round_search_span) >= targetmap.max_rounds_played)
|
||||
continue
|
||||
choices |= M
|
||||
if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote
|
||||
choices.Add("secret", "extended")
|
||||
if("dynamic")
|
||||
choices.Add(PEACE,CHAOS)
|
||||
if("custom")
|
||||
question = stripped_input(usr,"What is the vote for?")
|
||||
if(!question)
|
||||
return 0
|
||||
for(var/i=1,i<=10,i++)
|
||||
var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish"))
|
||||
if(!option || mode || !usr.client)
|
||||
break
|
||||
choices.Add(option)
|
||||
else
|
||||
return 0
|
||||
mode = vote_type
|
||||
initiator = initiator_key
|
||||
started_time = world.time
|
||||
var/text = "[capitalize(mode)] vote started by [initiator]."
|
||||
if(mode == "custom")
|
||||
text += "\n[question]"
|
||||
log_vote(text)
|
||||
var/vp = CONFIG_GET(number/vote_period)
|
||||
to_chat(world, "\n<font color='purple'><b>[text]</b>\nType <b>vote</b> or click <a href='?src=[REF(src)]'>here</a> to place your votes.\nYou have [DisplayTimeText(vp)] to vote.</font>")
|
||||
time_remaining = round(vp/10)
|
||||
for(var/c in GLOB.clients)
|
||||
SEND_SOUND(c, sound('sound/misc/server-ready.ogg'))
|
||||
var/client/C = c
|
||||
var/datum/action/vote/V = new
|
||||
if(question)
|
||||
V.name = "Vote: [question]"
|
||||
C.player_details.player_actions += V
|
||||
V.Grant(C.mob)
|
||||
generated_actions += V
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/datum/controller/subsystem/vote/proc/interface(client/C)
|
||||
if(!C)
|
||||
return
|
||||
var/admin = 0
|
||||
var/trialmin = 0
|
||||
if(C.holder)
|
||||
admin = 1
|
||||
if(check_rights_for(C, R_ADMIN))
|
||||
trialmin = 1
|
||||
voting |= C
|
||||
|
||||
if(mode)
|
||||
if(question)
|
||||
. += "<h2>Vote: '[question]'</h2>"
|
||||
else
|
||||
. += "<h2>Vote: [capitalize(mode)]</h2>"
|
||||
. += "Time Left: [time_remaining] s<hr><ul>"
|
||||
for(var/i=1,i<=choices.len,i++)
|
||||
var/votes = choices[choices[i]]
|
||||
var/ivotedforthis = ((C.ckey in voted) && (voted[C.ckey] == i) ? TRUE : FALSE)
|
||||
if(!votes)
|
||||
votes = 0
|
||||
. += "<li>[ivotedforthis ? "<b>" : ""]<a href='?src=[REF(src)];vote=[i]'>[choices[i]]</a> ([obfuscated ? (admin ? "??? ([votes])" : "???") : votes] votes)[ivotedforthis ? "</b>" : ""]</li>" // CIT CHANGE - adds obfuscated votes
|
||||
. += "</ul><hr>"
|
||||
if(admin)
|
||||
. += "(<a href='?src=[REF(src)];vote=cancel'>Cancel Vote</a>) "
|
||||
else
|
||||
. += "<h2>Start a vote:</h2><hr><ul><li>"
|
||||
//restart
|
||||
var/avr = CONFIG_GET(flag/allow_vote_restart)
|
||||
if(trialmin || avr)
|
||||
. += "<a href='?src=[REF(src)];vote=restart'>Restart</a>"
|
||||
else
|
||||
. += "<font color='grey'>Restart (Disallowed)</font>"
|
||||
if(trialmin)
|
||||
. += "\t(<a href='?src=[REF(src)];vote=toggle_restart'>[avr ? "Allowed" : "Disallowed"]</a>)"
|
||||
. += "</li><li>"
|
||||
//gamemode
|
||||
var/avm = CONFIG_GET(flag/allow_vote_mode)
|
||||
if(trialmin || avm)
|
||||
. += "<a href='?src=[REF(src)];vote=gamemode'>GameMode</a>"
|
||||
else
|
||||
. += "<font color='grey'>GameMode (Disallowed)</font>"
|
||||
if(trialmin)
|
||||
. += "\t(<a href='?src=[REF(src)];vote=toggle_gamemode'>[avm ? "Allowed" : "Disallowed"]</a>)"
|
||||
|
||||
. += "</li>"
|
||||
//custom
|
||||
if(trialmin)
|
||||
. += "<li><a href='?src=[REF(src)];vote=custom'>Custom</a></li>"
|
||||
. += "</ul><hr>"
|
||||
. += "<a href='?src=[REF(src)];vote=close' style='position:absolute;right:50px'>Close</a>"
|
||||
return .
|
||||
|
||||
|
||||
/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc)
|
||||
if(!usr || !usr.client)
|
||||
return //not necessary but meh...just in-case somebody does something stupid
|
||||
switch(href_list["vote"])
|
||||
if("close")
|
||||
voting -= usr.client
|
||||
usr << browse(null, "window=vote")
|
||||
return
|
||||
if("cancel")
|
||||
if(usr.client.holder)
|
||||
reset()
|
||||
if("toggle_restart")
|
||||
if(usr.client.holder)
|
||||
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
|
||||
if("toggle_gamemode")
|
||||
if(usr.client.holder)
|
||||
CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode))
|
||||
if("restart")
|
||||
if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder)
|
||||
initiate_vote("restart",usr.key)
|
||||
if("gamemode")
|
||||
if(CONFIG_GET(flag/allow_vote_mode) || usr.client.holder)
|
||||
initiate_vote("gamemode",usr.key)
|
||||
if("custom")
|
||||
if(usr.client.holder)
|
||||
initiate_vote("custom",usr.key)
|
||||
else
|
||||
submit_vote(round(text2num(href_list["vote"])))
|
||||
usr.vote()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/remove_action_buttons()
|
||||
for(var/v in generated_actions)
|
||||
var/datum/action/vote/V = v
|
||||
if(!QDELETED(V))
|
||||
V.remove_from_client()
|
||||
V.Remove(V.owner)
|
||||
generated_actions = list()
|
||||
|
||||
/mob/verb/vote()
|
||||
set category = "OOC"
|
||||
set name = "Vote"
|
||||
|
||||
var/datum/browser/popup = new(src, "vote", "Voting Panel")
|
||||
popup.set_window_options("can_close=0")
|
||||
popup.set_content(SSvote.interface(client))
|
||||
popup.open(0)
|
||||
|
||||
/datum/action/vote
|
||||
name = "Vote!"
|
||||
button_icon_state = "vote"
|
||||
|
||||
/datum/action/vote/Trigger()
|
||||
if(owner)
|
||||
owner.vote()
|
||||
remove_from_client()
|
||||
Remove(owner)
|
||||
|
||||
/datum/action/vote/IsAvailable()
|
||||
return 1
|
||||
|
||||
/datum/action/vote/proc/remove_from_client()
|
||||
if(!owner)
|
||||
return
|
||||
if(owner.client)
|
||||
owner.client.player_details.player_actions -= src
|
||||
else if(owner.ckey)
|
||||
var/datum/player_details/P = GLOB.player_details[owner.ckey]
|
||||
if(P)
|
||||
P.player_actions -= src
|
||||
|
||||
#undef PEACE
|
||||
#undef CHAOS
|
||||
@@ -0,0 +1,83 @@
|
||||
#define STARTUP_STAGE 1
|
||||
#define MAIN_STAGE 2
|
||||
#define WIND_DOWN_STAGE 3
|
||||
#define END_STAGE 4
|
||||
|
||||
//Used for all kinds of weather, ex. lavaland ash storms.
|
||||
SUBSYSTEM_DEF(weather)
|
||||
name = "Weather"
|
||||
flags = SS_BACKGROUND
|
||||
wait = 10
|
||||
runlevels = RUNLEVEL_GAME
|
||||
var/list/processing = list()
|
||||
var/list/eligible_zlevels = list()
|
||||
var/list/next_hit_by_zlevel = list() //Used by barometers to know when the next storm is coming
|
||||
|
||||
/datum/controller/subsystem/weather/fire()
|
||||
// process active weather
|
||||
for(var/V in processing)
|
||||
var/datum/weather/W = V
|
||||
if(W.aesthetic || W.stage != MAIN_STAGE)
|
||||
continue
|
||||
for(var/i in GLOB.mob_living_list)
|
||||
var/mob/living/L = i
|
||||
if(W.can_weather_act(L))
|
||||
W.weather_act(L)
|
||||
|
||||
// start random weather on relevant levels
|
||||
for(var/z in eligible_zlevels)
|
||||
var/possible_weather = eligible_zlevels[z]
|
||||
var/datum/weather/W = pickweight(possible_weather)
|
||||
run_weather(W, list(text2num(z)))
|
||||
eligible_zlevels -= z
|
||||
var/randTime = rand(3000, 6000)
|
||||
addtimer(CALLBACK(src, .proc/make_eligible, z, possible_weather), randTime + initial(W.weather_duration_upper), TIMER_UNIQUE) //Around 5-10 minutes between weathers
|
||||
next_hit_by_zlevel["[z]"] = world.time + randTime + initial(W.telegraph_duration)
|
||||
|
||||
/datum/controller/subsystem/weather/Initialize(start_timeofday)
|
||||
for(var/V in subtypesof(/datum/weather))
|
||||
var/datum/weather/W = V
|
||||
var/probability = initial(W.probability)
|
||||
var/target_trait = initial(W.target_trait)
|
||||
|
||||
// any weather with a probability set may occur at random
|
||||
if (probability)
|
||||
for(var/z in SSmapping.levels_by_trait(target_trait))
|
||||
LAZYINITLIST(eligible_zlevels["[z]"])
|
||||
eligible_zlevels["[z]"][W] = probability
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/weather/proc/run_weather(datum/weather/weather_datum_type, z_levels)
|
||||
if (istext(weather_datum_type))
|
||||
for (var/V in subtypesof(/datum/weather))
|
||||
var/datum/weather/W = V
|
||||
if (initial(W.name) == weather_datum_type)
|
||||
weather_datum_type = V
|
||||
break
|
||||
if (!ispath(weather_datum_type, /datum/weather))
|
||||
CRASH("run_weather called with invalid weather_datum_type: [weather_datum_type || "null"]")
|
||||
return
|
||||
|
||||
if (isnull(z_levels))
|
||||
z_levels = SSmapping.levels_by_trait(initial(weather_datum_type.target_trait))
|
||||
else if (isnum(z_levels))
|
||||
z_levels = list(z_levels)
|
||||
else if (!islist(z_levels))
|
||||
CRASH("run_weather called with invalid z_levels: [z_levels || "null"]")
|
||||
return
|
||||
|
||||
var/datum/weather/W = new weather_datum_type(z_levels)
|
||||
W.telegraph()
|
||||
|
||||
/datum/controller/subsystem/weather/proc/make_eligible(z, possible_weather)
|
||||
eligible_zlevels[z] = possible_weather
|
||||
next_hit_by_zlevel["[z]"] = null
|
||||
|
||||
/datum/controller/subsystem/weather/proc/get_weather(z, area/active_area)
|
||||
var/datum/weather/A
|
||||
for(var/V in processing)
|
||||
var/datum/weather/W = V
|
||||
if((z in W.impacted_z_levels) && W.area_type == active_area.type)
|
||||
A = W
|
||||
break
|
||||
return A
|
||||
Reference in New Issue
Block a user