TGUI Faction Select (#21737)

## Description
Hello! First PR here so I figured I'd do a big one ~~and stress out the
maintainers as quickly as possible~~. :)

While going through the process of creating a character the 'faction
select' UI stuck out to me as *functional*, but difficult to parse
visually and pretty dated looking by modern SS13 standards. (Not too
bad, but you know what I mean)
The new version is realistically *bit* too flashy for what it is (a
single-use interface hidden away in the character creation menu), but it
was very good practise for me to get back into DM/TGUI programming so it
works out either way.

I'm very happy to implement any feedback or suggested changes that could
help improve it.

## Actual Description
This PR replaces the old HTML 'faction select' interface with a new TGUI
one, and also cleans up and fixes some adjacent code I bumped into while
working on it.

A couple of notes about the current version:
* The 'Citizenship Check' line is currently non-functional and will
always show a checkmark. I know a lot of the corporations have specific
backgrounds/origins they won't hire people from, but I wasn't sure where
exactly that's enforced in code, if at all.
(I noticed `/datum/faction` has a `blacklisted_citizenship_types`
variable, but it's currently unused.)
* De-adminning or changing species doesn't immediately update the UI to
reflect the changes, you have to click on something new to trigger an
update manually. This doesn't have any actual consequences since I made
sure to sanitise everything, but it is a visual thing.

## Screenshots

<details><summary><b>Collapsed so it doesn't take up so much
room:</b></summary>

### Before
<img width="752" height="482" alt="xziPCYXbkn"
src="https://github.com/user-attachments/assets/334e0609-c2e3-4a33-92ad-15120b78ab35"
/>

### After
<img width="900" height="645" alt="NT"
src="https://github.com/user-attachments/assets/476702c7-c83c-4fee-a2cd-34d912468903"
/>

### Unathi attempting to join Zeng-Hu (Button disabled)
<img width="900" height="645" alt="Unathi fail"
src="https://github.com/user-attachments/assets/c5a256c1-5531-4cbe-8198-27411c1aab9a"
/>

### Faction Pages
<details><summary><b>Stellar Corporate Conglomerate</b></summary>
<img width="900" height="645" alt="SCC"
src="https://github.com/user-attachments/assets/6e8bdb64-a220-41d4-a503-1268f9f93a30"
/>

</details>
<details><summary><b>Nanotrasen</b></summary>
<img width="900" height="645" alt="NT"
src="https://github.com/user-attachments/assets/1ff107b5-7b3e-40e2-8f86-390ce29e165b"
/>

</details>
<details><summary><b>Hephaestus Industries</b></summary>
<img width="900" height="645" alt="HI"
src="https://github.com/user-attachments/assets/6fc23159-990d-4de2-9745-827e11ca0442"
/>

</details>
<details><summary><b>Idris Incorporated</b></summary>
<img width="900" height="645" alt="II"
src="https://github.com/user-attachments/assets/366a9824-81f1-4dd1-b385-4fa3968e39e0"
/>

</details>
<details><summary><b>Orion Express</b></summary>
<img width="900" height="645" alt="OE"
src="https://github.com/user-attachments/assets/5588f0fe-d6c3-44b4-8297-cca4ed991bd8"
/>

</details>
<details><summary><b>Private Military Contracting Group</b></summary>
<img width="900" height="645" alt="PMCG"
src="https://github.com/user-attachments/assets/a3dc6540-bfde-4fb2-939d-11e066b1e316"
/>

</details>
<details><summary><b>Zavodskoi Interstellar</b></summary>
<img width="900" height="645" alt="ZI"
src="https://github.com/user-attachments/assets/a8c2c33d-7138-4164-a9f1-6a2dc3eb3e61"
/>

</details>
<details><summary><b>Zeng-Hu Pharmaceuticals</b></summary>
<img width="900" height="645" alt="ZHP"
src="https://github.com/user-attachments/assets/4535ba80-efcb-4e50-9396-299bc16db2b5"
/>

</details>
<details><summary><b>Independent</b></summary>
<img width="900" height="645" alt="Independent"
src="https://github.com/user-attachments/assets/dcf7b46c-edce-4104-a4f5-000479a9f963"
/>

</details>
<hr>
</details>

## Changelog
- rscadd: "Revamped the 'Faction Select' UI in the occupation menu of
character setup."
- bugfix: "Fixed changing species in character setup not resetting your
faction if it became invalid."
  - Not player-facing:
- Fixed the TGUI dev server not working by bumping up the 'source-map'
version. You still have to manually refresh the interface with Ctrl+R or
Ctrl+F5 but it actually links now.
- Added a `wiki_page` variable to each faction to allow for it to be
manually set rather than being exclusively based on the name. (Mostly
just futureproofing)
- Added some line breaks to the faction descriptions to make them more
easily readable.

---------

Signed-off-by: SabreML <57483089+SabreML@users.noreply.github.com>
Co-authored-by: VMSolidus <evilexecutive@gmail.com>
This commit is contained in:
SabreML
2026-01-26 23:39:22 +00:00
committed by GitHub
co-authored by VMSolidus
parent 6f9b3d6b5c
commit ec22fccc6a
22 changed files with 482 additions and 129 deletions
+1
View File
@@ -3848,6 +3848,7 @@
#include "code\modules\tgui\tgui.dm"
#include "code\modules\tgui\tgui_window.dm"
#include "code\modules\tgui\modules\armor_values.dm"
#include "code\modules\tgui\modules\faction_select.dm"
#include "code\modules\tgui\modules\flavor_text.dm"
#include "code\modules\tgui\modules\hivenet_manifest.dm"
#include "code\modules\tgui\modules\ipc_diagnostic.dm"
+3
View File
@@ -119,3 +119,6 @@
/proc/cmp_fusion_reaction_des(singleton/fusion_reaction/A, singleton/fusion_reaction/B)
return B.priority - A.priority
/proc/cmp_faction(datum/faction/A, datum/faction/B)
return A.ui_priority - B.ui_priority
+4 -5
View File
@@ -1,12 +1,11 @@
/datum/faction/admin
name = "Admin Jobs"
description = {"<p>
This faction is for IC administrative jobs used by staff.
</p>"}
departments = {"Command"}
description = "This faction is for IC administrative jobs used by staff."
departments = list(DEPARTMENT_COMMAND)
title_suffix = "SCC"
ui_priority = -INFINITY // top of the list
allowed_role_types = ADMIN_ROLES
allowed_species_types = list(
+13 -11
View File
@@ -2,7 +2,11 @@
var/name
var/description
var/title_suffix
var/departments
var/list/departments
var/wiki_page
/// Number indicating the list index of this faction in the 'faction select' interface.
var/ui_priority
var/list/allowed_role_types
var/list/allowed_species_types
@@ -60,33 +64,31 @@
role.blacklisted_species = J.blacklisted_species
. += role
/datum/faction/proc/get_selection_error(datum/preferences/prefs, var/mob/user)
/datum/faction/proc/get_selection_error(datum/preferences/prefs, mob/user)
if(length(allowed_species_types))
var/datum/species/S = prefs.get_species_datum()
if(!S)
return "No valid species selected."
return "No valid species"
if(!is_type_in_typecache(S, allowed_species_types))
return "Invalid species selected."
return "Invalid species"
if(length(blacklisted_citizenship_types))
var/datum/citizenship/C = SSrecords.citizenships[prefs.citizenship]
if(!C)
return "No valid nation selected."
return "No valid nation"
if(is_type_in_typecache(C, blacklisted_citizenship_types))
return "Invalid nation selected."
return "Invalid nation"
if (!is_visible(user))
return "This faction is not available to you."
if(!is_visible(user))
return "This faction is not available to you"
return null
/datum/faction/proc/can_select(datum/preferences/prefs, var/mob/user)
/datum/faction/proc/can_select(datum/preferences/prefs, mob/user)
return !get_selection_error(prefs, user)
/datum/faction/proc/get_logo_name()
+5 -4
View File
@@ -1,17 +1,18 @@
/datum/faction/hephaestus_industries
name = "Hephaestus Industries"
description = {"<p>
description = {"
Hephaestus Industries, a sprawling and diverse mega-corporation
focused on engineering and manufacturing on a massive scale, found their start
as a conglomerate of several aerospace companies in the 22nd century. Initially
as a conglomerate of several aerospace companies in the 22nd century.<br><br> Initially
funded by sales of new designs for warp technology, the company fell on hard times
during the Second Great Depression in the late 23rd century. Receiving bailouts
from the Sol Alliance and securing several crucial production contracts, they have slowly
worked their way to become the dominant manufacturing mega-corporation in the
Sol Alliance, pioneering interstellar logistics and construction on an awe-inspiring scale.
</p>"}
departments = {"Engineering<br>Operations"}
"}
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_CARGO)
title_suffix = "Hepht"
wiki_page = "Hephaestus_Industries"
allowed_role_types = HEPH_ROLES
+5 -4
View File
@@ -1,16 +1,17 @@
/datum/faction/idris_incorporated
name = "Idris Incorporated"
description = {"<p>
description = {"
The Orion Spur's largest interstellar banking conglomerate, Idris Incorporated
is operated by the mysterious Idris family. Idris Incorporated's influence
can be found in nearly every corner of human space with their financing of
nearly every type of business and enterprise. Their higher risk ventures have
nearly every type of business and enterprise.<br> Their higher risk ventures have
payment enforced by the infamous Idris Reclamation Units, shell IPCs sent to
claim payment from negligent loan takers. In recent years, they have begun
diversifying into more service-based industries.
</p>"}
departments = {"Security<br>Service"}
"}
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_SERVICE)
title_suffix = "Idris"
wiki_page = "Idris_Incorporated"
allowed_role_types = IDRIS_ROLES
+8 -5
View File
@@ -1,15 +1,18 @@
/datum/faction/nano_trasen
name = "NanoTrasen"
description = {"<p>
Considered the largest megacorporation within the Orion Spur, many will find themselves doing the biddings of NanoTrasen.
description = {"
Considered the largest megacorporation within the Orion Spur, many will find themselves doing the biddings of NanoTrasen.<br>
Initially a biotechnical research company, it rapidly grew in size after the discovery of phoron.
NanoTrasen's continued monopoly on the resource catapulted it into the limelight, where it has remained for the last forty-odd years.
NanoTrasen's continued monopoly on the resource catapulted it into the limelight, where it has remained for the last forty-odd years.<br><br>
During the Phoron Scarcity, NanoTrasen's power has since begun to waver, resulting in their profit margins diminishing considerably.
Nonetheless, NanoTrasen has managed to secure itself as a crucial member of the newly-founded Stellar Corporate Conglomerate
allowing themselves to remain as a dominant corporate presence within the Orion Spur.
</p>"}
departments = {"Medical<br>Research<br>Service"}
"}
departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_SCIENCE, DEPARTMENT_SERVICE)
title_suffix = "NT"
wiki_page = "NanoTrasen_Corporation"
ui_priority = -1
is_default = TRUE
+6 -5
View File
@@ -1,15 +1,16 @@
/datum/faction/orion_express
name = "Orion Express"
description = {"<p>
description = {"
Founded in 2464, the Orion Express is a corporation designed to handle logistics for the
Stellar Corporate Conglomerate in the wake of the Phoron Scarcity and the sudden issues
the Conglomeration of the megacorporations presented. It consists of its main branch, dedicated
the Conglomeration of the megacorporations presented.<br> It consists of its main branch, dedicated
to cargo services and transport, but also features a fledgling robotics division, mainly focused
on industrial synthetics to aid in its logistics missions. The Orion Express is expected to become an
on industrial synthetics to aid in its logistics missions.<br> The Orion Express is expected to become an
integral part of the Stellar Corporate Conglomerate's future through delivering supplies and merchandise throughout the Orion Spur.
</p>"}
departments = {"Operations<br>Service"}
"}
departments = list(DEPARTMENT_CARGO, DEPARTMENT_SERVICE)
title_suffix = "Orion"
wiki_page = "Orion_Express"
allowed_role_types = ORION_ROLES
+6 -5
View File
@@ -1,16 +1,17 @@
/datum/faction/pmc
name = "Private Military Contracting Group"
description = {"<p>
description = {"
The Private Military Contracting Group is a coalition of security and medical contractors;
borne from the necessity of protecting the Stellar Corporate Conglomerate and its holdings.
borne from the necessity of protecting the Stellar Corporate Conglomerate and its holdings.<br>
Following the ever-growing corporate empire, mercenaries and contractors from all across the spur are deployed accordingly;
from mere office buildings to outposts in the Corporate Reconstruction Zone.
from mere office buildings to outposts in the Corporate Reconstruction Zone.<br><br>
Unlike the other members of the Corporate Conglomerate, the Private Military Contracting Group has few employees of its own.
Only some liaisons and bureaucrats work behind the scenes to hire and manage the contractors;
the rest of its members are part of several organizations contracted to supply the PMCG.
</p>"}
departments = {"Medical<br>Security"}
"}
departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_SECURITY)
title_suffix = "PMCG"
wiki_page = "Private_Military_Contracting_Group"
allowed_role_types = PMC_ROLES
+7 -4
View File
@@ -1,16 +1,19 @@
/datum/faction/scc
name = "Stellar Corporate Conglomerate"
description = {"<p>
description = {"
The Stellar Corporate Conglomerate (SCC) was formed at the height of corporate power in the galaxy,
originally to secure Tau Ceti assets during the Second Solarian Incursion.
Chainlink now exercises undisputed economic dominance over the Orion Spur,
and is now willing and eager to supersede any and all higher authority placed before it.
and is now willing and eager to supersede any and all higher authority placed before it.<br>
Cooperation has been deemed essential ever since Einstein Engines resurfaced due to the emerging prominence of warp travel.
They've remained in a shaky peace, puppeteered to carry out the engimatic whims of the Trasen family.
Of course, this doesn't stop their greed for power and glory - they're just corporations, after all.
</p>"}
departments = {"Command<br>Equipment"}
"}
departments = list(DEPARTMENT_COMMAND, DEPARTMENT_EQUIPMENT)
title_suffix = "SCC"
wiki_page = "Stellar_Corporate_Conglomerate"
ui_priority = -2
allowed_role_types = SCC_ROLES
+6 -4
View File
@@ -1,14 +1,16 @@
/datum/faction/unaffiliated
name = "Independent"
description = {"<p>
description = {"
For individuals not necessarily tied to any of the large mega corporations lording
over human space, the mark of "independent" can open as many doors as it is likely to
close. You would often see people lacking a concrete affiliation popping up as
close.<br> You would often see people lacking a concrete affiliation popping up as
reporters, journalists, individual traders. Their presence, though often an inconvenience
for the mega corporations, is often necessary to forward the facade of economic freedom
that Tau Ceti reports to have.
</p>"}
departments = {"Civilian"}
"}
departments = list(DEPARTMENT_CIVILIAN)
allowed_role_types = INDEP_ROLES
title_suffix = "INDEP"
ui_priority = INFINITY // bottom of the list
+6 -5
View File
@@ -1,15 +1,16 @@
/datum/faction/zavodskoi_interstellar
name = "Zavodskoi Interstellar"
description = {"<p>
description = {"
The largest weapons producer in human space, Zavodskoi Interstellar initially
found its place with the invention of a militarized voidsuit for use in the Interstellar War.
found its place with the invention of a militarized voidsuit for use in the Interstellar War.<br>
With many lucrative weapon contracts thanks to the Sol Alliance, as well as acquisitions of
other major armaments companies, Zavodskoi weapons can be found in the hands of nearly every
military force across the Orion Spur. They are the main corporation found in the Empire of
military force across the Orion Spur.<br> They are the main corporation found in the Empire of
Dominia, and are at the forefront of weapons development technology.
</p>"}
departments = {"Engineering<br>Research<br>Security"}
"}
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SCIENCE, DEPARTMENT_SECURITY)
title_suffix = "Zavod"
wiki_page = "Zavodskoi_Interstellar"
allowed_role_types = ZAVOD_ROLES
+5 -5
View File
@@ -1,18 +1,18 @@
/datum/faction/zeng_hu
name = "Zeng-Hu Pharmaceuticals"
description = {"<p>
description = {"
Zeng-Hu Pharmaceuticals, born of a merger of two major biotech companies on
Earth in 2032, was the first to successfully develop cryogenics in the 21st
century for the purposes of space travel. This development, crucial to
interstellar colonization, helped propel them to their current position as the
largest pharmaceutical and medical corporation in the Orion Spur. In more recent
largest pharmaceutical and medical corporation in the Orion Spur.<br> In more recent
years, they were also the first mega-corporation to partner with the newly-discovered
Skrell, working closely with this alien species to pioneer cloning, a once
controversial field that is now more accepted today.
</p>"}
departments = {"Medical<br>Research"}
"}
departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_SCIENCE)
title_suffix = "Zeng"
wiki_page = "Zeng-Hu_Pharmaceuticals"
allowed_role_types = ZENG_ROLES
@@ -490,6 +490,11 @@ GLOBAL_LIST_INIT(valid_bloodtypes, list(
pref.citizenship = OO.possible_citizenships[1]
pref.religion = OO.possible_religions[1]
var/datum/faction/current_faction = SSjobs.name_factions[pref.faction]
if(!current_faction?.can_select(pref, user))
to_chat(user, SPAN_WARNING("[pref.faction] does not employ [mob_species.name_plural]. Resetting to [SSjobs.default_faction.name]!"))
pref.faction = SSjobs.default_faction.name
// Follows roughly the same way hair does above, but for gradient styles
var/global/list/valid_gradients = list()
@@ -1,6 +1,8 @@
/datum/category_item/player_setup_item/occupation
name = "Occupation"
sort_order = 1
/// The module datum for the faction select interface, if it's currently open.
var/datum/tgui_module/faction_select/faction_ui
/datum/category_item/player_setup_item/occupation/load_character(var/savefile/S)
S["alternate_option"] >> pref.alternate_option
@@ -163,7 +165,7 @@
"<style>span.none{color: black} span.low{color: #DDD} span.med{color: yellow} span.high{color: lime} a:hover span{color: #40628a !important}</style>",
"<center><b>Character Faction</b><br>",
"<small>This will influence the jobs you can select from, and the starting equipment.</small><br>",
"<b><a href='byond://?src=[REF(src)];faction_preview=[html_encode(pref.faction)]'>[pref.faction]</a></b></center><br><hr>"
"<b><a href='byond://?src=[REF(src)];faction_select=1;'>[pref.faction]</a></b></center><br><hr>"
)
dat += list(
@@ -292,17 +294,17 @@
if(SetJob(user, href_list["set_job"]))
return TOPIC_REFRESH_UPDATE_PREVIEW
else if(href_list["faction_preview"])
show_faction_menu(user, html_decode(href_list["faction_preview"]))
else if(href_list["faction_select"])
if(!faction_ui)
faction_ui = new(src)
faction_ui.ui_interact(user)
return TOPIC_NOACTION
else if(href_list["faction_select"])
validate_and_set_faction(html_decode(href_list["faction_select"]))
show_faction_menu(user, html_decode(href_list["faction_select"]))
return TOPIC_REFRESH_UPDATE_PREVIEW
return ..()
/datum/category_item/player_setup_item/occupation/proc/on_ui_close()
faction_ui = null
/datum/category_item/player_setup_item/occupation/proc/sanitize_faction()
if (!SSjobs.name_factions[pref.faction])
pref.faction = SSjobs.default_faction.name
@@ -448,67 +450,10 @@
pref.player_alt_titles.Cut()
/datum/category_item/player_setup_item/occupation/proc/show_faction_menu(mob/user, selected_faction) //note : selected faction is what you choose to see, pref.faction is the actual chosen faction
simple_asset_ensure_is_sent(user, /datum/asset/simple/faction_icons)
var/list/dat = list("<center><h2>")
var/list/factions = list()
for (var/datum/faction/faction in SSjobs.factions)
if(!faction.is_visible(user))
continue
if (faction.name == selected_faction)
factions += "[faction.name]"
else
factions += "<a href='byond://?src=[REF(src)];faction_preview=[html_encode(faction.name)]'>[faction.name]</a>"
dat += factions.Join(" ")
var/datum/faction/faction = SSjobs.name_factions[selected_faction]
if(!istype(faction))
to_client_chat(SPAN_DANGER("Invalid faction chosen. Resetting to [SSjobs.default_faction.name]."))
selected_faction = SSjobs.default_faction.name
faction = SSjobs.name_factions[selected_faction]
return
dat += "</h2></center><hr/>"
dat += "<table padding='8px'>"
dat += "<tr>"
dat += "<td width = 500>[faction.description]</td>"
dat += "<td width = 200 align='center'>"
dat += {"<img style="height:132px;" src="[faction.get_logo_name()]">"}
dat += "<br/><b>Departments:</br></b>"
dat += "<small><b>[faction.departments]</b></small>"
dat += "</td>"
dat += "</tr>"
dat += "</table><center><hr/>"
dat += "You can learn more about this faction on <a href='byond://?src=[REF(user.client)];JSlink=wiki;wiki_page=[replacetext(faction.name, " ", "_")]'>the wiki</a>."
if (selected_faction == pref.faction)
dat += "<br>\[Faction selected\]"
else if (faction.can_select(pref,user))
dat += "<br>\[<a href='byond://?src=[REF(src)];faction_select=[html_encode(selected_faction)]'>Select faction</a>\]"
else
dat += "<br><span class='warning'>[faction.get_selection_error(pref, user)]</span>"
dat += "</center>"
user << browse(HTML_SKELETON(dat.Join()), "window=factionpreview;size=750x450")
/datum/category_item/player_setup_item/occupation/proc/validate_and_set_faction(selected_faction)
var/datum/faction/faction = SSjobs.name_factions[selected_faction]
if (!faction)
to_client_chat(SPAN_DANGER("Invalid faction chosen. Resetting to default."))
selected_faction = SSjobs.default_faction.name
/datum/category_item/player_setup_item/occupation/proc/validate_and_set_faction(datum/faction/faction)
ResetJobs() // How to be horribly lazy.
pref.faction = selected_faction
to_client_chat(SPAN_NOTICE("New faction chosen. Job preferences reset."))
pref.faction = faction.name
to_client_chat(SPAN_NOTICE("New faction chosen! Job preferences reset."))
/datum/preferences/proc/GetPlayerAltTitle(datum/job/job)
return player_alt_titles[job.title] || job.title
@@ -0,0 +1,95 @@
/datum/tgui_module/faction_select
/// The 'occupation' datum of the user viewing the interface.
var/datum/category_item/player_setup_item/occupation/occupation
/// The name of the currently viewed faction.
var/viewed_faction
/datum/tgui_module/faction_select/New(datum/category_item/player_setup_item/occupation/occupation)
. = ..()
src.occupation = occupation
viewed_faction = occupation.pref.faction
/datum/tgui_module/faction_select/ui_interact(mob/user, datum/tgui/ui)
. = ..()
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "FactionSelect")
ui.set_autoupdate(FALSE)
ui.open()
/datum/tgui_module/faction_select/ui_close(mob/user)
. = ..()
occupation.on_ui_close()
/datum/tgui_module/faction_select/ui_data(mob/user)
var/list/data = alist()
data["chosen_faction"] = occupation.pref.faction
var/datum/faction/faction_datum = get_faction(viewed_faction, user)
// Set `viewed_faction` to the `get_faction()` result from it, since that double checks that it's actually valid.
data["viewed_faction"] = faction_datum.name
data["viewed_selection_error"] = faction_datum.get_selection_error(occupation.pref, user)
return data
/datum/tgui_module/faction_select/ui_static_data(mob/user)
var/list/data = alist()
var/list/factions = list()
for(var/datum/faction/faction as anything in sort_list(SSjobs.factions, GLOBAL_PROC_REF(cmp_faction)))
if(!faction.is_visible(user))
continue
factions.Add(list(alist(
"name" = faction.name,
"suffix" = faction.title_suffix,
"desc" = faction.description,
"logo" = faction.get_logo_name(),
"departments" = faction.departments,
"wiki_page" = faction.wiki_page
)))
data["factions"] = factions
data["wiki_url"] = GLOB.config.wikiurl
return data
/datum/tgui_module/faction_select/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
var/datum/faction/faction = get_faction(params["faction"], usr, FALSE)
if(action == "view_faction")
viewed_faction = faction.name
return TRUE
if(action == "choose_faction")
var/faction_error = faction.get_selection_error(occupation.pref, usr)
if(faction_error)
to_chat(usr, SPAN_DANGER("Error selecting faction!: [faction_error]"))
return FALSE
occupation.validate_and_set_faction(faction)
usr.client.prefs.ShowChoices(usr)
usr.client.prefs.update_preview_icon()
return TRUE
if(action == "open_wiki")
var/client/client = usr.get_client()
client?.wiki(faction.wiki_page)
return FALSE
/datum/tgui_module/faction_select/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/faction_icons)
)
/datum/tgui_module/faction_select/proc/get_faction(faction_name, mob/user, quiet = TRUE)
var/datum/faction/faction = SSjobs.name_factions[faction_name]
if(!istype(faction) || !faction.is_visible(user))
if(!quiet)
to_chat(usr, SPAN_WARNING("Invalid faction chosen. Resetting to [SSjobs.default_faction.name]."))
update_static_data(user) // Update the faction list to hopefully remove anything invalid.
return SSjobs.default_faction
return faction
@@ -0,0 +1,14 @@
# Your name.
author: SabreML
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- rscadd: "Revamped the 'Faction Select' UI in the occupation menu of character setup."
- bugfix: "Fixed changing species in character setup not resetting your faction if it became invalid."
+1 -1
View File
@@ -6,7 +6,7 @@
"dependencies": {
"axios": "^1.12.0",
"glob": "^7.1.7",
"source-map": "^0.7.3",
"source-map": "^0.7.4",
"stacktrace-parser": "^0.1.10",
"ws": "^7.5.10"
}
@@ -0,0 +1,232 @@
// code/modules/tgui/modules/faction_select.dm
import { classes } from 'common/react';
import { resolveAsset } from '../assets';
import { useBackend } from '../backend';
import { Box, Button, Divider, Flex, Icon, LabeledList, Section, Stack } from '../components';
import { Window } from '../layouts';
export type FactionSelectData = {
chosen_faction: string;
viewed_faction: string;
viewed_selection_error: string;
factions: Faction[];
wiki_url: string;
};
export type Faction = {
name: string;
suffix: string;
desc: string;
logo: string;
departments: string[];
wiki_page: string;
};
export const FactionSelect = () => {
return (
<Window theme="faction_select" width={900} height={645}>
<Window.Content>
<Stack fill>
<Stack.Item width="22em">
<FactionList />
</Stack.Item>
<Stack.Item grow>
<FactionInfo />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
const FactionList = (props, context) => {
const { act, data } = useBackend<FactionSelectData>(context);
return (
<Section fill fitted pl={1} py={1} scrollable>
<Stack vertical>
{data.factions.map((faction) => (
<Stack.Item key={faction.name}>
<Button
className={`faction_entry--${faction.suffix}`}
fluid
selected={faction.name === data.chosen_faction}
color={faction.name === data.viewed_faction ? 'label' : 'grey'}
style={{ 'white-space': 'normal' }}
onClick={() => act('view_faction', { faction: faction.name })}>
<Flex align="center" justify="space-between">
<Flex.Item bold fontSize={1.08}>
{faction.name}
</Flex.Item>
<Flex.Item pt={1} pr={0.5}>
<Box
as="img"
src={resolveAsset(faction.logo)}
width="48px"
height="48px"
/>
</Flex.Item>
</Flex>
</Button>
</Stack.Item>
))}
</Stack>
</Section>
);
};
const FactionInfo = (props, context) => {
const { act, data } = useBackend<FactionSelectData>(context);
const currentFaction = data.factions.find(
(faction) => faction.name === data.viewed_faction
)!; // Non-null assertion for `currentFaction` since if it is null, that shouldn't be handled here.
const FormatDescription = (text: string) => {
return text
.replace(/\n\t/g, ' ')
.replace(/<br>\s*/g, '\n')
.trim();
};
return (
<Flex height="100%" direction="column" justify="space-between">
<Flex.Item>
<Section>
<Box
my={1}
textAlign="center"
bold
fontSize={2.1}
fontFamily="Arial Black">
{currentFaction.name}
</Box>
</Section>
</Flex.Item>
<Flex.Item>
<Divider />
</Flex.Item>
<Flex.Item grow>
<Flex height="100%">
<Flex.Item grow>
<Section fill scrollable fontSize={1.1} preserveWhitespace>
{FormatDescription(currentFaction.desc)}
</Section>
</Flex.Item>
<Flex.Item align="center" height="100%" mt={12.5}>
<div
class={classes([
'Divider--vertical',
'Divider--vertical--dashed',
])}
style={{ height: '85%' }}
/>
</Flex.Item>
<Flex.Item width="15.5em">
<FactionPanel currentFaction={currentFaction} />
</Flex.Item>
</Flex>
</Flex.Item>
<Flex.Item>
<Divider />
<Box textAlign="center">
You can learn more about this faction on the wiki:&nbsp;
<Button
icon="arrow-up-right-from-square"
disabled={!currentFaction.wiki_page}
title={
currentFaction.wiki_page
? data.wiki_url + currentFaction.wiki_page
: 'This faction does not have a wiki page'
}
onClick={() => act('open_wiki', { 'faction': currentFaction.name })}
/>
&nbsp;
</Box>
</Flex.Item>
</Flex>
);
};
const FactionPanel = (props: { currentFaction: Faction }, context) => {
const { currentFaction } = props;
const { act, data } = useBackend<FactionSelectData>(context);
const currentIsChosen = currentFaction.name === data.chosen_faction;
const CanSelect = () =>
currentFaction.name === data.chosen_faction ||
!!data.viewed_selection_error;
const Checkmark = (input: boolean) => {
if (input) {
return <Icon name="check" size={1.1} color="good" />;
} else {
return <Icon name="xmark" size={1.1} color="bad" />;
}
};
return (
<Flex
height="100%"
direction="column"
align="center"
justify="space-between">
<Flex.Item>
<Stack vertical align="center" textColor="label">
<Stack.Item>
<Box
as="img"
src={resolveAsset(currentFaction.logo)}
height="13em"
width="13em"
/>
</Stack.Item>
<Stack.Item bold fontSize={1.25}>
<u>Departments:</u>
</Stack.Item>
{currentFaction.departments.map((department) => (
<Stack.Item key={department} bold>
{department}
</Stack.Item>
))}
</Stack>
</Flex.Item>
<Flex.Item>
<Stack vertical>
<Stack.Item>
<Divider />
</Stack.Item>
<Stack.Item>
<Button
width="15em"
height="3.75em"
textAlign="center"
bold
verticalAlignContent="middle"
style={{ 'white-space': 'normal' }}
disabled={CanSelect()}
onClick={() =>
act('choose_faction', { 'faction': currentFaction.name })
}>
{`[${
currentIsChosen
? 'Faction Selected'
: data.viewed_selection_error ?? 'Select Faction'
}]`}
</Button>
</Stack.Item>
<Stack.Item fontFamily="Tahoma" fontSize={1.05}>
<LabeledList>
<LabeledList.Item label="Citizenship Check">
{Checkmark(true)}
{/* NYI (/datum/faction/var/blacklisted_citizenship_types) */}
</LabeledList.Item>
<LabeledList.Item label="Cultural Check">
{Checkmark(!data.viewed_selection_error)}
</LabeledList.Item>
</LabeledList>
</Stack.Item>
</Stack>
</Flex.Item>
</Flex>
);
};
@@ -0,0 +1,36 @@
@use '../components/Divider.scss';
// Colours based on the companies' 'Official Colours' sections on the wiki.
$company-colors: (
'SCC': #1966b3,
'NT': #14498f,
'Hepht': #aa6d18,
'Idris': #369fa1,
'Orion': #aa6818,
'PMCG': #1a1a1a,
'Zavod': #8a1414,
'Zeng': #b56cea,
// 'Independent' doesn't get a colour :(
);
.theme-faction_select {
.Divider {
&--vertical--dashed {
border-left: Divider.$thickness dashed Divider.$color;
}
}
.LabeledList__content {
text-align: right;
}
.Layout__content {
background-image: none;
}
@each $name, $color in $company-colors {
.faction_entry--#{$name}:not(.Button--selected) {
background-image: linear-gradient(to right, transparent 65%, $color);
}
}
}
+3 -2
View File
@@ -23,6 +23,7 @@
@include meta.load-css('./components/BlockQuote.scss');
@include meta.load-css('./components/Button.scss');
@include meta.load-css('./components/ColorBox.scss');
@include meta.load-css('./components/Dialog.scss');
@include meta.load-css('./components/Dimmer.scss');
@include meta.load-css('./components/Divider.scss');
@include meta.load-css('./components/Dropdown.scss');
@@ -44,12 +45,12 @@
@include meta.load-css('./components/Tabs.scss');
@include meta.load-css('./components/TextArea.scss');
@include meta.load-css('./components/Tooltip.scss');
@include meta.load-css('./components/Dialog.scss');
// Interfaces
@include meta.load-css('./interfaces/ListInput.scss');
@include meta.load-css('./interfaces/AlertModal.scss');
@include meta.load-css('./interfaces/CrewManifest.scss');
@include meta.load-css('./interfaces/FactionSelect.scss');
@include meta.load-css('./interfaces/ListInput.scss');
@include meta.load-css('./interfaces/PersonalCrafting.scss');
@include meta.load-css('./interfaces/RequestManager.scss');
+8 -1
View File
@@ -9462,6 +9462,13 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"source-map@npm:^0.7.4":
version: 0.7.6
resolution: "source-map@npm:0.7.6"
checksum: 932f4a2390aa7100e91357d88cc272de984ad29139ac09eedfde8cc78d46da35f389065d0c5343c5d71d054a6ebd4939a8c0f2c98d5df64fe97bb8a730596c2d
languageName: node
linkType: hard
"sprintf-js@npm:~1.0.2":
version: 1.0.3
resolution: "sprintf-js@npm:1.0.3"
@@ -9967,7 +9974,7 @@ resolve@^2.0.0-next.3:
dependencies:
axios: ^1.12.0
glob: ^7.1.7
source-map: ^0.7.3
source-map: ^0.7.4
stacktrace-parser: ^0.1.10
ws: ^7.5.10
languageName: unknown