diff --git a/CMLS.txt b/CMLS.txt
new file mode 100644
index 00000000000..b51c7528854
--- /dev/null
+++ b/CMLS.txt
@@ -0,0 +1,118 @@
+# HOW TO CMLS
+
+There are only two things to worry about: The Gun and the Ammo Kind
+
+### AmmoKind
+This defines all the settings for the casing, projectile, magazine, and ammobox. This datum, depending on the vars you set, will automatically:
+ - Generate a box and/or a crate that'll be added to the CMLS vendor
+ - Generate a projectile, casing, and magazine
+ - Load all the sprites from the associated icon file
+
+Lets say you want to add in 4.92x14mm Scrungy. This'll be a basic Medium AmmoKind. It would look something like this:
+```C
+/datum/ammo_kind/medium/q_4_92x14mm_scrungy
+ name = "4.92x14mm Scrungy"
+ bullet_flavor = "A very scrungy bullet." // You can paste in somme bullshit AI algovomit here, the longer the better, it will show up on the casing (but not the magazine!)."
+ casing_kind = "cartridge" // 'you load a 4.92x14mm Scrungy cartridge into the gun'
+ projectile_kind = "bullet" // 'you are hit by a 4.92x14mm Scrungy bullet!'
+ box_name = "box of 4.92x14mm Scrungy bullets" // name of the associated box of bullets
+ box_flavor = "Algovomit goes here" // desc of the box
+ crate_name = "crate of 4.92x14mm Scrungy bullets" // name of the associated crate
+ crate_flavor = "More algovomit" // desc of the crate
+ magazine_name = "compact magazine" // if your gun can eject a magazine, it makes a magazine with this name when you eject it
+ magazine_flavor = "Its a magazine!" // desc of that magazine
+ caliber = CALIBER_COMPACT // the caliber of the casing, boxes, crates, and magazines
+ sound_properties = CSP_PISTOL_LIGHT // the sounds this bullet makes (look up [code\modules\projectiles\ammo_casing_gun_sound_properties.dm])
+ ammo_icon = 'icons/obj/ammo/compact.dmi' // the icon that the datum pulls all its sprites from (it does this automatically!)
+ damage_list = list(
+ "30" = 30,
+ "35" = 10,
+ "40" = 1,
+ "200" = 1,
+ ) // the list of damages this bullet will do. All statistical things are calculated by the datum from this list. If you have a really high value in there somewhere, the datum will interpret that as a crit, and generate a statblock accordingly.
+ damage_type = BRUTE // Damage type of the projectile, look up [code\__DEFINES\combat.dm] around line 6ish
+ damage_armor = "bullet" // the armor type the projectile checks against
+ pellet_count = 1 // number of pellets, used for shotguns
+ caseless = FALSE // Deletes the casing on shooting, not sure if it works
+```
+
+For any children of this AmmoKind, all you really need are the names and flavors. If any of the names or flavors are not set, the AmmoKind will automatically generate somewhat fitting names and flavors for whatever's missing. For instance, this is a perfectly valid AmmoKind:
+
+```C
+/datum/ammo_kind/medium/q_4_92x14mm_scrungy
+ name = "4.92x14mm Scrungy"
+```
+
+It will inherit all the vars from ammo_kind/medium!
+
+If your AmmoKind doesn't have any special sprites (as in, the projectile, casing, box, crate, and magazine don't need to look any different from the parent AmmoKind), you're done for the AmmoKind section! Yay! We'll get into how to make it look different later.
+
+### Gun
+Ballistic guns can be CMLSed!
+Say you want to make a gun for that Scrungy round. A basic one would look like this:
+
+```C
+/obj/item/gun/ballistic/scrungy_classic
+ name = "Superduper Scrungy Classic"
+ desc = "This gun sucks (and swallows)"
+
+ use_cmls = TRUE // Forces the gun to use the CMLS system
+ var/damage_list = list(
+ "10" = 50,
+ "1" = 2,
+ "40" = 2,
+ ) // If set, these values will be used instead of the damages in AmmoKind
+ damage_type = BRUTE // Overrides the damage type of the projectile. Can be null to use the AmmoKind's value
+ damage_armor_type = "bullet" // Overrides the armor check of the projectile. Can be null to use the AmmoKind's value
+ ammo_kind = /datum/ammo_kind/medium/q_4_92x14mm_scrungy // the AmmoKind that this gun will use. It will set up everything on the gun, nice and easy
+ ammo_magazine_name = "%MAXAMMO% round clipazine" // Name of the magazine inside the gun, for the text used when you 'eject' the magazine
+ ammo_capacity = 10 // How many bullets can go in the gun
+ ammo_single_load = FALSE // Whether or not you can only load one bullet at a time
+ is_revolver = FALSE // when you go to eject the magazine, it instead just dumps out the casings, like a revolver
+ sound_magazine_eject = "gun_remove_empty_magazine" // sound it makes when you eject the magazine, if applicable
+ var/sound_magazine_insert = "gun_insert_full_magazine" // sound it makes when you insert a magazine, if applicable
+```
+
+And that's it! Your gun (should) be fully functional at this point! Do note that the AmmoKind sprite cataloguer does *not* handle the gun's sprites, those are still handled in the same way as before, so be sure your gun isn't invisible after your changes!
+
+### Sprites
+AmmoKinds automatically read the icon states in their ammo_icon and catalogue all the sprites associated with the kind of ammo it is, handling all that mess on its end! The way it does this is that it runs through the names of each icon_state, reads certain keywords, and categorizes them accordingly.
+
+These names are made up of one of two sets of tokens:
+
+"CORB-suffix" for states with no variation (full boxes, empty boxes, etc)
+
+"CORB-suffix-partial-key" for states that vary based on the number of bullets in the associated box
+
+CORB can be one of four things:
+ - bullet
+ - box
+ - crate
+ - magazine
+
+Suffix can be one of four things:
+ - projectile
+ - full
+ - empty
+ - partial
+
+Partial can be one of three things:
+ - broad
+ - percent
+ - count
+
+Key depends on if Partial is percent or count
+ - For percent, it will display this sprite if the ammobox is less than this percent full of bullets
+ - For count, it will display this sprite if the ammobox contains this amount or less of bullets inside
+
+I'll expand on this later, but the attached images should explain at least some of it!
+
+
+
+
+
+
+
+
+
+
diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm
index 2f1b61a197f..c1e77398571 100644
--- a/code/__DEFINES/jobs.dm
+++ b/code/__DEFINES/jobs.dm
@@ -260,6 +260,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"
@@ -414,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 01fdf6657f5..2cf53b23b33 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,136 @@ SUBSYSTEM_DEF(job)
/datum/controller/subsystem/job/proc/JobDebug(message)
log_job_debug(message)
+
+/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.showme()
+
+/// 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() // merek gives good catjobs
+ /// now fill them with some jobs
+ for(var/cjob in categlob["jobs"])
+ var/datum/job/job = SSjob.GetJob(cjob)
+ if(!job)
+ continue
+ var/list/tug_slug = job.get_tgui_slug()
+ if(!tug_slug)
+ continue
+ tug_slug["Category"] = cat
+ category_slug["CatJobs"] += list(tug_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()
+ var/mob/user = ckey2mob(my_ckey)
+ ui_interact(user)
+ return TRUE
+
+/datum/job_preview_holder/proc/showme()
+ 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
+ compile_tgui_map()
+ 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/list/cat in slugpower)
+ for(var/list/job in cat["CatJobs"])
+ var/datum/job/j = SSjob.GetJob(job["Title"])
+ if(!j)
+ continue
+ var/my_time = LAZYACCESS(xps, j.exp_type)
+ 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["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)
+ else
+ job["SpawnFailure"] = JOB_UNAVAILABLE_NOT_NEWPLAYER
+ var/list/data = list()
+ data["AllJobsNCats"] = slugpower
+ 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)
+ . = ..()
+ var/mob/user = ckey2mob(params["MyCkey"])
+ if(!user)
+ return
+ 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
+ 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 fe6e591a19b..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,17 +81,66 @@
/// 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
+
+
+ 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
@@ -157,7 +203,7 @@
/datum/job/proc/override_latejoin_spawn(mob/living/carbon/human/H) //Return TRUE to force latejoining to not automatically place the person in latejoin shuttle/whatever.
return FALSE
-//Used for a special check of whether to allow a client to latejoin as this job.
+//Used for a special check of whether to allow a client to latejoin as this
/datum/job/proc/special_check_latejoin(client/C)
return TRUE
@@ -259,6 +305,58 @@
return max(0, minimal_player_age - C.player_age)
+
+/datum/job/proc/get_tgui_slug() // meor
+ if(LAZYLEN(tgui_slug))
+ return tgui_slug
+ var/list/coolgat = list()
+ coolgat["Title"] = title
+ coolgat["Path"] = "[type]"
+ coolgat["Description"] = description
+ coolgat["Supervisors"] = supervisors
+ coolgat["Paycheck"] = paycheck || 0
+ coolgat["TotalPositions"] = total_positions || 0
+ coolgat["SpawnPositions"] = spawn_positions || 0
+ coolgat["CurrentPositions"] = current_positions || 0
+ coolgat["Forbids"] = forbids
+ coolgat["Enforces"] = enforces
+ coolgat["Extrastuff"] = extrastuff
+ coolgat["HasTimeLock"] = exp_requirements ? exp_requirements > 0 : FALSE
+ coolgat["ReqMinutes"] = "[DisplayTimeText(exp_requirements, abbreviated = TRUE)]"
+ 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
+
/datum/job/proc/config_check()
return TRUE
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 5310454e9de..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"
@@ -412,6 +409,29 @@ GLOBAL_LIST_INIT(position_categories, list(
"Den" = list("jobs" = den_positions, "color" = "#884488"),
))
+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" = tunnelrats_positions | redwater_positions | ashdown_positions | den_positions), // 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 8854848b81e..44004b5abc6 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))
+ 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,7 @@
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
-
+ . = 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
@@ -530,10 +514,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()
@@ -735,6 +719,10 @@
LateChoices()
/mob/dead/new_player/proc/LateChoices()
+ if(SSjob.ShowJobPreview(client?.ckey))
+ return // its handlinmg imt
+
+
var/list/dat = list()
dat += "