Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 137 additions & 5 deletions code/modules/mob/new_player/new_player.dm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
var/totalPlayers = 0
var/totalPlayersReady = 0
var/datum/browser/panel
/// Track if we've shown the Ctrl+Click tip
var/shown_ctrl_tip = FALSE
universal_speak = 1

invisibility = 101
Expand Down Expand Up @@ -40,7 +42,7 @@

else
output += "<a href='byond://?src=[REF(src)];manifest=1'>View the Crew Manifest</A><br><br>"
output += "<p><a href='byond://?src=[REF(src)];late_join=1'>Join Game!</A></p>"
output += "<p><a href='byond://?src=[REF(src)];late_join=1' onclick='var e = window.event || event; if(e && e.ctrlKey) { window.location=\"byond://?src=[REF(src)];late_join=1;force_legacy=1\"; return false; } return true;'>Join Game!</A> "

output += "<p><a href='byond://?src=[REF(src)];observe=1'>Observe</A></p>"

Expand Down Expand Up @@ -173,6 +175,9 @@
return 1

if(href_list["late_join"])
// Ctrl+Click forces legacy UI
var/force_legacy = href_list["force_legacy"] ? TRUE : FALSE

if(!SSticker.IsRoundInProgress())
to_chat(usr, span_red("The round is either not ready, or has already finished..."))
return
Expand Down Expand Up @@ -227,7 +232,15 @@
tgui_alert(src, "The server is full!", "Oh No!")
return TRUE

LateChoices()
// Choose UI based on button clicked or auto-detection
if(!force_legacy && use_tgui_latejoin())
// Show tip once about Ctrl+Click
if(!shown_ctrl_tip)
shown_ctrl_tip = TRUE
to_chat(src, span_notice("Tip: You can use Ctrl+Click on 'Join Game!' to open the legacy interface, if TGUI menu does not show."))
ui_interact(src) // Try TGUI first
else
LateChoices() // Fallback to legacy

if(href_list["manifest"])
show_manifest(src, nano_state = GLOB.interactive_state)
Expand Down Expand Up @@ -312,19 +325,26 @@
return FALSE
if(jobban_isbanned(src.ckey,rank))
return FALSE
// Check setup restrictions (e.g., Church jobs require specific setup options)
if(client && client.prefs && job.is_restricted(client.prefs))
return FALSE
return TRUE

/mob/new_player/proc/AttemptLateSpawn(rank, spawning_at)
if(src != usr)
return FALSE
return LateSpawn(rank)

// Shared late spawn logic (used by both legacy Topic and TGUI)
/mob/new_player/proc/LateSpawn(rank)
if(!SSticker.IsRoundInProgress())
to_chat(usr, span_red("The round is either not ready, or has already finished..."))
to_chat(src, span_red("The round is either not ready, or has already finished..."))
return FALSE
if(!GLOB.enter_allowed)
to_chat(usr, span_notice("There is an administrative lock on entering the game!"))
to_chat(src, span_notice("There is an administrative lock on entering the game!"))
return FALSE
if(!IsJobAvailable(rank))
src << alert("[rank] is not available. Please try another.")
to_chat(src, span_warning("[rank] is not available. Please try another."))
return FALSE

spawning = 1
Expand Down Expand Up @@ -374,6 +394,118 @@

qdel(src)

// TGUI Detection - check if we should use TGUI or fallback to legacy browser
/mob/new_player/proc/use_tgui_latejoin()
// Check if client exists
if(!client)
return FALSE

// Check if TGUI subsystem exists
if(!SStgui)
return FALSE

// Try to get a window from the pool to verify availability
var/datum/tgui_window/window = SStgui.request_pooled_window(src)
if(!window)
// Pool exhausted or unavailable - use fallback
to_chat(src, span_warning("TGUI window pool exhausted, using legacy interface."))
return FALSE

// Return window to pool (we were just checking)
window.release_lock()

return TRUE

// TGUI Interface - modern UI for job selection
/mob/new_player/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "LateJoin")
ui.set_autoupdate(FALSE) // Disable autoupdate to prevent dexterity checks
ui.open()

/mob/new_player/ui_state(mob/user)
return GLOB.always_state // No distance/access restrictions for new players

/mob/new_player/ui_status(mob/user, datum/ui_state/state)
// Always allow interaction for new_player, skip all checks
return UI_INTERACTIVE

/mob/new_player/ui_data(mob/user)
var/list/data = list()

// Player info
data["playerName"] = client.prefs.be_random_name ? "friend" : client.prefs.real_name

// Round info
data["roundDuration"] = DisplayTimeText(world.time - SSticker.round_start_time)

// Evacuation status
data["isEvacuating"] = evacuation_controller.is_evacuating()
data["isEvacuated"] = evacuation_controller.has_evacuated()

// Build job list grouped by department
var/list/departments = list()

for(var/datum/department/dept in SSjob.departments)
var/list/dept_data = list(
"name" = dept.name,
"jobs" = list()
)

for(var/datum/job/job in dept.jobs)
// Count active players
var/active = 0
for(var/mob/M in GLOB.player_list)
if(M.mind && M.client && M.mind.assigned_role == job.title)
if(M.client.inactivity <= 10 MINUTES)
active++

// Check availability (including experience requirements)
var/is_available = IsJobAvailable(job.title)

var/list/job_data = list(
"title" = job.title,
"currentPositions" = job.current_positions,
"totalPositions" = job.total_positions, // -1 = unlimited
"activePlayers" = active,
"expRequired" = job.exp_requirements,
"expType" = job.exp_required_type,
"department" = job.department,
"available" = is_available,
"description" = job.description,
"supervisors" = job.supervisors,
)

dept_data["jobs"] += list(job_data)

if(length(dept_data["jobs"]))
departments += list(dept_data)

data["departments"] = departments

return data

/mob/new_player/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)

Check failure on line 489 in code/modules/mob/new_player/new_player.dm

View workflow job for this annotation

GitHub Actions / Run Linters

proc never calls parent, required by /datum/proc/ui_act
// Don't call parent - we don't need the default checks for new_player
// Parent would check UI_INTERACTIVE status which may fail for new_player

switch(action)
if("select_job")
var/job_title = params["job"]
if(!job_title)
return FALSE

// Use shared spawn logic
LateSpawn(job_title)
return TRUE

if("close")
ui.close()
return TRUE

return FALSE

/mob/new_player/proc/LateChoices()
var/name = client.prefs.be_random_name ? "friend" : client.prefs.real_name

Expand Down
Binary file added tgui/packages/tgui/assets/50px-Command.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tgui/packages/tgui/assets/54px-NeoTheology.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tgui/packages/tgui/assets/54px-Ship.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tgui/packages/tgui/assets/64px-Guild.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tgui/packages/tgui/assets/64px-Ironhammer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tgui/packages/tgui/assets/64px-Moebius.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tgui/packages/tgui/assets/64px-Technomancers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading