From 3d0a80240329744cbd57434b6c7b4181d6b05936 Mon Sep 17 00:00:00 2001 From: Superlagg Date: Mon, 1 Jul 2024 08:37:58 -0700 Subject: [PATCH 1/4] we be jobbers --- code/__DEFINES/jobs.dm | 1 + code/controllers/subsystem/job.dm | 127 +++++ code/modules/jobs/job_types/_job.dm | 32 ++ code/modules/jobs/jobs.dm | 23 + .../modules/mob/dead/new_player/new_player.dm | 59 +-- tgui/packages/tgui/interfaces/JobPreview.js | 460 ++++++++++++++++++ 6 files changed, 664 insertions(+), 38 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/JobPreview.js diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 7d65c33b102..81646ef433b 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -248,6 +248,7 @@ #define JOB_UNAVAILABLE_SLOTFULL 5 #define JOB_UNAVAILABLE_SPECIESLOCK 6 #define JOB_UNAVAILABLE_WHITELIST 7 +#define JOB_UNAVAILABLE_NOT_NEWPLAYER 8 #define DEFAULT_RELIGION "Christianity" #define DEFAULT_DEITY "Space Jesus" diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index 01fdf6657f5..c9646423be0 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -19,6 +19,8 @@ SUBSYSTEM_DEF(job) var/list/level_order = list(JP_HIGH,JP_MEDIUM,JP_LOW) + var/list/preview_holders = list() + /datum/controller/subsystem/job/Initialize(timeofday) SSmapping.HACK_LoadMapConfig() if(!occupations.len) @@ -819,3 +821,128 @@ SUBSYSTEM_DEF(job) /datum/controller/subsystem/job/proc/JobDebug(message) log_job_debug(message) + +/datum/controller/subsystem/job/proc/ShowJobPreview(ckey, rankname) + if(!ckey || !rankname) + return + var/datum/job/job = GetJob(rankname) + if(!job) + return + var/datum/job_preview_holder/jph = preview_holders[ckey] + if(!jph) + jph = new /datum/job_preview_holder(ckey) + preview_holders[ckey] = jph + return jph.show_job(job) + +/// A holder for the TGUI job preview thing!! +/datum/job_preview_holder + var/datum/job/my_job + var/my_ckey + var/list/tgui_slugs = list() + +/datum/job_preview_holder/New(ckey) + . = ..() + my_ckey = ckey + +/datum/job_preview_holder/proc/compile_tgui_map() + tgui_slugs.Cut() + var/list/categories = GLOB.position_ordering.Copy() + for(var/cat in GLOB.position_categories) + if(!islist(categories[cat])) + categories[cat] = list() + var/list/categlob = GLOB.position_categories[cat] + var/list/category_slug = list() + category_slug["CatColor"] = categlob["color"] + category_slug["CatTitle"] = cat + category_slug["CatJobs"] = list() + /// now fill them with some jobs + var/list/catjobs = list() // merek gives good catjobs + for(var/cjob in categlob["jobs"]) + var/datum/job/job = SSjob.GetJob(cjob) + if(!job) + continue + category_slug["CatJobs"] += list(job.get_tgui_slug()) + categories[cat] = category_slug + // trim empty categories + for(var/cat in categories) + if(!LAZYLEN(categories[cat])) + categories -= cat + // then add the naked slugs to the list, to preserve order + for(var/cat in categories) + tgui_slugs += list(categories[cat]) + +/datum/job_preview_holder/proc/show_main() + my_job = null + var/mob/user = ckey2mob(my_ckey) + ui_interact(user) + return TRUE + +/datum/job_preview_holder/proc/show_job(datum/job/job) + if(!job) + return + my_job = job + var/mob/user = ckey2mob(my_ckey) + ui_interact(user) + return TRUE + +/datum/job_preview_holder/ui_state(mob/user) + return GLOB.always_state + +/datum/job_preview_holder/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "JobPreview") + ui.open() + ui.set_autoupdate(FALSE) + update_static_data(user, ui) + +/datum/job_preview_holder/ui_static_data(mob/user) + . = ..() + if(!user || !user.client) + return + var/list/slugpower = tgui_slugs.Copy() + /// cleverly slot the player-specific data into the jobs within the slugs + var/list/xps = user?.client?.prefs?.exp.Copy() + for(var/cat in slugpower) + for(var/list/job in slugpower[cat]["Jobs"]) + var/datum/job/j = SSjob.GetJob(job["Title"]) + if(!j) + continue + + var/my_time = LAZYACCESS(xps, j.exp_type) + job["CurrentMinutes"] = DisplayTimeText(my_time, abbreviated = TRUE) || "0m" + var/timeleft = 0 + if(istype(j)) + timeleft = j.required_playtime_remaining(user.client) + job["TimeTillCanSpawn"] = "[DisplayTimeText(timeleft, abbreviated = TRUE)]" + if(isnewplayer(user)) + var/mob/dead/new_player/player = user + job["SpawnFailure"] = player.IsJobUnavailable(j.title, latejoin = TRUE) + else + job["SpawnFailure"] = JOB_UNAVAILABLE_NOT_NEWPLAYER + var/list/data = list() + data["AllJobsNCats"] = slugpower + data["MyJob"] = my_job?.get_tgui_slug() + return data + +/datum/job_preview_holder/ui_data(mob/user) + . = ..() + var/list/data = list() + data["MyCkey"] = user.client?.ckey || my_ckey + +/datum/job_preview_holder/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + var/mob/user = ckey2mob(params["MyCkey"]) + if(!user) + return + if(action != "JoinJob") + return + if(!istype(user, /mob/dead/new_player)) + to_chat(user, span_alert("You'll want to respawn and mess with this from the lobby screen if you want to become this job.")) + return + var/mob/dead/new_player/player = user + player.AttemptLateSpawn(my_job.title) + + + + diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 3be7077dda3..a409f2beb30 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -95,6 +95,12 @@ //For things that faction Enforces. var/enforces = "" + + //Job difficulty + var/difficulty = "" + + var/list/tgui_slug = list() + //List of outfit datums that can be selected by this job - after spawning - as additional equipment. //This is ontop of the base job outfit var/list/datum/outfit/loadout_options @@ -259,6 +265,32 @@ return max(0, minimal_player_age - C.player_age) + +/datum/job/proc/get_tgui_slug() + if(LAZYLEN(tgui_slug)) + return tgui_slug + var/list/coolgat = list() + coolgat["Title"] = job.title + coolgat["Path"] = job.type + coolgat["Category"] = cat + coolgat["Description"] = job.description + coolgat["Supervisors"] = job.supervisors + coolgat["Paycheck"] = job.paycheck + coolgat["TotalPositions"] = job.total_positions + coolgat["SpawnPositions"] = job.spawn_positions + coolgat["CurrentPositions"] = job.current_positions + coolgat["Forbids"] = job.forbids + coolgat["Enforces"] = job.enforces + coolgat["Extrastuff"] = job.extrastuff + coolgat["HasTimeLock"] = job.my_job.exp_requirements > 0 + coolgat["ReqMinutes"] = "[DisplayTimeText(job.exp_requirements, abbreviated = TRUE)]" + coolgat["ReqType"] = job.exp_type + coolgat["Difficulty"] = job.difficulty + coolgat["JobColor"] = job.color + coolgat["CurrencyUnit"] = SSeconomy.currency_unit + tgui_slug = coolgat + return tgui_slug + /datum/job/proc/config_check() return TRUE diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm index 163edf4462a..15853fca625 100644 --- a/code/modules/jobs/jobs.dm +++ b/code/modules/jobs/jobs.dm @@ -371,6 +371,29 @@ GLOBAL_LIST_INIT(position_categories, list( //"Tunnel Rats" = list("jobs" = tunnelrats_positions, "color" = "#f81717"), )) +GLOBAL_LIST_INIT(position_ordering, list( + EXP_TYPE_WASTELAND = list(), + "New Boston" = list(), + "Reclaimers" = list(), + EXP_TYPE_TRIBAL = list(), + "Guild" = list(), + "Den" = list(), + "Ashdown" = list(), + "Tunnel Rats" = list(), + "Garland" = list(), + EXP_TYPE_BROTHERHOOD = list(), + EXP_TYPE_LEGION = list(), + EXP_TYPE_NCR = list(), + EXP_TYPE_VAULT = list(), + EXP_TYPE_ENCLAVE = list(), + EXP_TYPE_KHAN = list(), + EXP_TYPE_FOLLOWERS = list(), + EXP_TYPE_SILICON = list(), + EXP_TYPE_CLUB = list(), + EXP_TYPE_BIKER = list(), + "debug" = list(), +)) + GLOBAL_LIST_INIT(exp_jobsmap, list( EXP_TYPE_LIVING = list("titles" = list("Texarkana Ranger")), // all living mobs EXP_TYPE_CREW = list("titles" = command_positions | engineering_positions | medical_positions | science_positions | supply_positions | security_positions | civilian_positions | list("AI","Cyborg")), // crew positions diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 60c9d7ab27d..8eeb21e4f42 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -235,25 +235,25 @@ if(href_list["late_join"] == "override") LateChoices() return + PreLateChoices() - if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums))) - to_chat(usr, span_danger("[CONFIG_GET(string/hard_popcap_message)]")) + // if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums))) + // to_chat(usr, span_danger("[CONFIG_GET(string/hard_popcap_message)]")) - var/queue_position = SSticker.queued_players.Find(usr) - if(queue_position == 1) - to_chat(usr, span_notice("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, span_notice("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, span_notice("You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len].")) - return + // var/queue_position = SSticker.queued_players.Find(usr) + // if(queue_position == 1) + // to_chat(usr, span_notice("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, span_notice("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, span_notice("You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len].")) + // return /* if(GLOB.data_core.get_record_by_name(client.prefs.real_name)) alert(src, "This character name is already in use. Choose another.") return */ - PreLateChoices() if(href_list["join_as_creature"]) CreatureSpawn() @@ -267,17 +267,11 @@ message_admins(msg) to_chat(usr, span_danger("The round is either not ready, or has already finished...")) return - - if(!GLOB.enter_allowed) - to_chat(usr, span_notice("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, span_warning("Server is full.")) - return - - AttemptLateSpawn(href_list["SelectedJob"]) + if(!SSjob.ShowJobPreview(client?.ckey, href_list["SelectedJob"])) + var/do_it_anyway = alert(src, "Are you sure you want to join as this role?", "Role Selection", "Yes", "No") + if(do_it_anyway == "Yes") + AttemptLateSpawn(href_list["SelectedJob"]) + // AttemptLateSpawn(href_list["SelectedJob"]) return if(href_list["JoinAsGhostRole"]) @@ -504,17 +498,6 @@ to_chat(client.mob, span_danger("Your ooc notes is empty, please enter information about your roleplaying preferences.")) return - 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 @@ -530,10 +513,10 @@ if(job && !job.override_latejoin_spawn(character)) SSjob.SendToLateJoin(character) - if(!arrivals_docked) - var/atom/movable/screen/splash/Spl = new(character.client, TRUE) - Spl.Fade(TRUE) - character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) + // if(!arrivals_docked) + // var/atom/movable/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() diff --git a/tgui/packages/tgui/interfaces/JobPreview.js b/tgui/packages/tgui/interfaces/JobPreview.js new file mode 100644 index 00000000000..c1b682128c7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/JobPreview.js @@ -0,0 +1,460 @@ +import { + useBackend, + useLocalState, +} from '../backend'; +import { + AnimatedNumber, + Box, + Button, + Tooltip, + Section, + LabeledList, + Stack, + Flex, + Table, + Fragment, + Tabs, + Icon, +} from '../components'; +import { toFixed } from 'common/math'; +import { + formatMoney, + formatTime, +} from '../format'; +import { Window } from '../layouts'; +import { multiline } from '../../common/string'; + +// ANATOMY OF AllJobsNCats +// AllJobsNCats = [ +// { +// "CatColor": "#BADABB", +// "CatTitle": "Nash Boston Pro Team", +// "CatJobs": [ +// { +// "Title": "Head Coach", +// "Path": "/datum/job/headcoach", +// "Category": "Nash Boston Pro Team", +// "Description": "The head coach kinda blows.", +// "Supervisors": "Principal Dingus", +// "Paycheck": 100, +// "TotalPositions": 1, +// "SpawnPositions": 1, +// "CurrentPositions": 0, +// "Forbids": "dragons", +// "Enforces": "catboys", +// "Extrastuff": "This job is really cool.", +// "HasTimeLock": true, +// "ReqMinutes": "59 minutes", +// "ReqType": "Living", +// "Difficulty": 1, +// "JobColor": "#FF0000", +// "CurrencyUnit": "dollars" +// }, +// and so on... +// }, +// and so on... +// ] +// ANATOMY OF MyJob +// Its just a job object + + +export const JobPreview = (props, context) => { + const { act, data } = useBackend(context); + const { + AllJobsNCats = [], + MyJob = {}, + MyCkey = "Fooby", + } = data; + + // checks if MyJob is empty + const MenuState = Object.keys(MyJob).length === 0 ? "MAIN" : "JOBDESC"; + const JobMenuWidth = MenuState ? "100%" : 0; + const JobMenuShrinkfit = MenuState ? false : true; + + + return ( + + + + + + + {MenuState === "JOBDESC" ? ( + + + + ) : null} + + + + ); +}; + +// THe job menu! Holds all the categories and jobs +// has two states: MAIN and JOBDESC +// MAIN is the default state, and fills the window with the categories and jobs +// JOBDESC is the state where the user is looking at a job's description +// and the job menu is shrunk to the left and a single column of buttons is dere +// it also makes the list scrollable +const JobMenu = (props, context) => { + const { act, data } = useBackend(context); + const { + AllJobsNCats = [], + MyJob = {}, + MyCkey, + } = data; + + const MenuState = Object.keys(MyJob).length === 0 ? "MAIN" : "JOBDESC"; + const WrapFlex = MenuState === "MAIN" ? "wrap" : "nowrap"; + const Basisness = MenuState === "MAIN" ? "auto" : "100%"; + + return ( +
act('ClearJob')} /> + ) : null}> + + {AllJobsNCats.map((Cat, index) => ( + + + + ))} + +
+ ); +}; + +// The category job list +// Holds the jobs for a single category +const CatJobList = (props, context) => { + const { act, data } = useBackend(context); + const { + Cat = {}, + } = props; + + const { + CatColor, + CatTitle, + CatJobs = [], + } = Cat; + + return ( + + + {CatTitle} + + + {CatJobs.map((Job, index) => ( + + + + ))} + + + ); +}; + +// The job button +// A single job button +const JobButton = (props, context) => { + const { act, data } = useBackend(context); + const { + Job = {}, + } = props; + + const { + Title, + Description, + Supervisors, + Paycheck, + TotalPositions, + SpawnPositions, + CurrentPositions, + Forbids, + Enforces, + Extrastuff, + ReqMinutes, + ReqType, + Difficulty, + JobColor, + CurrencyUnit, + } = Job; + + return ( + + ); +}; + + + + + + + + + +// The job description +// Holds the job's description and details +const JobDescription = (props, context) => { + const { act, data } = useBackend(context); + const { + MyJob = {}, + MyCkey, + } = data; + const { + Title, + Description, + Supervisors, + Paycheck, + TotalPositions, + CurrentPositions, + Forbids, + Enforces, + Extrastuff, + ReqMinutes, + ReqType, + Difficulty, + JobColor, + CurrencyUnit, + } = MyJob; + + return ( +
+ )}> + + + {ReqType + && ReqType.length > 0 + && ReqMinutes + && ReqMinutes.length > 0 ? ( + + {"This job requires " + + ReqMinutes + + " of playtime as " + + ReqType + + " to take!"} + + ) : null} + + {Description} + + {Extrastuff && Extrastuff.length > 0 ? ( + + {Extrastuff} + + ) : null} + + + + + {Supervisors} + + + {Paycheck} {CurrencyUnit} + + {Forbids && Forbids.length > 0 ? ( + + {Forbids} + + ) : null} + {Enforces && Enforces.length > 0 ? ( + + {Enforces} + + ) : null} + + + +
+ ); +}; + +const JoinButton = (props, context) => { + const { act, data } = useBackend(context); + const { + Title, + Description, + Supervisors, + Paycheck, + TotalPositions, + SpawnPositions, + CurrentPositions, + Forbids, + Enforces, + Extrastuff, + ReqMinutes, + ReqType, + Difficulty, + CurrentMinutes, + CanSpawnAs, + TimeTillCanSpawn, + SpawnFailure, + } = data; + + const ButtCantIcon = "times"; + const ButtCanIcon = "user"; + const ButtCantStyle = { + "color": "black", + }; + const ButtCanStyle = { + "color": "#00FF00", + }; + + switch (SpawnFailure) { + // Can do! + case 0: + return ( + ))} @@ -179,62 +209,21 @@ const CatJobList = (props, context) => { ); }; -// The job button -// A single job button -const JobButton = (props, context) => { - const { act, data } = useBackend(context); - const { - Job = {}, - } = props; - - const { - Title, - Description, - Supervisors, - Paycheck, - TotalPositions, - SpawnPositions, - CurrentPositions, - Forbids, - Enforces, - Extrastuff, - ReqMinutes, - ReqType, - Difficulty, - JobColor, - CurrencyUnit, - } = Job; - - return ( - - ); -}; - - - - - - - - - // The job description // Holds the job's description and details const JobDescription = (props, context) => { const { act, data } = useBackend(context); + + const [ + MyJobject, + setMyJobject, + ] = useLocalState(context, "MyJobject", {}); + const { - MyJob = {}, + MyJob = MyJobject, MyCkey, } = data; + const { Title, Description, @@ -254,6 +243,7 @@ const JobDescription = (props, context) => { return (
{ {ReqType - && ReqType.length > 0 + && ReqType.length > 1 && ReqMinutes - && ReqMinutes.length > 0 ? ( + && ReqMinutes.length > 1 ? ( {"This job requires " + ReqMinutes @@ -274,7 +264,7 @@ const JobDescription = (props, context) => { + " to take!"} ) : null} - + {Description} {Extrastuff && Extrastuff.length > 0 ? ( From 331f16abeef64a13734d251422006c78269bc26f Mon Sep 17 00:00:00 2001 From: Superlagg Date: Sun, 7 Jul 2024 20:12:22 -0700 Subject: [PATCH 3/4] Jobbers --- code/__DEFINES/jobs.dm | 33 + code/_onclick/hud/_defines.dm | 3 +- code/_onclick/hud/hud.dm | 1 + code/_onclick/hud/human.dm | 4 + .../hud/screen_objects/character_actions.dm | 10 + code/controllers/subsystem/job.dm | 35 +- code/modules/jobs/job_types/_job.dm | 94 ++- code/modules/jobs/job_types/jobs_we_use.dm | 218 +++++- code/modules/jobs/jobs.dm | 9 +- .../modules/mob/dead/new_player/new_player.dm | 7 +- icons/mob/screen_gen.dmi | Bin 137590 -> 137856 bytes tgui/packages/tgui/interfaces/JobPreview.js | 671 ++++++++++++++---- 12 files changed, 901 insertions(+), 184 deletions(-) diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 2962ac764ae..c1e77398571 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -415,3 +415,36 @@ #define JOB_DISPLAY_ORDER_KHORCHIN 131 #define JOB_DISPLAY_ORDER_KIPCHAK 132 #define JOB_DISPLAY_ORDER_MANGUDAI 133 + +#define REC_SKILL_COMBAT_LVL1 "Combat - Lvl I" = \ + "You can't go two steps outside without running into something that wants to test the limits of your (and its) immortality. \ + Being able to defend yourself is a must, whether it's with a gun, a sword, or your bare hands. \ + Most critters you'll encounter in the wastes are fairly low-threat, such as Pillbugs, Rats, and Geckos, which will be \ + good practice for the more dangerous creatures you'll encounter later on. \ + Your survival knife is surprisingly effective! If you are on Harm intent (the red one), if you click past a critter, you'll \ + swing at it with your knife. Guns are a simple point-and-click affair, but most require either bullets, magazines full of bullets, \ + batteries, or just a good pump to work. You can quickly reload guns by pressing (by default) ctrl-R. \ + Some guns need to be racked or cocked after each shot, and this is done by pressing the use key (default: C) while holding the gun." +#define REC_SKILL_HEALING_LVL1 "Healing - Lvl I" = \ + "You're going to get hurt. A lot. So, you'll want to be able to patch yourself up when you do, preferably before you \ + die. Thankfully, first aid is fairly simple! There are yellow Broc flowers scattered throughout the wastes that, when eaten, \ + will heal your bruises, albeit slowly. You can also find Stimpaks, which are a bit more potent, but harder to come by. If you \ + craft a Broc flower with a Xander root (the brown ones), you'll get some healing powder that'll heal both bruises and burns, \ + and fairly quickly at that. If all else fails, click the green cross on the bottom right of your screen, then use whatever \ + it spawns on yourself to heal yourself up -- be sure to drink water if you're doing this!" +#define REC_SKILL_TRAUMA_LVL1 "Trauma - Lvl I" = \ + "Most folk bleed when they are injured, and you are (probably) no exception. If you find yourself leaking red stuff, \ + find a bandage and click yourself with it to apply it to any wounds you've taken. If you have sutures, you can use them \ + too, and if you have both bandages and sutures applied, you'll heal up even faster! Do note that if you are injured while \ + you have a bandage or suture applied, they'll be destroyed and you'll be right back to bleeding! As your wounds heal up, \ + be sure to stay good and fed, as this speeds along the process." +#define REC_SKILL_SURVIVAL_LVL1 "Survival - Lvl I" = \ + "Surviving in the wastes is a tough business, but you're a tough person! You'll need to eat and drink to keep yourself \ + alive, and you can do this by finding food and water in the wastes. You can find food in the form of various plants, \ + animals, and pre-event foodstuffs, and you can find water in the form of bottles of water, ponds, and sinks. You can \ + cook most meats by sticking them into a microwave or oven (found in most houses)." + + + + + diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 92e294bb6a2..518678b99fe 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -144,7 +144,8 @@ #define ui_character_actions "EAST-1:28, SOUTH+1:2" #define ui_bayou "EAST-1:28, SOUTH+0:2" //Character directory -#define ui_pvpbuttons "EAST-1:28, SOUTH+1:18" //slut directory +#define ui_pvpbuttons "EAST-1:28, SOUTH+1:18" //sludt directory +#define ui_job_viewer "EAST-1:28, SOUTH+2:0" //job viewer //living #define ui_living_pull "EAST-1:28,CENTER-2:15" diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index b8965c4a516..3f6d7657bbe 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -71,6 +71,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( var/atom/movable/screen/newbie_hud_button var/atom/movable/screen/chardir_hud_button var/atom/movable/screen/pvp_focus_toggle/pvp_focus_toggle + var/atom/movable/screen/job_button // subtypes can override this to force a specific UI style var/ui_style diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index 8d975d9d45f..20e5d30792e 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -398,6 +398,10 @@ pvp_focus_toggle.hud = src infodisplay += pvp_focus_toggle + job_button = new /atom/movable/screen/job_button() + job_button.hud = src + infodisplay += job_button + pull_icon = new /atom/movable/screen/pull() pull_icon.icon = ui_style pull_icon.hud = src diff --git a/code/_onclick/hud/screen_objects/character_actions.dm b/code/_onclick/hud/screen_objects/character_actions.dm index 52ff16c77e6..9e9fd1dc397 100644 --- a/code/_onclick/hud/screen_objects/character_actions.dm +++ b/code/_onclick/hud/screen_objects/character_actions.dm @@ -123,6 +123,16 @@ if(usr.client) usr.client.show_character_directory() +/atom/movable/screen/job_button + name = "Review Role" + desc = "Review your current role, as well as the roles of others." + icon = 'icons/mob/screen_gen.dmi' + icon_state = "jobviewer" + screen_loc = ui_job_viewer + +/atom/movable/screen/job_button/Click(location,control,params) + SSjob.ShowJobPreview(usr) + /atom/movable/screen/pvp_focus_toggle name = "PVP focus On/Off" icon = 'icons/mob/screen_gen.dmi' diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index 818ff287965..2cf53b23b33 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -822,17 +822,15 @@ SUBSYSTEM_DEF(job) /datum/controller/subsystem/job/proc/JobDebug(message) log_job_debug(message) -/datum/controller/subsystem/job/proc/ShowJobPreview(ckey, rankname) - if(!ckey || !rankname) - return - var/datum/job/job = GetJob(rankname) - if(!job) +/datum/controller/subsystem/job/proc/ShowJobPreview(ckey) + ckey = extract_ckey(ckey) + if(!ckey) return var/datum/job_preview_holder/jph = preview_holders[ckey] if(!jph) jph = new /datum/job_preview_holder(ckey) preview_holders[ckey] = jph - return jph.show_job(job) + return jph.showme() /// A holder for the TGUI job preview thing!! /datum/job_preview_holder @@ -875,15 +873,11 @@ SUBSYSTEM_DEF(job) tgui_slugs += list(categories[cat]) /datum/job_preview_holder/proc/show_main() - my_job = null var/mob/user = ckey2mob(my_ckey) ui_interact(user) return TRUE -/datum/job_preview_holder/proc/show_job(datum/job/job) - if(!job) - return - my_job = job +/datum/job_preview_holder/proc/showme() var/mob/user = ckey2mob(my_ckey) ui_interact(user) return TRUE @@ -913,11 +907,15 @@ SUBSYSTEM_DEF(job) if(!j) continue var/my_time = LAZYACCESS(xps, j.exp_type) - job["CurrentMinutes"] = DisplayTimeText(my_time, abbreviated = TRUE) || "0m" + if(my_time < 1) + job["CurrentMinutes"] = "0m" + else + job["CurrentMinutes"] = DisplayTimeText(my_time * 10 * 60, abbreviated = TRUE) || "0m" var/timeleft = 0 if(istype(j)) timeleft = j.required_playtime_remaining(user.client) - job["TimeTillCanSpawn"] = "[DisplayTimeText(timeleft, abbreviated = TRUE)]" + job["RawTimeLeft"] = timeleft + job["TimeTillCanSpawn"] = "[DisplayTimeText(timeleft * 10 * 60, abbreviated = TRUE)]" if(isnewplayer(user)) var/mob/dead/new_player/player = user job["SpawnFailure"] = player.IsJobUnavailable(j.title, latejoin = TRUE) @@ -925,13 +923,15 @@ SUBSYSTEM_DEF(job) job["SpawnFailure"] = JOB_UNAVAILABLE_NOT_NEWPLAYER var/list/data = list() data["AllJobsNCats"] = slugpower - data["MyJob"] = my_job?.get_tgui_slug() return data /datum/job_preview_holder/ui_data(mob/user) . = ..() var/list/data = list() + data["IsInGame"] = !isnewplayer(user) data["MyCkey"] = user.client?.ckey || my_ckey + data["CurrentJobTitle"] = user.mind?.assigned_role || "Assistant" + return data /datum/job_preview_holder/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() @@ -941,10 +941,15 @@ SUBSYSTEM_DEF(job) if(action != "JoinJob") return if(!istype(user, /mob/dead/new_player)) + user.playsound_local(user, 'sound/machines/low_buzz.ogg', 80, TRUE) to_chat(user, span_alert("You'll want to respawn and mess with this from the lobby screen if you want to become this job.")) return var/mob/dead/new_player/player = user - player.AttemptLateSpawn(my_job.title) + if(!player.AttemptLateSpawn(params["JoinJob"])) + return + ui = SStgui.try_update_ui(user, src, ui) + if(ui) + ui.close() diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index d78618e2129..b8e4632edc7 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -29,9 +29,6 @@ //How many players have this job var/current_positions = 0 - //Supervisors, who this person answers to directly - var/supervisors = "" - //Sellection screen color var/selection_color = "#ffffff" @@ -48,7 +45,7 @@ var/outfit = null var/plasma_outfit = null //the outfit given to plasmamen - var/exp_requirements = 0 //This is in... minutes? + var/exp_requirements = 0 // Minutes required of exp_type to play this job var/exp_type = EXP_TYPE_LIVING var/exp_type_department = "" @@ -84,21 +81,64 @@ /// Starting skill modifiers. var/list/starting_modifiers - //Description, short text about the job - var/description = "" //Description, short text about the job var/extrastuff = "" - //Against the faction rules, for imporant things that you SHOULDNT do. - var/forbids = "" - //For things that faction Enforces. - var/enforces = "" + var/enforces = "" // unused, use responsibilities instead + + /// DESCRIPTION STUFF TO FILL OUT THE FIVE HEAD OF THE JOB DESCRIPTION STUFF + /// Brief description, the elevator pitch about the general idea of the job + var/brief_description + /// Extended description, a more detailed description of the job, what it's about, what it does, etc. + /// Optional! + var/description + /// Lore about the job, stuff relating to the job's history, its story-related importance, etc. + var/lore + /// Who your boss is, who you answer to, who you look up to, those typically considered your supervisors + var/supervisors + /// Who you boss around, who you're in charge of, who you're responsible for, those typically considered your subordinates + var/subordinates + /// Things you're not allowed to do, things you're forbidden from doing, things you're not supposed to do + var/forbids // "This role forbids you from..." + /// General job responsibilities, what you're expected to do, what you're supposed to be doing, what you're supposed to be responsible for + /// Your overall role in town + var/overall_responsibilities + /// What you do, what your job entails, what your responsibilities are, what you're expected to do + /// this is a list that'll be displayed as bullet points + var/list/specific_responsibilities + /// A brief Day In The Life Of, an optional section that describes what a typical day in the life of this job is like + var/day_in_the_life + /// What skills that you, as a player, are recommended to have to play this job + var/list/recommended_skills + /// What part of the game this job is most focused on, what you'll be doing most of the time + /// Things like Adventuring, Socializing, Job-Simming, etc. + var/focus // "This role focuses on..." + /// Difficulty description, whether or not this job is for beginners, intermediate players, or advanced players + var/difficulty + /// OOC rules and guidelines for playing this job, what you should and shouldn't do, what's expected of you + var/ooc_rules + + /// Faction related stuff, typically applied to the parent job of a group of jobs + var/faction_leader + var/faction_name + /// Short description of the faction + var/faction_short_description + /// Lore behind the faction + var/faction_lore + /// General vibe of the faction + var/faction_vibe + /// Goals of the faction + var/faction_goals + /// Rivals of the faction + var/faction_rivals + /// Allies of the faction + var/faction_allies + /// Points of contention for the faction + var/faction_points_of_contention + - //Job difficulty - var/difficulty = "" - var/list/tgui_slug = list() //List of outfit datums that can be selected by this job - after spawning - as additional equipment. @@ -286,6 +326,34 @@ coolgat["ReqType"] = exp_type || "Living" coolgat["Difficulty"] = difficulty coolgat["JobColor"] = selection_color || "#ffffff" + + coolgat["BriefDescription"] = brief_description + coolgat["Description"] = description + coolgat["Lore"] = lore + if(LAZYLEN(lore) > 150) + coolgat["LoreShort"] = "[copytext(lore, 0, 150)]..." + coolgat["Supervisors"] = supervisors + coolgat["Subordinates"] = subordinates + coolgat["Forbids"] = forbids + coolgat["OverallResponsibilities"] = overall_responsibilities + coolgat["SpecificResponsibilities"] = specific_responsibilities + coolgat["DayInTheLife"] = day_in_the_life + coolgat["RecommendedSkills"] = list() + for(var/skill in recommended_skills) + coolgat["RecommendedSkills"] += list(skill, recommended_skills[skill]) + coolgat["Focus"] = focus + coolgat["Difficulty"] = difficulty + coolgat["OOCRules"] = ooc_rules + coolgat["FactionName"] = faction_name + coolgat["FactionLeader"] = faction_leader + coolgat["FactionShortDescription"] = faction_short_description + coolgat["FactionLore"] = faction_lore + coolgat["FactionVibe"] = faction_vibe + coolgat["FactionGoals"] = faction_goals + coolgat["FactionRivals"] = faction_rivals + coolgat["FactionAllies"] = faction_allies + coolgat["FactionPointsOfContention"] = faction_points_of_contention + tgui_slug = coolgat return tgui_slug diff --git a/code/modules/jobs/job_types/jobs_we_use.dm b/code/modules/jobs/job_types/jobs_we_use.dm index c1c6800d952..e739a4957d7 100644 --- a/code/modules/jobs/job_types/jobs_we_use.dm +++ b/code/modules/jobs/job_types/jobs_we_use.dm @@ -29,6 +29,51 @@ faction = FACTION_WASTELAND outfit = /datum/outfit/job/cb + faction_name = "Wastelanders" + faction_leader = null + faction_short_description = {" + Residents of the wasteland, these wanderers have no masters, no obligations, and no home aside from those they make for themselves. + They are free to roam the wasteland as they please, taking on whatever work they can find and making a living for themselves. + "} + faction_lore = {" + Unaligned and untethered to any particular faction, the Wastelanders are a diverse group of individuals who have chosen (or forced) + to make their way in the wasteland on their own terms. They are free to roam the wasteland as they please, taking on whatever work + they can find and whatever scraps they can scavenge, all in a day's work to see the next sunrise.

+ While they may not have the resources or the backing of the larger factions, the Wastelanders are a hardy and resourceful bunch, + able to make a living for themselves in the harshest of conditions. To be a Wastelander is to be free, to be independent, and to + be beholden to no one but yourself. Some may think of their independence as a death sentence, alone to the hellish and often alien + forces of the wasteland, but ask any long time Waster and they'll tell you that the wastes fear them more than any other. + "} + faction_vibe = {" + The general vibe of the Wastelanders is one of independence, self-reliance, and a certain degree of lawlessness. + Overall, Wastelanders tend to focus on adventuring, scavenging, and survival, often alone or in small groups. + It is a great faction for players who want to explore the wasteland, fight monsters, and make it big in the wasteland. + "} + faction_goals = {" + Your goals are whatever you make of them. You are free to roam the wasteland as you please, taking on whatever work you feel like, + and making a living for yourself. You can choose to be a hero, a villain, or anything in between. The wasteland is your oyster, + though don't be surprised to find a clam instead. + "} + faction_rivals = {" + While the Wastelanders are not officially aligned with any other faction, and by default don't have any official rivals, + they are often at odds with the more organized factions of the wasteland. The Wastelanders are a wild card, and their + independence and unpredictability can make them a threat to the more established powers of the region. Rivalry is often + on a case-by-case basis, and can vary greatly depending on the individual Wastelander and the faction they are dealing with. + "} + faction_allies = {" + While the Wastelanders are not officially aligned with any other faction, and by default don't have any official allies, + they are often not opposed to working with other factions when it suits their needs. The Wastelanders are a diverse group, + and their interests can align with those of other factions, even if only temporarily. The Wastelanders are a wild card, + and their independence and unpredictability can make them valuable allies in the right circumstances. + "} + faction_points_of_contention = {" + Wastelanders are often seen as vagrants, outcasts, and troublemakers by the more established factions of the wasteland. The term + 'murderhobo' is often used to describe them, and while not all Wastelanders fit this stereotype, many do. Anyone from simple + homesteaders to hardened mercenaries to straight up raiders are considered Wastelanders, and being a part of this 'faction' means + being associated with all of them. Whether or not this is actually true is up to you to decide. + "} + + ////////////////////////////// /// Wastelander /// As basic as it gets, no masters, no obligations, just you and the wasteland, how fun @@ -37,8 +82,44 @@ flag = F13WASTELANDER total_positions = -1 spawn_positions = -1 - description = "The most broad and open role, you have arrived in the region for purposes known only to you. If you're new, the settlement of Nash to the Midwest may prove a valuable first stop. Try to make a living for yourself - or simply survive - and craft your own unique story." - supervisors = "fate" + brief_description = {" + You are a Wastelander, a wanderer in the wasteland who has arrived in the region for purposes known only to you. + Try to make a living for yourself - or simply survive - and craft your own unique story. + "} + description = {" + You are one of the many Wastelanders who roam the wasteland, making a living out of whatever multi-dimensional horrors + and ancient ruins you can find in the wasteland. You could be a scavenger, a mercenary, a raider, or anything in between, + but one thing is for sure: you are beholden to no one but yourself. You are free to roam the wasteland as you please, + taking on whatever work you can find and whatever scraps you can scavenge, all in a day's work! + "} + lore = null + supervisors = "Fate." + subordinates = "Anyone you can convince to follow you." + forbids = "Nothing!" + overall_responsibilities = "Survive, thrive, and make a living for yourself in the wasteland." + specific_responsibilities = list( + "Scavenge for supplies and resources.", + "Explore the wasteland and uncover its secrets.", + "Take on odd jobs and contracts for pay.", + "Fight off the various dangers of the wasteland.", + "Craft and build your own gear and equipment.", + "Make a name for yourself in the wasteland.", + "Have fun and make it all up as you go along!", + ) + day_in_the_life = {" + As a Wastelander, your day-to-day life is one of adventure, danger, and excitement. You might start your day by scavenging + for supplies in the ruins of a pre-war city, then head out to explore a long-forgotten bunker, and end the day by fighting + off a horde of mutated creatures. Or you might spend the day building a lovely home in the middle of the swamp for you and + that cute radvixen you met this morning. The wasteland is your oyster, and it's up to you to make that oyster a clam! + "} + recommended_skills = list( + REC_SKILL_COMBAT_LVL1, + REC_SKILL_HEALING_LVL1, + REC_SKILL_TRAUMA_LVL1, + REC_SKILL_SURVIVAL_LVL1, + ) + focus = "Survival, exploration, combat, and crafting, as well as chatting up the various folks you meet in the wasteland." + difficulty = "Easy to Medium. Most of the challenge comes from combat and survival against monsters and the occasional hostile survivor." selection_color = "#dddddd" paycheck = 0 // Wastelanders are paid in the form of loot and barter access = list() @@ -54,8 +135,56 @@ social_faction = FACTION_TRIBE total_positions = -1 spawn_positions = -1 - description = "You are a member of the of a tribe who has wandered to this area, but does not belong to the Mountain River tribe. From where you came is up to you, why you are here is your own, and it is up to you to survive on your own and attempt to thrive." - supervisors = "the Ways of your own tribe" + + brief_description = {" + You are a member of the of a tribe who has wandered to this area, but does not belong to the Mountain River tribe. + From where you came is up to you, why you are here is your own, and it is up to you to survive on your own and attempt to thrive. + "} + description = {" + You are a wastelander who belongs to a tribe that isn't the Mountain River tribe, and may or may not be native to the region. + You have your own reasons for being here, and your own goals and ambitions, along with the skills, knowledge, and traditions of + your tribe to help you along the way. You are free to roam the wasteland as you please, taking on whatever work you can find and + whatever scraps you can scavenge. Whether you were born into the tribe or joined later in life, you are a member of this tribe, + whatever that may mean to you. Maybe you were cast out for some crime, or maybe you wandered out this way as you were foraging + for your camp, or maybe you were just bored and wanted to see what was out past the horizon. Whatever the case, you are here now, + and it is up to you to make the most of it. + "} + lore = {" + Most of the tribes in the surrounding regions tend to be nomadic, moving from place to place in search of food, water, and shelter. + They are often small, close-knit communities, bound together by blood, tradition, and a shared history. The tribes are often + led by a chief or elder, who is responsible for making decisions for the tribe and guiding them through the wasteland. The tribes + often have their own customs, rituals, and beliefs, and are often wary of outsiders, but are not necessarily hostile to them. + Most tribes tend to favor a simpler set of equipment, often things that can be built out of scavenged materials or parts of the + land itself, though it is not uncommon for tribes to embrace more advanced technology if they can find it and make use of it. + "} + supervisors = "Fate, the Ways of your own tribe, and the whims of whatever god(s) you may or may not believe in." + subordinates = "Anyone you can convince to follow you and/or your tribe." + forbids = "Anything that goes against the ways of your tribe, if you have any." + overall_responsibilities = "Survive, thrive, and make a living for yourself in the wasteland." + specific_responsibilities = list( + "Scavenge for supplies and resources.", + "Explore the wasteland and uncover its secrets.", + "Take on odd jobs and contracts for pay.", + "Fight off the various dangers of the wasteland.", + "Craft and build your own gear and equipment.", + "Make a name for yourself in the wasteland.", + "Have fun and make it all up as you go along!", + ) + day_in_the_life = {" + As a member of a tribe, your day-to-day life is one of tradition, community, and survival. You might start your day by + performing a ritual to honor the spirits of the land, then head out to hunt for food, and end the day by sharing a meal + with your tribe around the campfire. Or you might spend the day exploring the ruins of a pre-war city, looking for + technology to bring back to your tribe, or fighting off a rival tribe that has encroached on your territory. The wasteland + is your home, and it's up to you to protect it and make it thrive! + "} + recommended_skills = list( + REC_SKILL_COMBAT_LVL1, + REC_SKILL_HEALING_LVL1, + REC_SKILL_TRAUMA_LVL1, + REC_SKILL_SURVIVAL_LVL1, + ) + focus = "Survival, exploration, combat, and crafting, as well as chatting up the various folks you meet in the wasteland." + difficulty = "Easy to Medium. Most of the challenge comes from combat and survival against monsters and the occasional hostile survivor." selection_color = "#dddddd" outfit = /datum/outfit/job/cb/tribal access = list() @@ -70,8 +199,36 @@ faction = FACTION_WASTELAND total_positions = -1 spawn_positions = -1 - description = "You are here for one thing and one thing alone: have a good time, either alone, or with others. This role is perfect for someone looking to have a nice private scene with someone, as it is completely safe from the chaos of the main game. Have fun!" - supervisors = "your throbbing biological urges" + + brief_description = {" + You are here for one thing and one thing alone: have a good time, either alone, or with others. This role is perfect for someone + looking to have a nice private scene with someone, as it is completely safe from the chaos of the main game. Have fun! + "} + description = {" + This is a testing area for characters, where you can try out new builds, test out new mechanics, or just have a good time. + As a part of the Backstage, you exist in a state of ambiguous in-character existence, where the canonity of your actions is + up to you and whoever you're with. You could be here for any number of reasons, from testing out a new character concept to + continuing an RP from last round, to even a multi-hour long session of hard core rutting that cute fox you met in the bar! + Whatever the case, you are here to have a good time, and that's all that matters. + "} + lore = null + supervisors = "Your throbbing biological urges." + subordinates = "Nobody (unless you're into that sort of thing)." + forbids = "Nothing in particular, as long as it's within the rules of the server, and not disruptive of others trying to do things." + overall_responsibilities = "Have a good time, either alone, or with others." + specific_responsibilities = list( + "Test out new character concepts.", + "Try out new mechanics and features.", + "Continue an RP from last round.", + "Explore the depths of that cute fox you met in the bar.", + "Have fun and make it all up as you go along!", + ) + day_in_the_life = {" + As a Backstage character, your day-to-day life is pretty much whatever you want it to be. + "} + recommended_skills = list() + focus = "Anywhere from testing game mechanics to long-form roleplay in a largely safe area." + difficulty = "Easy. You can't exactly 'fail' at this role, unless you're trying to." selection_color = "#dddddd" paycheck = 0 // They're likely gonna get Cozy and not get paid anyway access = list() @@ -91,6 +248,55 @@ spawn_positions = -1 outfit = /datum/outfit/job/cb + faction_name = "New Boston Townies" + faction_leader = "The leader of the New Boston Townies is the Adventurers Guild." + faction_short_description = {" + Residents of New Boston, these hardy, ambitious souls have come together to make a living in the harsh wasteland. + They are the backbone of the settlement, providing the goods and services that keep the community running. + "} + faction_lore = {" + New Boston, the town on the edge of what could be considered civilization. Situated on the southernmost end of the Corridor, just + before the Texarkana Swamp Heap, New Boston exists as the frontier between the known and the unknown, the 'mostly safe' and the + 'definitely not safe'. Arguably run by the Adventurers Guild, the town serves as a hub for all kinds of folks, those looking for work, + adventure, or just something to sate their lust for curiosity and excitement.

+ The Adventurers Guild is as the name suggests, a loose coalition of adventurers, mercenaries, and other such explorer-salvager folk + with a vested interest in taming the multi-dimensional hellscape that is the Wasteland. They're the entire reason that New Boston + came to be, as the town was founded by the Guild as a base of operations for their various expeditions and operations in the region. + Whether or not you're a part of the Guild, you're a part of New Boston, and that means you're a part of the town's future, for better + or for worse.

Add more lore here as needed. + "} + faction_vibe = {" + The general vibe of the New Boston Townies is one of community, cooperation, and survival. The residents of New Boston are a diverse + group of individuals, each with their own skills, talents, and ambitions, but all united in their desire to make a living in the + wasteland. The town is a place of opportunity, where hard work and determination can lead to success, and where the bonds of + friendship and camaraderie can help you through the toughest of times. It is a great faction for players who want to build a life + for themselves in the wasteland, and who are looking for a sense of community and belonging. + "} + faction_goals = {" + Your goals are to make a living for yourself in the wasteland, and to help the town of New Boston thrive. You can choose to be a + hero, a villain, or anything in between, but whatever you choose, your actions will have an impact on the town and its residents. + Whether you're a farmer, a prospector, a barkeep, or a preacher, you have a role to play in the town's future, and it's up to you + to make the most of it. + "} + faction_rivals = {" + The New Boston Townies are not officially aligned with, or against, any other faction, despite being the reason those factions exist + in the first place. While new Boston is typically seen as a neutral ground, it is not uncommon for the town to come into conflict + with other less savory groups, such as Redwater or the Tunnel Rats. Conflicts are entirely handled outside of the town, and anything + that happens in the wastes stays in the wastes. + "} + faction_allies = {" + The New Boston Townies are not officially aligned with any other faction, though they are generally considered to be on good + (or at least non-hostile) terms with most of the other factions in the region. The town serves as a hub for all kinds of folks, + and the residents of New Boston are known for their hospitality and willingness to work with others, so long as they don't cause + too much trouble. + "} + faction_points_of_contention = {" + While the New Boston Townies are generally seen as a friendly and welcoming bunch, they are not without their own internal + problems and conflicts. The town is a melting pot of different people, cultures, and beliefs, and tensions can run high at + times. Whether it's a dispute over land, resources, or just a good old-fashioned bar fight, the residents of New Boston are + not immune to the same problems that plague the rest of the wasteland. + "} + ////////////////////////////// /// Citizen /// A basic citizen of New Boston, here to make a living in this here wasteland diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm index e7007b7cc58..368f8467a40 100644 --- a/code/modules/jobs/jobs.dm +++ b/code/modules/jobs/jobs.dm @@ -266,12 +266,9 @@ GLOBAL_LIST_INIT(vault_positions, list( )) GLOBAL_LIST_INIT(wasteland_positions, list( - "Den Mob Boss", - "Den Mob Enforcer", - "Den Doctor", -// "Redwater Townie", - "Faithful", - "Vigilante", + // "Den Mob Boss", + // "Den Mob Enforcer", + // "Den Doctor", "Far-Lands Tribals", "Wastelander", "Character Testing Area" diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 4338c74376f..44004b5abc6 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -267,7 +267,7 @@ message_admins(msg) to_chat(usr, span_danger("The round is either not ready, or has already finished...")) return - if(!SSjob.ShowJobPreview(client?.ckey, href_list["SelectedJob"])) + if(!SSjob.ShowJobPreview(client?.ckey)) var/do_it_anyway = alert(src, "Are you sure you want to join as this role?", "Role Selection", "Yes", "No") if(do_it_anyway == "Yes") AttemptLateSpawn(href_list["SelectedJob"]) @@ -498,6 +498,7 @@ to_chat(client.mob, span_danger("Your ooc notes is empty, please enter information about your roleplaying preferences.")) return + . = TRUE //Remove the player from the join queue if he was in one and reset the timer SSticker.queued_players -= src SSticker.queue_delay = 4 @@ -718,6 +719,10 @@ LateChoices() /mob/dead/new_player/proc/LateChoices() + if(SSjob.ShowJobPreview(client?.ckey)) + return // its handlinmg imt + + var/list/dat = list() dat += "
Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
" diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi index 74f0b3b20be61c49becbe3bd3a29acf01f603d74..ce7d113e5e68ec4934dd18d46c7ed27f17fb80d5 100644 GIT binary patch delta 8921 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 delta 8653 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 M4WsMj*Bqn%3mqEy`Tzg` diff --git a/tgui/packages/tgui/interfaces/JobPreview.js b/tgui/packages/tgui/interfaces/JobPreview.js index 7db9999a946..5a82b3f6344 100644 --- a/tgui/packages/tgui/interfaces/JobPreview.js +++ b/tgui/packages/tgui/interfaces/JobPreview.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import { useBackend, useLocalState, @@ -14,6 +15,7 @@ import { Table, Fragment, Tabs, + ToggleBox, Icon, } from '../components'; import { toFixed } from 'common/math'; @@ -22,7 +24,11 @@ import { formatTime, } from '../format'; import { Window } from '../layouts'; -import { multiline } from '../../common/string'; +import { + multiline, +} from '../../common/string'; +import { marked } from 'marked'; +import { sanitizeText } from '../sanitize'; const MAIN = true; const JDESC = false; @@ -77,31 +83,49 @@ export const JobPreview = (props, context) => { const MenuState = Object .keys(MyJobject) .length === 0 ? MAIN : JDESC; - const JobMenuWidth = MenuState === MAIN ? "100%" : "5%"; - const DescriptionWidth = MenuState === MAIN ? "0%" : "95%"; + const JobMenuWidth = MenuState === MAIN ? "100%" : "30%"; + const DescriptionWidth = MenuState === MAIN ? "0%" : "70%"; const JobMenuShrinkfit = MenuState === MAIN ? 0 : 1; return ( - - + - - - {MenuState === JDESC ? ( - - - - ) : null} + + + + {MenuState === JDESC ? ( + + + + ) : null} + + -
+ ))} -
+ ); }; +// The job link +// Holds the job's title and color +const JobLink = (props, context) => { + const { act, data } = useBackend(context); + const { + Job = {}, + } = props; + + const { + Title, + JobColor, + HasTimeLock, + ReqType, + ReqMinutes, + CurrentMinutes, + RawTimeLeft, + TotalPositions, + CurrentPositions, + SpawnFailure, + } = Job; + + if (HasTimeLock && RawTimeLeft > 0) { + return ( + + + + {`${Title} [${ReqMinutes}]`} + + + ); + } else if (TotalPositions <= 1) { // Infinite positions + return ( + + + {Title} + + ); + } else if (CurrentPositions >= TotalPositions) { + return ( + + + {Title + " [FULL]"} + + ); + } else { + return ( + + + {Title + " [" + CurrentPositions + "/" + TotalPositions + "]"} + + ); + } +}; + // The job description // Holds the job's description and details const JobDescription = (props, context) => { @@ -238,66 +370,318 @@ const JobDescription = (props, context) => { ReqType, Difficulty, JobColor, + HasTimeLock, + CurrentMinutes, + RawTimeLeft, CurrencyUnit, - } = MyJob; + BriefDescription, + Lore, + LoreShort, + Subordinates, + OverallResponsibilities, + SpecificResponsibilities = [], + DayInTheLife, + RecommendedSkills = [], + Focus, + OOCRules, + FactionName, + FactionLeader, + FactionShortDescription, + FactionLore, + FactionVibe, + FactionGoals, + FactionRivals, + FactionAllies, + FactionPointsOfContention, + } = MyJobject; + + // skills are formatted as ["Skill Name", "Skill Description", "Skill Name", "Skill Description"] + // so we need to convert them into objects + const RecommendedSkillsObjects = []; + if (RecommendedSkills && RecommendedSkills.length > 0) { + for (let i = 0; i < RecommendedSkills.length; i += 2) { + RecommendedSkillsObjects.push([ + RecommendedSkills[i], + RecommendedSkills[i + 1], + ]); + } + } + + const CellStyle = { + "border": "1px solid " + JobColor, + "borderRadius": "5px", + }; return (
+ )}> - - - {ReqType - && ReqType.length > 1 - && ReqMinutes - && ReqMinutes.length > 1 ? ( - - {"This job requires " - + ReqMinutes - + " of playtime as " - + ReqType - + " to take!"} - - ) : null} - - {Description} + {HasTimeLock && RawTimeLeft > 0 + ? ( + + {"This role requires " + + ReqMinutes + + " of playtime as " + + ReqType + + " to take!"} +
+ {" You have " + + CurrentMinutes + + " of playtime as " + + ReqType + + " so far."} +
+ {" Keep it up!"}
- {Extrastuff && Extrastuff.length > 0 ? ( - - {Extrastuff} - + ) : null} + {/* The job specific stuff */} +
+ + + {/* Pay */} + + {"Paycheck: " + + formatMoney(Paycheck) + " " + CurrencyUnit} + + {/* Lorestuffs */} + {Lore && Lore.length > 0 + ? LoreShort ? ( + + )} + ClosedStuff={( + + )} /> + ) : ( + ) : null} - - - - - {Supervisors} - - - {Paycheck} {CurrencyUnit} - - {Forbids && Forbids.length > 0 ? ( - - {Forbids} - - ) : null} - {Enforces && Enforces.length > 0 ? ( - - {Enforces} - - ) : null} - - - +
+ {/* Responsibilities */} {/* Specific responsibilities */} + {OverallResponsibilities && OverallResponsibilities.length > 0 + ? ( +
+ + {SpecificResponsibilities && SpecificResponsibilities.length > 0 + ? ( + + {SpecificResponsibilities.map((Responsibility, index) => ( + +    +    + + + ))} + + ) : null} +
+ ) : null} + {/* Subs and supers */} + {Supervisors && Supervisors.length > 0 + ? ( +
+ +
+ ) : null} + {Subordinates && Subordinates.length > 0 + ? ( +
+ +
+ ) : null} + {/* Forbids and enforces */} + {Forbids && Forbids.length > 0 + ? ( +
+ +
+ ) : null} + {Enforces && Enforces.length > 0 + ? ( +
+ +
+ ) : null} + {/* Day in the life */} + {DayInTheLife && DayInTheLife.length > 0 + ? ( +
+ +
+ ) : null} + {/* Recommended skills */} + {RecommendedSkills && RecommendedSkills.length > 0 + ? ( // skill format: ["Skill Name", "Skill Description"] +
+ + {RecommendedSkillsObjects.map((Skill, index) => ( + + + + + + + + + ))} +
+
+ ) : null} + {/* Difficulty */} + {Difficulty && Difficulty > 0 + ? ( +
+ +
+ ) : null} + {/* Focus */} + {Focus && Focus.length > 0 + ? ( +
+ +
+ ) : null} + {/* OOC rules */} + {OOCRules && OOCRules.length > 0 + ? ( +
+ +
+ ) : null} + {/* Faction stuff */} + {FactionName && FactionName.length > 0 + ? ( + +
+ {FactionShortDescription && FactionShortDescription.length > 0 + ? ( + + ) : null} + {FactionLeader && FactionLeader.length > 0 + ? ( + + ) : null} + {FactionLore && FactionLore.length > 0 + ? ( + + ) : null} + {FactionVibe && FactionVibe.length > 0 + ? ( + + ) : null} + {FactionGoals && FactionGoals.length > 0 + ? ( + + ) : null} + {FactionRivals && FactionRivals.length > 0 + ? ( + + ) : null} + {FactionAllies && FactionAllies.length > 0 + ? ( + + ) : null} + {FactionPointsOfContention && FactionPointsOfContention.length > 0 + ? ( + + ) : null} +
+
+ ) : null}
); }; +// The cool box +// Formats text to be cool +// mkas a box that is the box spefified in the props +// with text formatted by CoolFormat +// and put in dangerouslySetInnerHTML +const CoolBox = (props, context) => { + const { + ...rest + } = props; + return ( + + ); +}; + +// formats descriptions to be cool +const CoolFormat = (text) => { + const SanitizedText = sanitizeText(text); + const MarkedText = marked( + SanitizedText, + { + smartypants: false, + gfm: false, + tables: false, + sanitize: false, + breaks: true, + smartLists: false, + } + ); + return SanitizedText; +}; + const JoinButton = (props, context) => { const { act, data } = useBackend(context); const { @@ -318,39 +702,46 @@ const JoinButton = (props, context) => { CanSpawnAs, TimeTillCanSpawn, SpawnFailure, + } = props.Job; + const { + CurrentJobTitle, + IsInGame, } = data; const ButtCantIcon = "times"; const ButtCanIcon = "user"; const ButtCantStyle = { - "color": "black", + "color": "red", }; const ButtCanStyle = { "color": "#00FF00", }; + if (CurrentJobTitle === Title) { + return ( + + {"This is your current role!"} + + ); + } else if (IsInGame) { + return (null); + } switch (SpawnFailure) { - // Can do! - case 0: - return ( -