Skip to content

Commit bad40f5

Browse files
authored
feat(ui): add late join tgui menu (#255)
* feat(ui): add LateJoin TGUI menu * feat(ui): add icons to factions in lateJoin menu, make code more DRY * feat(ui): add legacy LateJoin menu option * chore: resolve prettier issues in LateJoin.tsx * chore: remove legacy ui button, add ctrl click to open legacy ui, add tip how to open legacy ui
1 parent 3d7f331 commit bad40f5

9 files changed

Lines changed: 412 additions & 5 deletions

File tree

code/modules/mob/new_player/new_player.dm

Lines changed: 137 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
var/totalPlayers = 0
1010
var/totalPlayersReady = 0
1111
var/datum/browser/panel
12+
/// Track if we've shown the Ctrl+Click tip
13+
var/shown_ctrl_tip = FALSE
1214
universal_speak = 1
1315

1416
invisibility = 101
@@ -40,7 +42,7 @@
4042

4143
else
4244
output += "<a href='byond://?src=[REF(src)];manifest=1'>View the Crew Manifest</A><br><br>"
43-
output += "<p><a href='byond://?src=[REF(src)];late_join=1'>Join Game!</A></p>"
45+
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> "
4446

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

@@ -173,6 +175,9 @@
173175
return 1
174176

175177
if(href_list["late_join"])
178+
// Ctrl+Click forces legacy UI
179+
var/force_legacy = href_list["force_legacy"] ? TRUE : FALSE
180+
176181
if(!SSticker.IsRoundInProgress())
177182
to_chat(usr, span_red("The round is either not ready, or has already finished..."))
178183
return
@@ -227,7 +232,15 @@
227232
tgui_alert(src, "The server is full!", "Oh No!")
228233
return TRUE
229234

230-
LateChoices()
235+
// Choose UI based on button clicked or auto-detection
236+
if(!force_legacy && use_tgui_latejoin())
237+
// Show tip once about Ctrl+Click
238+
if(!shown_ctrl_tip)
239+
shown_ctrl_tip = TRUE
240+
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."))
241+
ui_interact(src) // Try TGUI first
242+
else
243+
LateChoices() // Fallback to legacy
231244

232245
if(href_list["manifest"])
233246
show_manifest(src, nano_state = GLOB.interactive_state)
@@ -312,19 +325,26 @@
312325
return FALSE
313326
if(jobban_isbanned(src.ckey,rank))
314327
return FALSE
328+
// Check setup restrictions (e.g., Church jobs require specific setup options)
329+
if(client && client.prefs && job.is_restricted(client.prefs))
330+
return FALSE
315331
return TRUE
316332

317333
/mob/new_player/proc/AttemptLateSpawn(rank, spawning_at)
318334
if(src != usr)
319335
return FALSE
336+
return LateSpawn(rank)
337+
338+
// Shared late spawn logic (used by both legacy Topic and TGUI)
339+
/mob/new_player/proc/LateSpawn(rank)
320340
if(!SSticker.IsRoundInProgress())
321-
to_chat(usr, span_red("The round is either not ready, or has already finished..."))
341+
to_chat(src, span_red("The round is either not ready, or has already finished..."))
322342
return FALSE
323343
if(!GLOB.enter_allowed)
324-
to_chat(usr, span_notice("There is an administrative lock on entering the game!"))
344+
to_chat(src, span_notice("There is an administrative lock on entering the game!"))
325345
return FALSE
326346
if(!IsJobAvailable(rank))
327-
src << alert("[rank] is not available. Please try another.")
347+
to_chat(src, span_warning("[rank] is not available. Please try another."))
328348
return FALSE
329349

330350
spawning = 1
@@ -374,6 +394,118 @@
374394

375395
qdel(src)
376396

397+
// TGUI Detection - check if we should use TGUI or fallback to legacy browser
398+
/mob/new_player/proc/use_tgui_latejoin()
399+
// Check if client exists
400+
if(!client)
401+
return FALSE
402+
403+
// Check if TGUI subsystem exists
404+
if(!SStgui)
405+
return FALSE
406+
407+
// Try to get a window from the pool to verify availability
408+
var/datum/tgui_window/window = SStgui.request_pooled_window(src)
409+
if(!window)
410+
// Pool exhausted or unavailable - use fallback
411+
to_chat(src, span_warning("TGUI window pool exhausted, using legacy interface."))
412+
return FALSE
413+
414+
// Return window to pool (we were just checking)
415+
window.release_lock()
416+
417+
return TRUE
418+
419+
// TGUI Interface - modern UI for job selection
420+
/mob/new_player/ui_interact(mob/user, datum/tgui/ui)
421+
ui = SStgui.try_update_ui(user, src, ui)
422+
if(!ui)
423+
ui = new(user, src, "LateJoin")
424+
ui.set_autoupdate(FALSE) // Disable autoupdate to prevent dexterity checks
425+
ui.open()
426+
427+
/mob/new_player/ui_state(mob/user)
428+
return GLOB.always_state // No distance/access restrictions for new players
429+
430+
/mob/new_player/ui_status(mob/user, datum/ui_state/state)
431+
// Always allow interaction for new_player, skip all checks
432+
return UI_INTERACTIVE
433+
434+
/mob/new_player/ui_data(mob/user)
435+
var/list/data = list()
436+
437+
// Player info
438+
data["playerName"] = client.prefs.be_random_name ? "friend" : client.prefs.real_name
439+
440+
// Round info
441+
data["roundDuration"] = DisplayTimeText(world.time - SSticker.round_start_time)
442+
443+
// Evacuation status
444+
data["isEvacuating"] = evacuation_controller.is_evacuating()
445+
data["isEvacuated"] = evacuation_controller.has_evacuated()
446+
447+
// Build job list grouped by department
448+
var/list/departments = list()
449+
450+
for(var/datum/department/dept in SSjob.departments)
451+
var/list/dept_data = list(
452+
"name" = dept.name,
453+
"jobs" = list()
454+
)
455+
456+
for(var/datum/job/job in dept.jobs)
457+
// Count active players
458+
var/active = 0
459+
for(var/mob/M in GLOB.player_list)
460+
if(M.mind && M.client && M.mind.assigned_role == job.title)
461+
if(M.client.inactivity <= 10 MINUTES)
462+
active++
463+
464+
// Check availability (including experience requirements)
465+
var/is_available = IsJobAvailable(job.title)
466+
467+
var/list/job_data = list(
468+
"title" = job.title,
469+
"currentPositions" = job.current_positions,
470+
"totalPositions" = job.total_positions, // -1 = unlimited
471+
"activePlayers" = active,
472+
"expRequired" = job.exp_requirements,
473+
"expType" = job.exp_required_type,
474+
"department" = job.department,
475+
"available" = is_available,
476+
"description" = job.description,
477+
"supervisors" = job.supervisors,
478+
)
479+
480+
dept_data["jobs"] += list(job_data)
481+
482+
if(length(dept_data["jobs"]))
483+
departments += list(dept_data)
484+
485+
data["departments"] = departments
486+
487+
return data
488+
489+
/mob/new_player/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
490+
// Don't call parent - we don't need the default checks for new_player
491+
// Parent would check UI_INTERACTIVE status which may fail for new_player
492+
493+
switch(action)
494+
if("select_job")
495+
var/job_title = params["job"]
496+
if(!job_title)
497+
return FALSE
498+
499+
// Use shared spawn logic
500+
LateSpawn(job_title)
501+
return TRUE
502+
503+
if("close")
504+
ui.close()
505+
return TRUE
506+
507+
return FALSE
508+
377509
/mob/new_player/proc/LateChoices()
378510
var/name = client.prefs.be_random_name ? "friend" : client.prefs.real_name
379511

4.05 KB
Loading
2.49 KB
Loading
462 Bytes
Loading
2.81 KB
Loading
2.54 KB
Loading
3.25 KB
Loading
3.13 KB
Loading

0 commit comments

Comments
 (0)