diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
old mode 100755
new mode 100644
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index fa172ab604..1cd32d43ac 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -1,623 +1,623 @@
-#define LINKIFY_READY(string, value) "[string] "
-
-/mob/dead/new_player
- var/ready = 0
- var/spawning = 0//Referenced when you want to delete the new_player later on in the code.
-
- flags_1 = NONE
-
- invisibility = INVISIBILITY_ABSTRACT
-
- density = FALSE
- stat = DEAD
- canmove = FALSE
-
- anchored = TRUE // don't get pushed around
-
- var/mob/living/new_character //for instant transfer once the round is set up
-
- //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles
- var/ineligible_for_roles = FALSE
-
-/mob/dead/new_player/Initialize()
- if(client && SSticker.state == GAME_STATE_STARTUP)
- var/obj/screen/splash/S = new(client, TRUE, TRUE)
- S.Fade(TRUE)
-
- if(length(GLOB.newplayer_start))
- forceMove(pick(GLOB.newplayer_start))
- else
- forceMove(locate(1,1,1))
-
- ComponentInitialize()
-
- . = ..()
-
-/mob/dead/new_player/prepare_huds()
- return
-
-/mob/dead/new_player/proc/new_player_panel()
- var/output = "
Welcome, [client ? client.prefs.real_name : "Unknown User"]
"
- output += "Setup Character
"
-
- if(SSticker.current_state <= GAME_STATE_PREGAME)
- switch(ready)
- if(PLAYER_NOT_READY)
- output += "\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | Not Ready | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]
"
- if(PLAYER_READY_TO_PLAY)
- output += "\[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]
"
- if(PLAYER_READY_TO_OBSERVE)
- output += "\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]
"
- else
- output += "View the Crew Manifest
"
- output += "Join Game!
"
- output += "[LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]
"
-
- if(!IsGuestKey(src.key))
- if (SSdbcore.Connect())
- var/isadmin = 0
- if(src.client && src.client.holder)
- isadmin = 1
- var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")")
- var/rs = REF(src)
- if(query_get_new_polls.Execute())
- var/newpoll = 0
- if(query_get_new_polls.NextRow())
- newpoll = 1
-
- if(newpoll)
- output += "Show Player Polls (NEW!)
"
- else
- output += "Show Player Polls
"
- qdel(query_get_new_polls)
- if(QDELETED(src))
- return
-
- output += " "
-
- //src << browse(output,"window=playersetup;size=210x240;can_close=0")
- var/datum/browser/popup = new(src, "playersetup", "New Player Options
", 250, 265)
- popup.set_window_options("can_close=0")
- popup.set_content(output)
- popup.open(FALSE)
-
-/mob/dead/new_player/Topic(href, href_list[])
- if(src != usr)
- return 0
-
- if(!client)
- return 0
-
- //Determines Relevent Population Cap
- var/relevant_cap
- var/hpc = CONFIG_GET(number/hard_popcap)
- var/epc = CONFIG_GET(number/extreme_popcap)
- if(hpc && epc)
- relevant_cap = min(hpc, epc)
- else
- relevant_cap = max(hpc, epc)
-
- if(href_list["show_preferences"])
- client.prefs.ShowChoices(src)
- return 1
-
- if(href_list["ready"])
- var/tready = text2num(href_list["ready"])
- //Avoid updating ready if we're after PREGAME (they should use latejoin instead)
- //This is likely not an actual issue but I don't have time to prove that this
- //no longer is required
- if(SSticker.current_state <= GAME_STATE_PREGAME)
- ready = tready
- //if it's post initialisation and they're trying to observe we do the needful
- if(!SSticker.current_state < GAME_STATE_PREGAME && tready == PLAYER_READY_TO_OBSERVE)
- ready = tready
- make_me_an_observer()
- return
-
- if(href_list["refresh"])
- src << browse(null, "window=playersetup") //closes the player setup window
- new_player_panel()
-
- if(href_list["late_join"])
- if(!SSticker || !SSticker.IsRoundInProgress())
- to_chat(usr, "The round is either not ready, or has already finished... ")
- return
-
- if(href_list["late_join"] == "override")
- LateChoices()
- return
-
- if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums)))
- to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)] ")
-
- var/queue_position = SSticker.queued_players.Find(usr)
- if(queue_position == 1)
- to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up. ")
- else if(queue_position)
- to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game. ")
- else
- SSticker.queued_players += usr
- to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len]. ")
- return
- LateChoices()
-
- if(href_list["manifest"])
- ViewManifest()
-
- if(href_list["SelectedJob"])
-
- if(!GLOB.enter_allowed)
- to_chat(usr, "There is an administrative lock on entering the game! ")
- return
-
- if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums))
- if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1]))
- to_chat(usr, "Server is full. ")
- return
-
- AttemptLateSpawn(href_list["SelectedJob"])
- return
-
- if(href_list["JoinAsGhostRole"])
- if(!GLOB.enter_allowed)
- to_chat(usr, " There is an administrative lock on entering the game! ")
-
- if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums))
- if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1]))
- to_chat(usr, "Server is full. ")
- return
-
- var/obj/effect/mob_spawn/MS = pick(GLOB.mob_spawners[href_list["JoinAsGhostRole"]])
- if(MS.attack_ghost(src, latejoinercalling = TRUE))
- SSticker.queued_players -= src
- SSticker.queue_delay = 4
- qdel(src)
-
- if(!ready && href_list["preference"])
- if(client)
- client.prefs.process_link(src, href_list)
- else if(!href_list["late_join"])
- new_player_panel()
-
- if(href_list["showpoll"])
- handle_player_polling()
- return
-
- if(href_list["pollid"])
- var/pollid = href_list["pollid"]
- if(istext(pollid))
- pollid = text2num(pollid)
- if(isnum(pollid) && ISINTEGER(pollid))
- src.poll_player(pollid)
- return
-
- if(href_list["votepollid"] && href_list["votetype"])
- var/pollid = text2num(href_list["votepollid"])
- var/votetype = href_list["votetype"]
- //lets take data from the user to decide what kind of poll this is, without validating it
- //what could go wrong
- switch(votetype)
- if(POLLTYPE_OPTION)
- var/optionid = text2num(href_list["voteoptionid"])
- if(vote_on_poll(pollid, optionid))
- to_chat(usr, "Vote successful. ")
- else
- to_chat(usr, "Vote failed, please try again or contact an administrator. ")
- if(POLLTYPE_TEXT)
- var/replytext = href_list["replytext"]
- if(log_text_poll_reply(pollid, replytext))
- to_chat(usr, "Feedback logging successful. ")
- else
- to_chat(usr, "Feedback logging failed, please try again or contact an administrator. ")
- if(POLLTYPE_RATING)
- var/id_min = text2num(href_list["minid"])
- var/id_max = text2num(href_list["maxid"])
-
- if( (id_max - id_min) > 100 ) //Basic exploit prevention
- //(protip, this stops no exploits)
- to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.")
- return
-
- for(var/optionid = id_min; optionid <= id_max; optionid++)
- if(!isnull(href_list["o[optionid]"])) //Test if this optionid was replied to
- var/rating
- if(href_list["o[optionid]"] == "abstain")
- rating = null
- else
- rating = text2num(href_list["o[optionid]"])
- if(!isnum(rating) || !ISINTEGER(rating))
- return
-
- if(!vote_on_numval_poll(pollid, optionid, rating))
- to_chat(usr, "Vote failed, please try again or contact an administrator. ")
- return
- to_chat(usr, "Vote successful. ")
- if(POLLTYPE_MULTI)
- var/id_min = text2num(href_list["minoptionid"])
- var/id_max = text2num(href_list["maxoptionid"])
-
- if( (id_max - id_min) > 100 ) //Basic exploit prevention
- to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.")
- return
-
- for(var/optionid = id_min; optionid <= id_max; optionid++)
- if(!isnull(href_list["option_[optionid]"])) //Test if this optionid was selected
- var/i = vote_on_multi_poll(pollid, optionid)
- switch(i)
- if(0)
- continue
- if(1)
- to_chat(usr, "Vote failed, please try again or contact an administrator. ")
- return
- if(2)
- to_chat(usr, "Maximum replies reached. ")
- break
- to_chat(usr, "Vote successful. ")
- if(POLLTYPE_IRV)
- if (!href_list["IRVdata"])
- to_chat(src, "No ordering data found. Please try again or contact an administrator. ")
- return
- var/list/votelist = splittext(href_list["IRVdata"], ",")
- if (!vote_on_irv_poll(pollid, votelist))
- to_chat(src, "Vote failed, please try again or contact an administrator. ")
- return
- to_chat(src, "Vote successful. ")
-
-//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT)
-/mob/dead/new_player/proc/make_me_an_observer()
- if(QDELETED(src) || !src.client)
- ready = PLAYER_NOT_READY
- return FALSE
-
- var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No")
-
- if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes")
- ready = PLAYER_NOT_READY
- src << browse(null, "window=playersetup") //closes the player setup window
- new_player_panel()
- return FALSE
-
- var/mob/dead/observer/observer = new()
- spawning = TRUE
-
- observer.started_as_observer = TRUE
- close_spawn_windows()
- var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list
- to_chat(src, "Now teleporting. ")
- if (O)
- observer.forceMove(O.loc)
- else
- to_chat(src, "Teleporting failed. Ahelp an admin please ")
- stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised")
- observer.key = key
- observer.client = client
- observer.set_ghost_appearance()
- if(observer.client && observer.client.prefs)
- observer.real_name = observer.client.prefs.real_name
- observer.name = observer.real_name
- observer.update_icon()
- observer.stop_sound_channel(CHANNEL_LOBBYMUSIC)
- QDEL_NULL(mind)
- qdel(src)
- return TRUE
-
-/proc/get_job_unavailable_error_message(retval, jobtitle)
- switch(retval)
- if(JOB_AVAILABLE)
- return "[jobtitle] is available."
- if(JOB_UNAVAILABLE_GENERIC)
- return "[jobtitle] is unavailable."
- if(JOB_UNAVAILABLE_BANNED)
- return "You are currently banned from [jobtitle]."
- if(JOB_UNAVAILABLE_PLAYTIME)
- return "You do not have enough relevant playtime for [jobtitle]."
- if(JOB_UNAVAILABLE_ACCOUNTAGE)
- return "Your account is not old enough for [jobtitle]."
- if(JOB_UNAVAILABLE_SLOTFULL)
- return "[jobtitle] is already filled to capacity."
- return "Error: Unknown job availability."
-
-/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE)
- var/datum/job/job = SSjob.GetJob(rank)
- if(!job)
- return JOB_UNAVAILABLE_GENERIC
- if((job.current_positions >= job.total_positions) && job.total_positions != -1)
- if(job.title == "Assistant")
- if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants
- return JOB_AVAILABLE
- for(var/datum/job/J in SSjob.occupations)
- if(J && J.current_positions < J.total_positions && J.title != job.title)
- return JOB_UNAVAILABLE_SLOTFULL
- else
- return JOB_UNAVAILABLE_SLOTFULL
- if(jobban_isbanned(src,rank))
- return JOB_UNAVAILABLE_BANNED
- if(QDELETED(src))
- return JOB_UNAVAILABLE_GENERIC
- if(!job.player_old_enough(client))
- return JOB_UNAVAILABLE_ACCOUNTAGE
- if(job.required_playtime_remaining(client))
- return JOB_UNAVAILABLE_PLAYTIME
- if(latejoin && !job.special_check_latejoin(client))
- return JOB_UNAVAILABLE_GENERIC
- return JOB_AVAILABLE
-
-/mob/dead/new_player/proc/AttemptLateSpawn(rank)
- var/error = IsJobUnavailable(rank)
- if(error != JOB_AVAILABLE)
- alert(src, get_job_unavailable_error_message(error, rank))
- return FALSE
-
- if(SSticker.late_join_disabled)
- alert(src, "An administrator has disabled late join spawning.")
- return FALSE
-
- var/arrivals_docked = TRUE
- if(SSshuttle.arrivals)
- close_spawn_windows() //In case we get held up
- if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin))
- src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.")
- return FALSE
-
- if(CONFIG_GET(flag/arrivals_shuttle_require_undocked))
- SSshuttle.arrivals.RequireUndocked(src)
- arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL
-
- //Remove the player from the join queue if he was in one and reset the timer
- SSticker.queued_players -= src
- SSticker.queue_delay = 4
-
- SSjob.AssignRole(src, rank, 1)
-
- var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind
- var/equip = SSjob.EquipRank(character, rank, TRUE)
- if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob.
- character = equip
-
- var/datum/job/job = SSjob.GetJob(rank)
-
- if(job && !job.override_latejoin_spawn(character))
- SSjob.SendToLateJoin(character)
- if(!arrivals_docked)
- var/obj/screen/splash/Spl = new(character.client, TRUE)
- Spl.Fade(TRUE)
- character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25)
-
- character.update_parallax_teleport()
-
- SSticker.minds += character.mind
-
- var/mob/living/carbon/human/humanc
- if(ishuman(character))
- humanc = character //Let's retypecast the var to be human,
-
- if(humanc) //These procs all expect humans
- GLOB.data_core.manifest_inject(humanc)
- if(SSshuttle.arrivals)
- SSshuttle.arrivals.QueueAnnounce(humanc, rank)
- else
- AnnounceArrival(humanc, rank)
- AddEmploymentContract(humanc)
- if(GLOB.highlander)
- to_chat(humanc, "THERE CAN BE ONLY ONE!!! ")
- humanc.make_scottish()
-
- if(GLOB.summon_guns_triggered)
- give_guns(humanc)
- if(GLOB.summon_magic_triggered)
- give_magic(humanc)
-
- GLOB.joined_player_list += character.ckey
- GLOB.latejoiners += character
-
- if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais.
- if(SSshuttle.emergency)
- switch(SSshuttle.emergency.mode)
- if(SHUTTLE_RECALL, SHUTTLE_IDLE)
- SSticker.mode.make_antag_chance(humanc)
- if(SHUTTLE_CALL)
- if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5)
- SSticker.mode.make_antag_chance(humanc)
-
- if(humanc && CONFIG_GET(flag/roundstart_traits))
- SSquirks.AssignQuirks(humanc, humanc.client, TRUE, FALSE, job, FALSE)
-
- log_manifest(character.mind.key,character.mind,character,latejoin = TRUE)
-
-/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee)
- //TODO: figure out a way to exclude wizards/nukeops/demons from this.
- for(var/C in GLOB.employmentCabinets)
- var/obj/structure/filingcabinet/employment/employmentCabinet = C
- if(!employmentCabinet.virgin)
- employmentCabinet.addFile(employee)
-
-
-/mob/dead/new_player/proc/LateChoices()
-
- var/level = "green"
- switch(GLOB.security_level)
- if(SEC_LEVEL_GREEN)
- level = "green"
- if(SEC_LEVEL_BLUE)
- level = "blue"
- if(SEC_LEVEL_AMBER)
- level = "amber"
- if(SEC_LEVEL_RED)
- level = "red"
- if(SEC_LEVEL_DELTA)
- level = "delta"
-
- var/dat = "Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)] Alert Level: [capitalize(level)]
"
- if(SSshuttle.emergency)
- switch(SSshuttle.emergency.mode)
- if(SHUTTLE_ESCAPE)
- dat += "The station has been evacuated.
"
- if(SHUTTLE_CALL)
- if(!SSshuttle.canRecall())
- dat += "The station is currently undergoing evacuation procedures.
"
- for(var/datum/job/prioritized_job in SSjob.prioritized_jobs)
- if(prioritized_job.current_positions >= prioritized_job.total_positions)
- SSjob.prioritized_jobs -= prioritized_job
- dat += ""
- var/column_counter = 0
- var/free_space = 0
- for(var/list/category in list(GLOB.command_positions) + list(GLOB.supply_positions) + list(GLOB.engineering_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.medical_positions) + list(GLOB.science_positions) + list(GLOB.security_positions))
- var/cat_color = "fff" //random default
- if(SSjob.name_occupations && SSjob.name_occupations[category[1]])
- cat_color = SSjob.name_occupations[category[1]].selection_color //use the color of the first job in the category (the department head) as the category color
- else
- cat_color = SSjob.occupations[category[1]].selection_color
- dat += ""
- dat += "[SSjob.name_occupations[category[1]].exp_type_department] "
-
- var/list/dept_dat = list()
- for(var/job in category)
- var/datum/job/job_datum = SSjob.name_occupations[job]
- if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE)
- var/command_bold = ""
- if(job in GLOB.command_positions)
- command_bold = " command"
- if(job_datum in SSjob.prioritized_jobs)
- dept_dat += "[job_datum.title] ([job_datum.current_positions]) "
- else
- dept_dat += "[job_datum.title] ([job_datum.current_positions]) "
- if(!dept_dat.len)
- dept_dat += "No positions open. "
- dat += jointext(dept_dat, "")
- dat += " "
- column_counter++
- if(free_space <=4)
- free_space++
- if(column_counter > 0 && (column_counter % 3 == 0))
- dat += " "
- if(free_space >= 5 && (free_space % 5 == 0) && (column_counter % 3 != 0))
- free_space = 0
- column_counter = 0
- dat += " "
-
- dat += "
"
-
- var/available_ghosts = 0
- for(var/spawner in GLOB.mob_spawners)
- if(!LAZYLEN(spawner))
- continue
- var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner])
- if(!istype(S) || !S.can_latejoin())
- continue
- available_ghosts++
- break
-
- if(!available_ghosts)
- dat += "There are currently no open ghost spawners.
"
- else
- var/list/categorizedJobs = list("Ghost Role" = list(jobs = list(), titles = GLOB.mob_spawners, color = "#ffffff"))
- for(var/spawner in GLOB.mob_spawners)
- if(!LAZYLEN(spawner))
- continue
- var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner])
- if(!istype(S) || !S.can_latejoin())
- continue
- categorizedJobs["Ghost Role"]["jobs"] += spawner
-
- dat += ""
- for(var/jobcat in categorizedJobs)
- if(!length(categorizedJobs[jobcat]["jobs"]))
- continue
- var/color = categorizedJobs[jobcat]["color"]
- dat += ""
- dat += "[jobcat] "
- for(var/spawner in categorizedJobs[jobcat]["jobs"])
- dat += "[spawner] "
-
- dat += " "
- dat += "
"
- dat += ""
-
- var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 720, 600)
- popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css')
- popup.set_content(jointext(dat, ""))
- popup.open(FALSE) // FALSE is passed to open so that it doesn't use the onclose() proc
-
-/mob/dead/new_player/proc/create_character(transfer_after)
- spawning = 1
- close_spawn_windows()
-
- var/mob/living/carbon/human/H = new(loc)
-
- var/frn = CONFIG_GET(flag/force_random_names)
- if(!frn)
- frn = jobban_isbanned(src, "appearance")
- if(QDELETED(src))
- return
- if(frn)
- client.prefs.random_character()
- client.prefs.real_name = client.prefs.pref_species.random_name(gender,1)
- client.prefs.copy_to(H)
- H.dna.update_dna_identity()
- if(mind)
- if(transfer_after)
- mind.late_joiner = TRUE
- mind.active = 0 //we wish to transfer the key manually
- mind.transfer_to(H) //won't transfer key since the mind is not active
-
- H.name = real_name
-
- . = H
- new_character = .
- if(transfer_after)
- transfer_character()
-
-/mob/dead/new_player/proc/transfer_character()
- . = new_character
- if(.)
- new_character.key = key //Manually transfer the key to log them in
- new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC)
- new_character = null
- qdel(src)
-
-/mob/dead/new_player/proc/ViewManifest()
- var/dat = ""
- dat += "Crew Manifest "
- dat += GLOB.data_core.get_manifest(OOC = 1)
-
- src << browse(dat, "window=manifest;size=387x420;can_close=1")
-
-/mob/dead/new_player/Move()
- return 0
-
-
-/mob/dead/new_player/proc/close_spawn_windows()
-
- src << browse(null, "window=latechoices") //closes late choices window
- src << browse(null, "window=playersetup") //closes the player setup window
- src << browse(null, "window=preferences") //closes job selection
- src << browse(null, "window=mob_occupation")
- src << browse(null, "window=latechoices") //closes late job selection
-
-/* Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense.
- A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled
- Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible"
- Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role
- This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong
-*/
-
-/mob/dead/new_player/proc/check_preferences()
- if(!client)
- return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe.
- if(client.prefs.joblessrole != RETURNTOLOBBY)
- return TRUE
- // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so.
- var/has_antags = FALSE
- if(client.prefs.be_special.len > 0)
- has_antags = TRUE
- if(client.prefs.job_preferences.len == 0)
- if(!ineligible_for_roles)
- to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences. ")
- ineligible_for_roles = TRUE
- ready = PLAYER_NOT_READY
- if(has_antags)
- log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.")
- message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.")
-
- return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well
- return TRUE
+#define LINKIFY_READY(string, value) "[string] "
+
+/mob/dead/new_player
+ var/ready = 0
+ var/spawning = 0//Referenced when you want to delete the new_player later on in the code.
+
+ flags_1 = NONE
+
+ invisibility = INVISIBILITY_ABSTRACT
+
+ density = FALSE
+ stat = DEAD
+ canmove = FALSE
+
+ anchored = TRUE // don't get pushed around
+
+ var/mob/living/new_character //for instant transfer once the round is set up
+
+ //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles
+ var/ineligible_for_roles = FALSE
+
+/mob/dead/new_player/Initialize()
+ if(client && SSticker.state == GAME_STATE_STARTUP)
+ var/obj/screen/splash/S = new(client, TRUE, TRUE)
+ S.Fade(TRUE)
+
+ if(length(GLOB.newplayer_start))
+ forceMove(pick(GLOB.newplayer_start))
+ else
+ forceMove(locate(1,1,1))
+
+ ComponentInitialize()
+
+ . = ..()
+
+/mob/dead/new_player/prepare_huds()
+ return
+
+/mob/dead/new_player/proc/new_player_panel()
+ var/output = "Welcome, [client ? client.prefs.real_name : "Unknown User"]
"
+ output += "Setup Character
"
+
+ if(SSticker.current_state <= GAME_STATE_PREGAME)
+ switch(ready)
+ if(PLAYER_NOT_READY)
+ output += "\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | Not Ready | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]
"
+ if(PLAYER_READY_TO_PLAY)
+ output += "\[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]
"
+ if(PLAYER_READY_TO_OBSERVE)
+ output += "\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]
"
+ else
+ output += "View the Crew Manifest
"
+ output += "Join Game!
"
+ output += "[LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]
"
+
+ if(!IsGuestKey(src.key))
+ if (SSdbcore.Connect())
+ var/isadmin = 0
+ if(src.client && src.client.holder)
+ isadmin = 1
+ var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")")
+ var/rs = REF(src)
+ if(query_get_new_polls.Execute())
+ var/newpoll = 0
+ if(query_get_new_polls.NextRow())
+ newpoll = 1
+
+ if(newpoll)
+ output += "Show Player Polls (NEW!)
"
+ else
+ output += "Show Player Polls
"
+ qdel(query_get_new_polls)
+ if(QDELETED(src))
+ return
+
+ output += " "
+
+ //src << browse(output,"window=playersetup;size=210x240;can_close=0")
+ var/datum/browser/popup = new(src, "playersetup", "New Player Options
", 250, 265)
+ popup.set_window_options("can_close=0")
+ popup.set_content(output)
+ popup.open(FALSE)
+
+/mob/dead/new_player/Topic(href, href_list[])
+ if(src != usr)
+ return 0
+
+ if(!client)
+ return 0
+
+ //Determines Relevent Population Cap
+ var/relevant_cap
+ var/hpc = CONFIG_GET(number/hard_popcap)
+ var/epc = CONFIG_GET(number/extreme_popcap)
+ if(hpc && epc)
+ relevant_cap = min(hpc, epc)
+ else
+ relevant_cap = max(hpc, epc)
+
+ if(href_list["show_preferences"])
+ client.prefs.ShowChoices(src)
+ return 1
+
+ if(href_list["ready"])
+ var/tready = text2num(href_list["ready"])
+ //Avoid updating ready if we're after PREGAME (they should use latejoin instead)
+ //This is likely not an actual issue but I don't have time to prove that this
+ //no longer is required
+ if(SSticker.current_state <= GAME_STATE_PREGAME)
+ ready = tready
+ //if it's post initialisation and they're trying to observe we do the needful
+ if(!SSticker.current_state < GAME_STATE_PREGAME && tready == PLAYER_READY_TO_OBSERVE)
+ ready = tready
+ make_me_an_observer()
+ return
+
+ if(href_list["refresh"])
+ src << browse(null, "window=playersetup") //closes the player setup window
+ new_player_panel()
+
+ if(href_list["late_join"])
+ if(!SSticker || !SSticker.IsRoundInProgress())
+ to_chat(usr, "The round is either not ready, or has already finished... ")
+ return
+
+ if(href_list["late_join"] == "override")
+ LateChoices()
+ return
+
+ if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums)))
+ to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)] ")
+
+ var/queue_position = SSticker.queued_players.Find(usr)
+ if(queue_position == 1)
+ to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up. ")
+ else if(queue_position)
+ to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game. ")
+ else
+ SSticker.queued_players += usr
+ to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len]. ")
+ return
+ LateChoices()
+
+ if(href_list["manifest"])
+ ViewManifest()
+
+ if(href_list["SelectedJob"])
+
+ if(!GLOB.enter_allowed)
+ to_chat(usr, "There is an administrative lock on entering the game! ")
+ return
+
+ if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums))
+ if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1]))
+ to_chat(usr, "Server is full. ")
+ return
+
+ AttemptLateSpawn(href_list["SelectedJob"])
+ return
+
+ if(href_list["JoinAsGhostRole"])
+ if(!GLOB.enter_allowed)
+ to_chat(usr, " There is an administrative lock on entering the game! ")
+
+ if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums))
+ if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1]))
+ to_chat(usr, "Server is full. ")
+ return
+
+ var/obj/effect/mob_spawn/MS = pick(GLOB.mob_spawners[href_list["JoinAsGhostRole"]])
+ if(MS.attack_ghost(src, latejoinercalling = TRUE))
+ SSticker.queued_players -= src
+ SSticker.queue_delay = 4
+ qdel(src)
+
+ if(!ready && href_list["preference"])
+ if(client)
+ client.prefs.process_link(src, href_list)
+ else if(!href_list["late_join"])
+ new_player_panel()
+
+ if(href_list["showpoll"])
+ handle_player_polling()
+ return
+
+ if(href_list["pollid"])
+ var/pollid = href_list["pollid"]
+ if(istext(pollid))
+ pollid = text2num(pollid)
+ if(isnum(pollid) && ISINTEGER(pollid))
+ src.poll_player(pollid)
+ return
+
+ if(href_list["votepollid"] && href_list["votetype"])
+ var/pollid = text2num(href_list["votepollid"])
+ var/votetype = href_list["votetype"]
+ //lets take data from the user to decide what kind of poll this is, without validating it
+ //what could go wrong
+ switch(votetype)
+ if(POLLTYPE_OPTION)
+ var/optionid = text2num(href_list["voteoptionid"])
+ if(vote_on_poll(pollid, optionid))
+ to_chat(usr, "Vote successful. ")
+ else
+ to_chat(usr, "Vote failed, please try again or contact an administrator. ")
+ if(POLLTYPE_TEXT)
+ var/replytext = href_list["replytext"]
+ if(log_text_poll_reply(pollid, replytext))
+ to_chat(usr, "Feedback logging successful. ")
+ else
+ to_chat(usr, "Feedback logging failed, please try again or contact an administrator. ")
+ if(POLLTYPE_RATING)
+ var/id_min = text2num(href_list["minid"])
+ var/id_max = text2num(href_list["maxid"])
+
+ if( (id_max - id_min) > 100 ) //Basic exploit prevention
+ //(protip, this stops no exploits)
+ to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.")
+ return
+
+ for(var/optionid = id_min; optionid <= id_max; optionid++)
+ if(!isnull(href_list["o[optionid]"])) //Test if this optionid was replied to
+ var/rating
+ if(href_list["o[optionid]"] == "abstain")
+ rating = null
+ else
+ rating = text2num(href_list["o[optionid]"])
+ if(!isnum(rating) || !ISINTEGER(rating))
+ return
+
+ if(!vote_on_numval_poll(pollid, optionid, rating))
+ to_chat(usr, "Vote failed, please try again or contact an administrator. ")
+ return
+ to_chat(usr, "Vote successful. ")
+ if(POLLTYPE_MULTI)
+ var/id_min = text2num(href_list["minoptionid"])
+ var/id_max = text2num(href_list["maxoptionid"])
+
+ if( (id_max - id_min) > 100 ) //Basic exploit prevention
+ to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.")
+ return
+
+ for(var/optionid = id_min; optionid <= id_max; optionid++)
+ if(!isnull(href_list["option_[optionid]"])) //Test if this optionid was selected
+ var/i = vote_on_multi_poll(pollid, optionid)
+ switch(i)
+ if(0)
+ continue
+ if(1)
+ to_chat(usr, "Vote failed, please try again or contact an administrator. ")
+ return
+ if(2)
+ to_chat(usr, "Maximum replies reached. ")
+ break
+ to_chat(usr, "Vote successful. ")
+ if(POLLTYPE_IRV)
+ if (!href_list["IRVdata"])
+ to_chat(src, "No ordering data found. Please try again or contact an administrator. ")
+ return
+ var/list/votelist = splittext(href_list["IRVdata"], ",")
+ if (!vote_on_irv_poll(pollid, votelist))
+ to_chat(src, "Vote failed, please try again or contact an administrator. ")
+ return
+ to_chat(src, "Vote successful. ")
+
+//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT)
+/mob/dead/new_player/proc/make_me_an_observer()
+ if(QDELETED(src) || !src.client)
+ ready = PLAYER_NOT_READY
+ return FALSE
+
+ var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No")
+
+ if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes")
+ ready = PLAYER_NOT_READY
+ src << browse(null, "window=playersetup") //closes the player setup window
+ new_player_panel()
+ return FALSE
+
+ var/mob/dead/observer/observer = new()
+ spawning = TRUE
+
+ observer.started_as_observer = TRUE
+ close_spawn_windows()
+ var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list
+ to_chat(src, "Now teleporting. ")
+ if (O)
+ observer.forceMove(O.loc)
+ else
+ to_chat(src, "Teleporting failed. Ahelp an admin please ")
+ stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised")
+ observer.key = key
+ observer.client = client
+ observer.set_ghost_appearance()
+ if(observer.client && observer.client.prefs)
+ observer.real_name = observer.client.prefs.real_name
+ observer.name = observer.real_name
+ observer.update_icon()
+ observer.stop_sound_channel(CHANNEL_LOBBYMUSIC)
+ QDEL_NULL(mind)
+ qdel(src)
+ return TRUE
+
+/proc/get_job_unavailable_error_message(retval, jobtitle)
+ switch(retval)
+ if(JOB_AVAILABLE)
+ return "[jobtitle] is available."
+ if(JOB_UNAVAILABLE_GENERIC)
+ return "[jobtitle] is unavailable."
+ if(JOB_UNAVAILABLE_BANNED)
+ return "You are currently banned from [jobtitle]."
+ if(JOB_UNAVAILABLE_PLAYTIME)
+ return "You do not have enough relevant playtime for [jobtitle]."
+ if(JOB_UNAVAILABLE_ACCOUNTAGE)
+ return "Your account is not old enough for [jobtitle]."
+ if(JOB_UNAVAILABLE_SLOTFULL)
+ return "[jobtitle] is already filled to capacity."
+ return "Error: Unknown job availability."
+
+/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE)
+ var/datum/job/job = SSjob.GetJob(rank)
+ if(!job)
+ return JOB_UNAVAILABLE_GENERIC
+ if((job.current_positions >= job.total_positions) && job.total_positions != -1)
+ if(job.title == "Assistant")
+ if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants
+ return JOB_AVAILABLE
+ for(var/datum/job/J in SSjob.occupations)
+ if(J && J.current_positions < J.total_positions && J.title != job.title)
+ return JOB_UNAVAILABLE_SLOTFULL
+ else
+ return JOB_UNAVAILABLE_SLOTFULL
+ if(jobban_isbanned(src,rank))
+ return JOB_UNAVAILABLE_BANNED
+ if(QDELETED(src))
+ return JOB_UNAVAILABLE_GENERIC
+ if(!job.player_old_enough(client))
+ return JOB_UNAVAILABLE_ACCOUNTAGE
+ if(job.required_playtime_remaining(client))
+ return JOB_UNAVAILABLE_PLAYTIME
+ if(latejoin && !job.special_check_latejoin(client))
+ return JOB_UNAVAILABLE_GENERIC
+ return JOB_AVAILABLE
+
+/mob/dead/new_player/proc/AttemptLateSpawn(rank)
+ var/error = IsJobUnavailable(rank)
+ if(error != JOB_AVAILABLE)
+ alert(src, get_job_unavailable_error_message(error, rank))
+ return FALSE
+
+ if(SSticker.late_join_disabled)
+ alert(src, "An administrator has disabled late join spawning.")
+ return FALSE
+
+ var/arrivals_docked = TRUE
+ if(SSshuttle.arrivals)
+ close_spawn_windows() //In case we get held up
+ if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin))
+ src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.")
+ return FALSE
+
+ if(CONFIG_GET(flag/arrivals_shuttle_require_undocked))
+ SSshuttle.arrivals.RequireUndocked(src)
+ arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL
+
+ //Remove the player from the join queue if he was in one and reset the timer
+ SSticker.queued_players -= src
+ SSticker.queue_delay = 4
+
+ SSjob.AssignRole(src, rank, 1)
+
+ var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind
+ var/equip = SSjob.EquipRank(character, rank, TRUE)
+ if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob.
+ character = equip
+
+ var/datum/job/job = SSjob.GetJob(rank)
+
+ if(job && !job.override_latejoin_spawn(character))
+ SSjob.SendToLateJoin(character)
+ if(!arrivals_docked)
+ var/obj/screen/splash/Spl = new(character.client, TRUE)
+ Spl.Fade(TRUE)
+ character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25)
+
+ character.update_parallax_teleport()
+
+ SSticker.minds += character.mind
+
+ var/mob/living/carbon/human/humanc
+ if(ishuman(character))
+ humanc = character //Let's retypecast the var to be human,
+
+ if(humanc) //These procs all expect humans
+ GLOB.data_core.manifest_inject(humanc)
+ if(SSshuttle.arrivals)
+ SSshuttle.arrivals.QueueAnnounce(humanc, rank)
+ else
+ AnnounceArrival(humanc, rank)
+ AddEmploymentContract(humanc)
+ if(GLOB.highlander)
+ to_chat(humanc, "THERE CAN BE ONLY ONE!!! ")
+ humanc.make_scottish()
+
+ if(GLOB.summon_guns_triggered)
+ give_guns(humanc)
+ if(GLOB.summon_magic_triggered)
+ give_magic(humanc)
+
+ GLOB.joined_player_list += character.ckey
+ GLOB.latejoiners += character
+
+ if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais.
+ if(SSshuttle.emergency)
+ switch(SSshuttle.emergency.mode)
+ if(SHUTTLE_RECALL, SHUTTLE_IDLE)
+ SSticker.mode.make_antag_chance(humanc)
+ if(SHUTTLE_CALL)
+ if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5)
+ SSticker.mode.make_antag_chance(humanc)
+
+ if(humanc && CONFIG_GET(flag/roundstart_traits))
+ SSquirks.AssignQuirks(humanc, humanc.client, TRUE, FALSE, job, FALSE)
+
+ log_manifest(character.mind.key,character.mind,character,latejoin = TRUE)
+
+/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee)
+ //TODO: figure out a way to exclude wizards/nukeops/demons from this.
+ for(var/C in GLOB.employmentCabinets)
+ var/obj/structure/filingcabinet/employment/employmentCabinet = C
+ if(!employmentCabinet.virgin)
+ employmentCabinet.addFile(employee)
+
+
+/mob/dead/new_player/proc/LateChoices()
+
+ var/level = "green"
+ switch(GLOB.security_level)
+ if(SEC_LEVEL_GREEN)
+ level = "green"
+ if(SEC_LEVEL_BLUE)
+ level = "blue"
+ if(SEC_LEVEL_AMBER)
+ level = "amber"
+ if(SEC_LEVEL_RED)
+ level = "red"
+ if(SEC_LEVEL_DELTA)
+ level = "delta"
+
+ var/dat = "Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)] Alert Level: [capitalize(level)]
"
+ if(SSshuttle.emergency)
+ switch(SSshuttle.emergency.mode)
+ if(SHUTTLE_ESCAPE)
+ dat += "The station has been evacuated.
"
+ if(SHUTTLE_CALL)
+ if(!SSshuttle.canRecall())
+ dat += "The station is currently undergoing evacuation procedures.
"
+ for(var/datum/job/prioritized_job in SSjob.prioritized_jobs)
+ if(prioritized_job.current_positions >= prioritized_job.total_positions)
+ SSjob.prioritized_jobs -= prioritized_job
+ dat += ""
+ var/column_counter = 0
+ var/free_space = 0
+ for(var/list/category in list(GLOB.command_positions) + list(GLOB.supply_positions) + list(GLOB.engineering_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.medical_positions) + list(GLOB.science_positions) + list(GLOB.security_positions))
+ var/cat_color = "fff" //random default
+ if(SSjob.name_occupations && SSjob.name_occupations[category[1]])
+ cat_color = SSjob.name_occupations[category[1]].selection_color //use the color of the first job in the category (the department head) as the category color
+ else
+ cat_color = SSjob.occupations[category[1]].selection_color
+ dat += ""
+ dat += "[SSjob.name_occupations[category[1]].exp_type_department] "
+
+ var/list/dept_dat = list()
+ for(var/job in category)
+ var/datum/job/job_datum = SSjob.name_occupations[job]
+ if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE)
+ var/command_bold = ""
+ if(job in GLOB.command_positions)
+ command_bold = " command"
+ if(job_datum in SSjob.prioritized_jobs)
+ dept_dat += "[job_datum.title] ([job_datum.current_positions]) "
+ else
+ dept_dat += "[job_datum.title] ([job_datum.current_positions]) "
+ if(!dept_dat.len)
+ dept_dat += "No positions open. "
+ dat += jointext(dept_dat, "")
+ dat += " "
+ column_counter++
+ if(free_space <=4)
+ free_space++
+ if(column_counter > 0 && (column_counter % 3 == 0))
+ dat += " "
+ if(free_space >= 5 && (free_space % 5 == 0) && (column_counter % 3 != 0))
+ free_space = 0
+ column_counter = 0
+ dat += " "
+
+ dat += "
"
+
+ var/available_ghosts = 0
+ for(var/spawner in GLOB.mob_spawners)
+ if(!LAZYLEN(spawner))
+ continue
+ var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner])
+ if(!istype(S) || !S.can_latejoin())
+ continue
+ available_ghosts++
+ break
+
+ if(!available_ghosts)
+ dat += "There are currently no open ghost spawners.
"
+ else
+ var/list/categorizedJobs = list("Ghost Role" = list(jobs = list(), titles = GLOB.mob_spawners, color = "#ffffff"))
+ for(var/spawner in GLOB.mob_spawners)
+ if(!LAZYLEN(spawner))
+ continue
+ var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner])
+ if(!istype(S) || !S.can_latejoin())
+ continue
+ categorizedJobs["Ghost Role"]["jobs"] += spawner
+
+ dat += ""
+ for(var/jobcat in categorizedJobs)
+ if(!length(categorizedJobs[jobcat]["jobs"]))
+ continue
+ var/color = categorizedJobs[jobcat]["color"]
+ dat += ""
+ dat += "[jobcat] "
+ for(var/spawner in categorizedJobs[jobcat]["jobs"])
+ dat += "[spawner] "
+
+ dat += " "
+ dat += "
"
+ dat += ""
+
+ var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 720, 600)
+ popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css')
+ popup.set_content(jointext(dat, ""))
+ popup.open(FALSE) // FALSE is passed to open so that it doesn't use the onclose() proc
+
+/mob/dead/new_player/proc/create_character(transfer_after)
+ spawning = 1
+ close_spawn_windows()
+
+ var/mob/living/carbon/human/H = new(loc)
+
+ var/frn = CONFIG_GET(flag/force_random_names)
+ if(!frn)
+ frn = jobban_isbanned(src, "appearance")
+ if(QDELETED(src))
+ return
+ if(frn)
+ client.prefs.random_character()
+ client.prefs.real_name = client.prefs.pref_species.random_name(gender,1)
+ client.prefs.copy_to(H)
+ H.dna.update_dna_identity()
+ if(mind)
+ if(transfer_after)
+ mind.late_joiner = TRUE
+ mind.active = 0 //we wish to transfer the key manually
+ mind.transfer_to(H) //won't transfer key since the mind is not active
+
+ H.name = real_name
+
+ . = H
+ new_character = .
+ if(transfer_after)
+ transfer_character()
+
+/mob/dead/new_player/proc/transfer_character()
+ . = new_character
+ if(.)
+ new_character.key = key //Manually transfer the key to log them in
+ new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC)
+ new_character = null
+ qdel(src)
+
+/mob/dead/new_player/proc/ViewManifest()
+ var/dat = ""
+ dat += "Crew Manifest "
+ dat += GLOB.data_core.get_manifest(OOC = 1)
+
+ src << browse(dat, "window=manifest;size=387x420;can_close=1")
+
+/mob/dead/new_player/Move()
+ return 0
+
+
+/mob/dead/new_player/proc/close_spawn_windows()
+
+ src << browse(null, "window=latechoices") //closes late choices window
+ src << browse(null, "window=playersetup") //closes the player setup window
+ src << browse(null, "window=preferences") //closes job selection
+ src << browse(null, "window=mob_occupation")
+ src << browse(null, "window=latechoices") //closes late job selection
+
+/* Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense.
+ A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled
+ Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible"
+ Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role
+ This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong
+*/
+
+/mob/dead/new_player/proc/check_preferences()
+ if(!client)
+ return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe.
+ if(client.prefs.joblessrole != RETURNTOLOBBY)
+ return TRUE
+ // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so.
+ var/has_antags = FALSE
+ if(client.prefs.be_special.len > 0)
+ has_antags = TRUE
+ if(client.prefs.job_preferences.len == 0)
+ if(!ineligible_for_roles)
+ to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences. ")
+ ineligible_for_roles = TRUE
+ ready = PLAYER_NOT_READY
+ if(has_antags)
+ log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.")
+ message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.")
+
+ return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well
+ return TRUE
diff --git a/tgstation.dme b/tgstation.dme
index 96bcffea30..afe619593e 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -550,6 +550,11 @@
#include "code\game\gamemodes\devil\game_mode.dm"
#include "code\game\gamemodes\devil\objectives.dm"
#include "code\game\gamemodes\devil\devil agent\devil_agent.dm"
+#include "code\game\gamemodes\dynamic\dynamic.dm"
+#include "code\game\gamemodes\dynamic\dynamic_rulesets.dm"
+#include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm"
+#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm"
+#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\meteor\meteor.dm"
#include "code\game\gamemodes\meteor\meteors.dm"
@@ -1792,17 +1797,39 @@
#include "code\modules\jobs\access.dm"
#include "code\modules\jobs\job_exp.dm"
#include "code\modules\jobs\jobs.dm"
+#include "code\modules\jobs\job_types\_job.dm"
+#include "code\modules\jobs\job_types\ai.dm"
#include "code\modules\jobs\job_types\assistant.dm"
+#include "code\modules\jobs\job_types\atmospheric_technician.dm"
+#include "code\modules\jobs\job_types\bartender.dm"
+#include "code\modules\jobs\job_types\botanist.dm"
#include "code\modules\jobs\job_types\captain.dm"
-#include "code\modules\jobs\job_types\cargo_service.dm"
-#include "code\modules\jobs\job_types\civilian.dm"
-#include "code\modules\jobs\job_types\civilian_chaplain.dm"
-#include "code\modules\jobs\job_types\engineering.dm"
-#include "code\modules\jobs\job_types\job.dm"
-#include "code\modules\jobs\job_types\medical.dm"
-#include "code\modules\jobs\job_types\science.dm"
-#include "code\modules\jobs\job_types\security.dm"
-#include "code\modules\jobs\job_types\silicon.dm"
+#include "code\modules\jobs\job_types\cargo_technician.dm"
+#include "code\modules\jobs\job_types\chaplain.dm"
+#include "code\modules\jobs\job_types\chemist.dm"
+#include "code\modules\jobs\job_types\chief_engineer.dm"
+#include "code\modules\jobs\job_types\chief_medical_officer.dm"
+#include "code\modules\jobs\job_types\clown.dm"
+#include "code\modules\jobs\job_types\cook.dm"
+#include "code\modules\jobs\job_types\curator.dm"
+#include "code\modules\jobs\job_types\cyborg.dm"
+#include "code\modules\jobs\job_types\detective.dm"
+#include "code\modules\jobs\job_types\geneticist.dm"
+#include "code\modules\jobs\job_types\head_of_personnel.dm"
+#include "code\modules\jobs\job_types\head_of_security.dm"
+#include "code\modules\jobs\job_types\janitor.dm"
+#include "code\modules\jobs\job_types\lawyer.dm"
+#include "code\modules\jobs\job_types\medical_doctor.dm"
+#include "code\modules\jobs\job_types\mime.dm"
+#include "code\modules\jobs\job_types\quartermaster.dm"
+#include "code\modules\jobs\job_types\research_director.dm"
+#include "code\modules\jobs\job_types\roboticist.dm"
+#include "code\modules\jobs\job_types\scientist.dm"
+#include "code\modules\jobs\job_types\security_officer.dm"
+#include "code\modules\jobs\job_types\shaft_miner.dm"
+#include "code\modules\jobs\job_types\station_engineer.dm"
+#include "code\modules\jobs\job_types\virologist.dm"
+#include "code\modules\jobs\job_types\warden.dm"
#include "code\modules\jobs\map_changes\map_changes.dm"
#include "code\modules\keybindings\bindings_admin.dm"
#include "code\modules\keybindings\bindings_atom.dm"
@@ -1913,15 +1940,17 @@
#include "code\modules\mob\dead\new_player\poll.dm"
#include "code\modules\mob\dead\new_player\preferences_setup.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\_sprite_accessories.dm"
+#include "code\modules\mob\dead\new_player\sprite_accessories\alienpeople.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\body_markings.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\caps.dm"
+#include "code\modules\mob\dead\new_player\sprite_accessories\Citadel_Snowflake.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\ears.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\frills.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\hair_face.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\hair_head.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\horns.dm"
-#include "code\modules\mob\dead\new_player\sprite_accessories\legs.dm"
-#include "code\modules\mob\dead\new_player\sprite_accessories\moth_wings.dm"
+#include "code\modules\mob\dead\new_player\sprite_accessories\ipc_synths.dm"
+#include "code\modules\mob\dead\new_player\sprite_accessories\legs_and_taurs.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\pines.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\snouts.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories\socks.dm"
@@ -2028,15 +2057,17 @@
#include "code\modules\mob\living\carbon\human\species_types\abductors.dm"
#include "code\modules\mob\living\carbon\human\species_types\android.dm"
#include "code\modules\mob\living\carbon\human\species_types\angel.dm"
+#include "code\modules\mob\living\carbon\human\species_types\bugmen.dm"
#include "code\modules\mob\living\carbon\human\species_types\corporate.dm"
#include "code\modules\mob\living\carbon\human\species_types\dullahan.dm"
#include "code\modules\mob\living\carbon\human\species_types\felinid.dm"
#include "code\modules\mob\living\carbon\human\species_types\flypeople.dm"
+#include "code\modules\mob\living\carbon\human\species_types\furrypeople.dm"
#include "code\modules\mob\living\carbon\human\species_types\golems.dm"
#include "code\modules\mob\living\carbon\human\species_types\humans.dm"
+#include "code\modules\mob\living\carbon\human\species_types\ipc.dm"
#include "code\modules\mob\living\carbon\human\species_types\jellypeople.dm"
#include "code\modules\mob\living\carbon\human\species_types\lizardpeople.dm"
-#include "code\modules\mob\living\carbon\human\species_types\mothmen.dm"
#include "code\modules\mob\living\carbon\human\species_types\mushpeople.dm"
#include "code\modules\mob\living\carbon\human\species_types\plasmamen.dm"
#include "code\modules\mob\living\carbon\human\species_types\podpeople.dm"
@@ -2290,6 +2321,10 @@
#include "code\modules\NTNet\network.dm"
#include "code\modules\NTNet\relays.dm"
#include "code\modules\NTNet\services\_service.dm"
+#include "code\modules\oracle_ui\assets.dm"
+#include "code\modules\oracle_ui\hookup_procs.dm"
+#include "code\modules\oracle_ui\oracle_ui.dm"
+#include "code\modules\oracle_ui\themed.dm"
#include "code\modules\paperwork\clipboard.dm"
#include "code\modules\paperwork\contract.dm"
#include "code\modules\paperwork\filingcabinet.dm"
@@ -2906,7 +2941,6 @@
#include "modular_citadel\code\game\objects\items\boombox.dm"
#include "modular_citadel\code\game\objects\items\holy_weapons.dm"
#include "modular_citadel\code\game\objects\items\honk.dm"
-#include "modular_citadel\code\game\objects\items\meat.dm"
#include "modular_citadel\code\game\objects\items\stunsword.dm"
#include "modular_citadel\code\game\objects\items\vending_items.dm"
#include "modular_citadel\code\game\objects\items\circuitboards\machine_circuitboards.dm"
@@ -2915,7 +2949,6 @@
#include "modular_citadel\code\game\objects\items\devices\radio\headset.dm"
#include "modular_citadel\code\game\objects\items\devices\radio\shockcollar.dm"
#include "modular_citadel\code\game\objects\items\melee\eutactic_blades.dm"
-#include "modular_citadel\code\game\objects\items\melee\misc.dm"
#include "modular_citadel\code\game\objects\items\robot\robot_upgrades.dm"
#include "modular_citadel\code\game\objects\items\storage\firstaid.dm"
#include "modular_citadel\code\game\objects\structures\tables_racks.dm"
@@ -3003,7 +3036,6 @@
#include "modular_citadel\code\modules\mining\mining_ruins.dm"
#include "modular_citadel\code\modules\mob\cit_emotes.dm"
#include "modular_citadel\code\modules\mob\mob.dm"
-#include "modular_citadel\code\modules\mob\dead\new_player\sprite_accessories.dm"
#include "modular_citadel\code\modules\mob\living\damage_procs.dm"
#include "modular_citadel\code\modules\mob\living\living.dm"
#include "modular_citadel\code\modules\mob\living\carbon\carbon.dm"
@@ -3013,11 +3045,6 @@
#include "modular_citadel\code\modules\mob\living\carbon\human\human.dm"
#include "modular_citadel\code\modules\mob\living\carbon\human\human_defense.dm"
#include "modular_citadel\code\modules\mob\living\carbon\human\human_movement.dm"
-#include "modular_citadel\code\modules\mob\living\carbon\human\life.dm"
-#include "modular_citadel\code\modules\mob\living\carbon\human\species.dm"
-#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\furrypeople.dm"
-#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\ipc.dm"
-#include "modular_citadel\code\modules\mob\living\carbon\human\species_types\jellypeople.dm"
#include "modular_citadel\code\modules\mob\living\silicon\ai\vox_sounds.dm"
#include "modular_citadel\code\modules\mob\living\silicon\robot\dogborg_equipment.dm"
#include "modular_citadel\code\modules\mob\living\silicon\robot\robot.dm"