diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm index 3c7399d98fa..41c2b4b7459 100644 --- a/code/__DEFINES/dcs/signals/signals_global.dm +++ b/code/__DEFINES/dcs/signals/signals_global.dm @@ -84,6 +84,9 @@ /// (new_name, old_name) #define COMSIG_GLOB_STATION_NAME_CHANGED "!station_name_changed" +/// Global signal sent before we decide what job everyone has +#define COMSIG_GLOB_PRE_JOBS_ASSIGNED "!pre_roles_assigned" + /// global signal when a global nullrod type is picked #define COMSIG_GLOB_NULLROD_PICKED "!nullrod_picked" diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 23c731565d7..ba1f3e9ab82 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -76,6 +76,7 @@ //Supply #define JOB_QUARTERMASTER "Quartermaster" #define JOB_CARGO_TECHNICIAN "Cargo Technician" +#define JOB_CARGO_GORILLA "Cargo Gorilla" #define JOB_SHAFT_MINER "Shaft Miner" #define JOB_BITRUNNER "Bitrunner" //Service @@ -140,21 +141,22 @@ #define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 20 #define JOB_DISPLAY_ORDER_SHAFT_MINER 21 #define JOB_DISPLAY_ORDER_BITRUNNER 22 -#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 23 -#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 24 -#define JOB_DISPLAY_ORDER_PARAMEDIC 25 -#define JOB_DISPLAY_ORDER_CHEMIST 26 -#define JOB_DISPLAY_ORDER_VIROLOGIST 27 -#define JOB_DISPLAY_ORDER_CORONER 28 -#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 29 -#define JOB_DISPLAY_ORDER_SCIENTIST 30 -#define JOB_DISPLAY_ORDER_ROBOTICIST 31 -#define JOB_DISPLAY_ORDER_GENETICIST 32 -#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 33 -#define JOB_DISPLAY_ORDER_WARDEN 34 -#define JOB_DISPLAY_ORDER_DETECTIVE 35 -#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 36 -#define JOB_DISPLAY_ORDER_PRISONER 37 +#define JOB_DISPLAY_ORDER_CARGO_GORILLA 23 +#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 24 +#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 25 +#define JOB_DISPLAY_ORDER_PARAMEDIC 26 +#define JOB_DISPLAY_ORDER_CHEMIST 27 +#define JOB_DISPLAY_ORDER_VIROLOGIST 28 +#define JOB_DISPLAY_ORDER_CORONER 29 +#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 30 +#define JOB_DISPLAY_ORDER_SCIENTIST 31 +#define JOB_DISPLAY_ORDER_ROBOTICIST 32 +#define JOB_DISPLAY_ORDER_GENETICIST 33 +#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 34 +#define JOB_DISPLAY_ORDER_WARDEN 35 +#define JOB_DISPLAY_ORDER_DETECTIVE 36 +#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 37 +#define JOB_DISPLAY_ORDER_PRISONER 38 #define DEPARTMENT_UNASSIGNED "No Department" @@ -200,6 +202,10 @@ #define JOB_CAN_BE_INTERN (1<<8) /// This job cannot have more slots opened by the Head of Personnel (but admins or other random events can still do this). #define JOB_CANNOT_OPEN_SLOTS (1<<9) +/// This job will not display on the job menu when there are no slots available, instead of appearing greyed out +#define JOB_HIDE_WHEN_EMPTY (1<<10) +/// This job cannot be signed up for at round start or recorded in your preferences +#define JOB_LATEJOIN_ONLY (1<<11) /// Combination flag for jobs which are considered regular crew members of the station. #define STATION_JOB_FLAGS (JOB_ANNOUNCE_ARRIVAL|JOB_CREW_MANIFEST|JOB_EQUIP_RANK|JOB_CREW_MEMBER|JOB_NEW_PLAYER_JOINABLE|JOB_REOPEN_ON_ROUNDSTART_LOSS|JOB_ASSIGN_QUIRKS|JOB_CAN_BE_INTERN) diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm index 8ac2f0d3ddf..b77942bad87 100644 --- a/code/_globalvars/lists/mapping.dm +++ b/code/_globalvars/lists/mapping.dm @@ -107,6 +107,7 @@ GLOBAL_LIST_EMPTY(generic_event_spawns) /// essentially allowing a user to override generic job spawnpoints with a specific one GLOBAL_LIST_EMPTY(jobspawn_overrides) +GLOBAL_LIST_EMPTY(gorilla_start) GLOBAL_LIST_EMPTY(wizardstart) GLOBAL_LIST_EMPTY(nukeop_start) GLOBAL_LIST_EMPTY(nukeop_leader_start) diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index bc195bcf5f4..c51fbaa9eb7 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -25,6 +25,7 @@ GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1. GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt")) GLOBAL_LIST_INIT(cyberauth_names, world.file2list("strings/names/cyberauth.txt")) GLOBAL_LIST_INIT(syndicate_monkey_names, world.file2list("strings/names/syndicate_monkey.txt")) +GLOBAL_LIST_INIT(cargorilla_names, world.file2list("strings/names/cargorilla.txt")) GLOBAL_LIST_INIT(guardian_first_names, world.file2list("strings/names/guardian_descriptions.txt")) GLOBAL_LIST_INIT(guardian_tech_surnames, world.file2list("strings/names/guardian_gamepieces.txt")) GLOBAL_LIST_INIT(guardian_fantasy_surnames, world.file2list("strings/names/guardian_tarot.txt")) diff --git a/code/_onclick/hud/new_player.dm b/code/_onclick/hud/new_player.dm index 2b2660b923f..9719a8a3859 100644 --- a/code/_onclick/hud/new_player.dm +++ b/code/_onclick/hud/new_player.dm @@ -1,37 +1,65 @@ #define SHUTTER_MOVEMENT_DURATION 0.4 SECONDS #define SHUTTER_WAIT_DURATION 0.2 SECONDS +/// Maximum number of station trait buttons we will display, please think hard before creating scenarios where there are more than this +#define MAX_STATION_TRAIT_BUTTONS_VERTICAL 3 /datum/hud/new_player ///Whether the menu is currently on the client's screen or not var/menu_hud_status = TRUE /datum/hud/new_player/New(mob/owner) - ..() + . = ..() - if(!owner || !owner.client) + if (!owner || !owner.client) return - if(owner.client.interviewee) + if (owner.client.interviewee) return var/list/buttons = subtypesof(/atom/movable/screen/lobby) - for(var/button_type in buttons) - var/atom/movable/screen/lobby/lobbyscreen = new button_type(our_hud = src) + for (var/atom/movable/screen/lobby/lobbyscreen as anything in buttons) + if (!initial(lobbyscreen.always_available)) + continue + lobbyscreen = new lobbyscreen(our_hud = src) lobbyscreen.SlowInit() static_inventory += lobbyscreen - if(!lobbyscreen.always_shown) + if (!lobbyscreen.always_shown) lobbyscreen.RegisterSignal(src, COMSIG_HUD_LOBBY_COLLAPSED, TYPE_PROC_REF(/atom/movable/screen/lobby, collapse_button)) lobbyscreen.RegisterSignal(src, COMSIG_HUD_LOBBY_EXPANDED, TYPE_PROC_REF(/atom/movable/screen/lobby, expand_button)) - if(istype(lobbyscreen, /atom/movable/screen/lobby/button)) + if (istype(lobbyscreen, /atom/movable/screen/lobby/button)) var/atom/movable/screen/lobby/button/lobby_button = lobbyscreen lobby_button.owner = REF(owner) + add_station_trait_buttons() + +/// Display buttons for relevant station traits +/datum/hud/new_player/proc/add_station_trait_buttons() + if (!mymob?.client || mymob.client.interviewee || !length(GLOB.lobby_station_traits)) + return + var/buttons_created = 0 + var/y_offset = 397 + var/y_button_offset = 27 + for (var/datum/station_trait/trait as anything in GLOB.lobby_station_traits) + if (!trait.can_display_lobby_button()) + continue + var/atom/movable/screen/lobby/button/sign_up/sign_up_button = new(our_hud = src) + sign_up_button.SlowInit() + sign_up_button.owner = REF(mymob) + sign_up_button.screen_loc = offset_to_screen_loc(233, y_offset, mymob.client.view) + y_offset += y_button_offset + static_inventory += sign_up_button + trait.setup_lobby_button(sign_up_button) + buttons_created++ + if (buttons_created >= MAX_STATION_TRAIT_BUTTONS_VERTICAL) + return /atom/movable/screen/lobby plane = SPLASHSCREEN_PLANE layer = LOBBY_MENU_LAYER screen_loc = "TOP,CENTER" - ///Whether this HUD element can be hidden from the client's "screen" (moved off-screen) or not + /// Whether this HUD element can be hidden from the client's "screen" (moved off-screen) or not var/always_shown = FALSE + /// If true we will create this button every time the HUD is generated + var/always_available = TRUE ///Set the HUD in New, as lobby screens are made before Atoms are Initialized. /atom/movable/screen/lobby/New(loc, datum/hud/our_hud, ...) @@ -399,6 +427,23 @@ var/mob/dead/new_player/new_player = hud.mymob new_player.handle_player_polling() +/// A generic "sign up" button used by station traits +/atom/movable/screen/lobby/button/sign_up + icon = 'icons/hud/lobby/signup_button.dmi' + icon_state = "signup" + base_icon_state = "signup" + always_available = FALSE + +/atom/movable/screen/lobby/button/sign_up/MouseEntered(location, control, params) + . = ..() + if(QDELETED(src) || !desc) + return + openToolTip(usr, tip_src = src, params = params, title = name, content = desc,) + +/atom/movable/screen/lobby/button/sign_up/MouseExited() + . = ..() + closeToolTip(usr) + /atom/movable/screen/lobby/button/collapse name = "Collapse Lobby Menu" icon = 'icons/hud/lobby/collapse_expand.dmi' @@ -526,3 +571,4 @@ #undef SHUTTER_MOVEMENT_DURATION #undef SHUTTER_WAIT_DURATION +#undef MAX_STATION_TRAIT_BUTTONS_VERTICAL diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 160a296abfc..f7523172274 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -55,6 +55,10 @@ /atom/movable/screen/proc/component_click(atom/movable/screen/component_button/component, params) return +/// Returns the mob this is being displayed to, if any +/atom/movable/screen/proc/get_mob() + return hud?.mymob + /atom/movable/screen/text icon = null icon_state = null diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index dae7845ac9f..ea7f6029755 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -94,6 +94,18 @@ SUBSYSTEM_DEF(job) set_overflow_role(CONFIG_GET(string/overflow_job)) // this must always go after load_jobs_from_config() due to how the legacy systems operate, this always takes precedent. return SS_INIT_SUCCESS +/// Returns a list of jobs that we are allowed to fuck with during random events +/datum/controller/subsystem/job/proc/get_valid_overflow_jobs() + var/static/list/overflow_jobs + if (!isnull(overflow_jobs)) + return overflow_jobs + + overflow_jobs = list() + for (var/datum/job/check_job in joinable_occupations) + if (!check_job.allow_bureaucratic_error) + continue + overflow_jobs += check_job + return overflow_jobs /datum/controller/subsystem/job/proc/set_overflow_role(new_overflow_role) var/datum/job/new_overflow = ispath(new_overflow_role) ? GetJobType(new_overflow_role) : GetJob(new_overflow_role) diff --git a/code/controllers/subsystem/processing/station.dm b/code/controllers/subsystem/processing/station.dm index b30a276fc36..224d790faca 100644 --- a/code/controllers/subsystem/processing/station.dm +++ b/code/controllers/subsystem/processing/station.dm @@ -18,6 +18,7 @@ PROCESSING_SUBSYSTEM_DEF(station) // Autowiki also wants consistent outputs, for example making sure the vending machine page always reports the normal products #if !defined(UNIT_TESTS) && !defined(AUTOWIKI) SetupTraits() + display_lobby_traits() #endif announcer = new announcer() //Initialize the station's announcer datum @@ -96,3 +97,12 @@ PROCESSING_SUBSYSTEM_DEF(station) for(var/i in trait_instance.blacklist) var/datum/station_trait/trait_to_remove = i selectable_traits_by_types[initial(trait_to_remove.trait_type)] -= trait_to_remove + +/// Update station trait lobby buttons for clients who joined before we initialised this subsystem +/datum/controller/subsystem/processing/station/proc/display_lobby_traits() + for (var/mob/dead/new_player/player as anything in GLOB.new_player_list) + var/datum/hud/new_player/observer_hud = player.hud_used + if (!istype(observer_hud)) + continue + observer_hud.add_station_trait_buttons() + observer_hud.show_hud(observer_hud.hud_version) diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 4480db95af9..9d2fd01732e 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -236,6 +236,7 @@ SUBSYSTEM_DEF(ticker) var/can_continue = FALSE can_continue = SSdynamic.pre_setup() //Choose antagonists CHECK_TICK + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_JOBS_ASSIGNED, src) can_continue = can_continue && SSjob.DivideOccupations() //Distribute jobs CHECK_TICK diff --git a/code/datums/components/ghost_direct_control.dm b/code/datums/components/ghost_direct_control.dm index c4d08380db1..412ae0f767e 100644 --- a/code/datums/components/ghost_direct_control.dm +++ b/code/datums/components/ghost_direct_control.dm @@ -30,7 +30,7 @@ src.ban_type = ban_type src.assumed_control_message = assumed_control_message || "You are [parent]!" src.extra_control_checks = extra_control_checks - src.after_assumed_control= after_assumed_control + src.after_assumed_control = after_assumed_control var/mob/mob_parent = parent LAZYADD(GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")], mob_parent) @@ -42,9 +42,10 @@ . = ..() RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, PROC_REF(on_ghost_clicked)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + RegisterSignal(parent, COMSIG_MOB_LOGIN, PROC_REF(on_login)) /datum/component/ghost_direct_control/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_ATOM_ATTACK_GHOST, COMSIG_ATOM_EXAMINE)) + UnregisterSignal(parent, list(COMSIG_ATOM_ATTACK_GHOST, COMSIG_ATOM_EXAMINE, COMSIG_MOB_LOGIN)) return ..() /datum/component/ghost_direct_control/Destroy(force, silent) @@ -138,3 +139,8 @@ to_chat(new_body, span_boldnotice(assumed_control_message)) after_assumed_control?.Invoke(harbinger) qdel(src) + +/// When someone else assumes control via some other means, get rid of our component +/datum/component/ghost_direct_control/proc/on_login() + SIGNAL_HANDLER + qdel(src) diff --git a/code/datums/station_traits/_station_trait.dm b/code/datums/station_traits/_station_trait.dm index 8e8303c0366..8e5b6310c71 100644 --- a/code/datums/station_traits/_station_trait.dm +++ b/code/datums/station_traits/_station_trait.dm @@ -1,3 +1,6 @@ +/// Station traits displayed in the lobby +GLOBAL_LIST_EMPTY(lobby_station_traits) + ///Base class of station traits. These are used to influence rounds in one way or the other by influencing the levers of the station. /datum/station_trait ///Name of the trait @@ -22,6 +25,10 @@ var/trait_flags = STATION_TRAIT_MAP_UNRESTRICTED /// Whether or not this trait can be reverted by an admin var/can_revert = TRUE + /// If set to true we'll show a button on the lobby to notify people about this trait + var/sign_up_button = FALSE + /// Lobby buttons controlled by this trait + var/list/lobby_buttons = list() /// The ID that we look for in dynamic.json. Not synced with 'name' because I can already see this go wrong var/dynamic_threat_id /// If ran during dynamic, do we reduce the total threat? Will be overriden by config if set @@ -34,7 +41,8 @@ if(threat_reduction) GLOB.dynamic_station_traits[src] = threat_reduction - + if(sign_up_button) + GLOB.lobby_station_traits += src if(trait_processes) START_PROCESSING(SSstation, src) if(trait_to_give) @@ -43,14 +51,10 @@ /datum/station_trait/Destroy() SSstation.station_traits -= src GLOB.dynamic_station_traits.Remove(src) + destroy_lobby_buttons() return ..() -/// Proc ran when round starts. Use this for roundstart effects. -/datum/station_trait/proc/on_round_start() - SIGNAL_HANDLER - return - -///type of info the centcom report has on this trait, if any. +/// Returns the type of info the centcom report has on this trait, if any. /datum/station_trait/proc/get_report() return "[name] - [report_message]" @@ -64,7 +68,7 @@ qdel(src) -///Called by decals if they can be colored, to see if we got some cool colors for them. Only takes the first station trait +/// Called by decals if they can be colored, to see if we got some cool colors for them. Only takes the first station trait /proc/request_station_colors(atom/thing_to_color, pattern) for(var/datum/station_trait/trait in SSstation.station_traits) var/decal_color = trait.get_decal_color(thing_to_color, pattern || PATTERN_DEFAULT) @@ -72,6 +76,50 @@ return decal_color return null -///Return a color for the decals, if any +/// Return a color for the decals, if any /datum/station_trait/proc/get_decal_color(thing_to_color, pattern) return + +/// Return TRUE if we want to show a lobby button, by default we assume we don't want it after the round begins +/datum/station_trait/proc/can_display_lobby_button() + return sign_up_button && !SSticker.HasRoundStarted() + +/// Apply any additional handling we need to our lobby button +/datum/station_trait/proc/setup_lobby_button(atom/movable/screen/lobby/button/sign_up/lobby_button) + SHOULD_CALL_PARENT(TRUE) + lobby_buttons |= lobby_button + RegisterSignal(lobby_button, COMSIG_ATOM_UPDATE_ICON, PROC_REF(on_lobby_button_update_icon)) + RegisterSignal(lobby_button, COMSIG_CLICK, PROC_REF(on_lobby_button_click)) + RegisterSignal(lobby_button, COMSIG_QDELETING, PROC_REF(on_lobby_button_destroyed)) + lobby_button.update_appearance(UPDATE_ICON) + +/// Called when our lobby button is clicked on +/datum/station_trait/proc/on_lobby_button_click(atom/movable/screen/lobby/button/sign_up/lobby_button, location, control, params, mob/dead/new_player/user) + SIGNAL_HANDLER + return + +/// Called when our lobby button tries to update its appearance +/datum/station_trait/proc/on_lobby_button_update_icon(atom/movable/screen/lobby/button/sign_up/lobby_button, updates) + SIGNAL_HANDLER + return + +/// Don't hold references to deleted buttons +/datum/station_trait/proc/on_lobby_button_destroyed(atom/movable/screen/lobby/button/sign_up/lobby_button) + SIGNAL_HANDLER + lobby_buttons -= lobby_button + +/// Proc ran when round starts. Use this for roundstart effects. By default we clean up our buttons here. +/datum/station_trait/proc/on_round_start() + SIGNAL_HANDLER + destroy_lobby_buttons() + +/// Remove all of our active lobby buttons +/datum/station_trait/proc/destroy_lobby_buttons() + for (var/atom/movable/screen/button as anything in lobby_buttons) + var/mob/hud_owner = button.get_mob() + qdel(button) + if (QDELETED(hud_owner)) + continue + var/datum/hud/using_hud = hud_owner.hud_used + using_hud?.show_hud(using_hud?.hud_version) + lobby_buttons = list() diff --git a/code/datums/station_traits/job_traits.dm b/code/datums/station_traits/job_traits.dm new file mode 100644 index 00000000000..12aa42332a9 --- /dev/null +++ b/code/datums/station_traits/job_traits.dm @@ -0,0 +1,95 @@ +/** + * A station trait which enables a temporary job + * Generally speaking these should always all be mutually exclusive, don't have too many at once + */ +/datum/station_trait/job + sign_up_button = TRUE + trait_flags = STATION_TRAIT_ABSTRACT + /// What tooltip to show on the button + var/button_desc = "Sign up to gain some kind of unusual job, not available in most rounds." + /// Type of job to enable + var/job_to_add = /datum/job/clown + /// Who signed up to this in the lobby + var/list/lobby_candidates + +/datum/station_trait/job/New() + . = ..() + blacklist += subtypesof(/datum/station_trait/job) - type // All but ourselves + RegisterSignal(SSdcs, COMSIG_GLOB_PRE_JOBS_ASSIGNED, PROC_REF(pre_jobs_assigned)) + +/datum/station_trait/job/setup_lobby_button(atom/movable/screen/lobby/button/sign_up/lobby_button) + RegisterSignal(lobby_button, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_lobby_button_update_overlays)) + lobby_button.desc = button_desc + return ..() + +/datum/station_trait/job/on_lobby_button_click(atom/movable/screen/lobby/button/sign_up/lobby_button, location, control, params, mob/dead/new_player/user) + if (LAZYFIND(lobby_candidates, user)) + LAZYREMOVE(lobby_candidates, user) + else + LAZYADD(lobby_candidates, user) + +/datum/station_trait/job/on_lobby_button_update_icon(atom/movable/screen/lobby/button/sign_up/lobby_button, updates) + if (LAZYFIND(lobby_candidates, lobby_button.get_mob())) + lobby_button.base_icon_state = "signup_on" + else + lobby_button.base_icon_state = "signup" + +/// Add an overlay based on whether you are actively signed up for this role +/datum/station_trait/job/proc/on_lobby_button_update_overlays(atom/movable/screen/lobby/button/sign_up/lobby_button, list/overlays) + SIGNAL_HANDLER + overlays += LAZYFIND(lobby_candidates, lobby_button.get_mob()) ? "tick" : "cross" + +/// Called before we start assigning roles, assign ours first +/datum/station_trait/job/proc/pre_jobs_assigned() + SIGNAL_HANDLER + sign_up_button = FALSE + destroy_lobby_buttons() + for (var/mob/dead/new_player/signee as anything in lobby_candidates) + if (isnull(signee) || !signee.client || !signee.mind || signee.ready != PLAYER_READY_TO_PLAY) + LAZYREMOVE(lobby_candidates, signee) + if (!LAZYLEN(lobby_candidates)) + on_failed_assignment() + return // Nobody signed up :( + var/mob/dead/new_player/picked_player = pick(lobby_candidates) + picked_player.mind.assigned_role = new job_to_add() + lobby_candidates = null + +/// Called if we didn't assign a role before the round began, we add it to the latejoin menu instead +/datum/station_trait/job/proc/on_failed_assignment() + var/datum/job/our_job = job_to_add + our_job = SSjob.GetJob(our_job::title) + our_job.total_positions++ + + +/// Adds a gorilla to the cargo department, replacing the sloth and the mech +/datum/station_trait/job/cargorilla + name = "Cargo Gorilla" + button_desc = "Sign up to become the Cargo Gorilla, a peaceful shepherd of boxes." + weight = 1 + show_in_report = FALSE // Selective attention test. Did you spot the gorilla? + job_to_add = /datum/job/cargo_gorilla + trait_flags = STATION_TRAIT_MAP_UNRESTRICTED + +/datum/station_trait/job/cargorilla/New() + . = ..() + RegisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(replace_cargo)) + +/datum/station_trait/job/cargorilla/on_lobby_button_update_overlays(atom/movable/screen/lobby/button/sign_up/lobby_button, list/overlays) + . = ..() + overlays += LAZYFIND(lobby_candidates, lobby_button.get_mob()) ? "gorilla_on" : "gorilla_off" + +/// Remove the cargo equipment and personnel that are being replaced by a gorilla. +/datum/station_trait/job/cargorilla/proc/replace_cargo(datum/source) + SIGNAL_HANDLER + var/mob/living/basic/sloth/cargo_sloth = GLOB.cargo_sloth + if(isnull(cargo_sloth)) + lobby_candidates = list() + destroy_lobby_buttons() // Sorry folks + sign_up_button = FALSE + return + + // hmm our sloth looks funny today + qdel(cargo_sloth) + // monkey carries the crates, the age of robot is over + if(GLOB.cargo_ripley) + qdel(GLOB.cargo_ripley) diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm index 50f43d3cfb9..7483aaf56b3 100644 --- a/code/datums/station_traits/negative_traits.dm +++ b/code/datums/station_traits/negative_traits.dm @@ -123,7 +123,7 @@ /datum/station_trait/overflow_job_bureaucracy/proc/set_overflow_job_override(datum/source) SIGNAL_HANDLER - var/datum/job/picked_job = pick(SSjob.joinable_occupations) + var/datum/job/picked_job = pick(SSjob.get_valid_overflow_jobs()) chosen_job_name = lowertext(picked_job.title) // like Chief Engineers vs like chief engineers SSjob.set_overflow_role(picked_job.type) diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm index d3a4b0d08a2..d008341b66a 100644 --- a/code/datums/station_traits/neutral_traits.dm +++ b/code/datums/station_traits/neutral_traits.dm @@ -141,60 +141,6 @@ var/new_colored_assistant_type = pick(subtypesof(/datum/colored_assistant) - get_configured_colored_assistant_type()) GLOB.colored_assistant = new new_colored_assistant_type -/datum/station_trait/cargorilla - name = "Cargo Gorilla" - trait_type = STATION_TRAIT_NEUTRAL - weight = 1 - show_in_report = FALSE // Selective attention test. Did you spot the gorilla? - - /// The gorilla we created, we only hold this ref until the round starts. - var/mob/living/basic/gorilla/cargorilla/cargorilla - -/datum/station_trait/cargorilla/New() - . = ..() - RegisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(replace_cargo)) - -/// Replace some cargo equipment and 'personnel' with a gorilla. -/datum/station_trait/cargorilla/proc/replace_cargo(datum/source) - SIGNAL_HANDLER - - var/mob/living/basic/sloth/cargo_sloth = GLOB.cargo_sloth - if(isnull(cargo_sloth)) - return - - cargorilla = new(cargo_sloth.loc) - cargorilla.name = cargo_sloth.name - // We do a poll on roundstart, don't let ghosts in early - INVOKE_ASYNC(src, PROC_REF(make_id_for_gorilla)) - // hm our sloth looks funny today - qdel(cargo_sloth) - - // monkey carries the crates, the age of robot is over - if(GLOB.cargo_ripley) - qdel(GLOB.cargo_ripley) - -/// Makes an ID card for the gorilla -/datum/station_trait/cargorilla/proc/make_id_for_gorilla() - var/obj/item/card/id/advanced/cargo_gorilla/gorilla_id = new(cargorilla.loc) - gorilla_id.registered_name = cargorilla.name - gorilla_id.update_label() - - cargorilla.put_in_hands(gorilla_id, del_on_fail = TRUE) - -/datum/station_trait/cargorilla/on_round_start() - if(!cargorilla) - return - - addtimer(CALLBACK(src, PROC_REF(get_ghost_for_gorilla), cargorilla), 12 SECONDS) // give ghosts a bit of time to funnel in - cargorilla = null - -/// Get us a ghost for the gorilla. -/datum/station_trait/cargorilla/proc/get_ghost_for_gorilla(mob/living/basic/gorilla/cargorilla/gorilla) - if(QDELETED(gorilla)) - return - - gorilla.poll_for_gorilla() - /datum/station_trait/birthday name = "Employee Birthday" trait_type = STATION_TRAIT_NEUTRAL diff --git a/code/modules/client/preferences/middleware/jobs.dm b/code/modules/client/preferences/middleware/jobs.dm index f07c0720061..201e57668ea 100644 --- a/code/modules/client/preferences/middleware/jobs.dm +++ b/code/modules/client/preferences/middleware/jobs.dm @@ -32,6 +32,8 @@ var/list/jobs = list() for (var/datum/job/job as anything in SSjob.joinable_occupations) + if (job.job_flags & JOB_LATEJOIN_ONLY) + continue var/datum/job_department/department_type = job.department_for_prefs || job.departments_list?[1] if (isnull(department_type)) stack_trace("[job] does not have a department set, yet is a joinable occupation!") @@ -86,6 +88,8 @@ var/list/job_required_experience = list() for (var/datum/job/job as anything in SSjob.all_occupations) + if (job.job_flags & JOB_LATEJOIN_ONLY) + continue var/required_playtime_remaining = job.required_playtime_remaining(user.client) if (required_playtime_remaining) job_required_experience[job.title] = list( diff --git a/code/modules/events/bureaucratic_error.dm b/code/modules/events/bureaucratic_error.dm index 5f206134a92..774100fb735 100644 --- a/code/modules/events/bureaucratic_error.dm +++ b/code/modules/events/bureaucratic_error.dm @@ -13,19 +13,15 @@ priority_announce("A recent bureaucratic error in the Organic Resources Department may result in personnel shortages in some departments and redundant staffing in others.", "Paperwork Mishap Alert") /datum/round_event/bureaucratic_error/start() - var/list/jobs = SSjob.joinable_occupations.Copy() - if(prob(33)) // Only allows latejoining as a single role. Add latejoin AI bluespace pods for fun later. + var/list/jobs = SSjob.get_valid_overflow_jobs() + if(prob(33)) // Only allows latejoining as a single role. var/datum/job/overflow = pick_n_take(jobs) overflow.spawn_positions = -1 overflow.total_positions = -1 // Ensures infinite slots as this role. Assistant will still be open for those that cant play it. for(var/job in jobs) var/datum/job/current = job - if(!current.allow_bureaucratic_error) - continue current.total_positions = 0 - else // Adds/removes a random amount of job slots from all jobs. - for(var/datum/job/current as anything in jobs) - if(!current.allow_bureaucratic_error) - continue - var/ran = rand(-2,4) - current.total_positions = max(current.total_positions + ran, 0) + return + // Adds/removes a random amount of job slots from all jobs. + for(var/datum/job/current as anything in jobs) + current.total_positions = max(current.total_positions + rand(-2,4), 0) diff --git a/code/modules/jobs/job_types/cargo_gorilla.dm b/code/modules/jobs/job_types/cargo_gorilla.dm new file mode 100644 index 00000000000..96a79e3a4d3 --- /dev/null +++ b/code/modules/jobs/job_types/cargo_gorilla.dm @@ -0,0 +1,50 @@ +/datum/job/cargo_gorilla + title = JOB_CARGO_GORILLA + description = "Assist the supply department by moving freight and disposing of unwanted fruits." + department_head = list(JOB_QUARTERMASTER) + faction = FACTION_STATION + total_positions = 0 + spawn_positions = 0 + supervisors = SUPERVISOR_QM + spawn_type = /mob/living/basic/gorilla/cargorilla + config_tag = "CARGO_GORILLA" + random_spawns_possible = FALSE + display_order = JOB_DISPLAY_ORDER_CARGO_GORILLA + departments_list = list(/datum/job_department/cargo) + mail_goodies = list( + /obj/item/food/grown/banana = 1, + ) + rpg_title = "Beast of Burden" + allow_bureaucratic_error = FALSE + job_flags = JOB_ANNOUNCE_ARRIVAL | JOB_NEW_PLAYER_JOINABLE | JOB_EQUIP_RANK | JOB_CANNOT_OPEN_SLOTS | JOB_HIDE_WHEN_EMPTY | JOB_LATEJOIN_ONLY + +/datum/job/cargo_gorilla/get_roundstart_spawn_point() + if (length(GLOB.gorilla_start)) + return pick(GLOB.gorilla_start) + return ..() + +/datum/job/cargo_gorilla/get_spawn_mob(client/player_client, atom/spawn_point) + if (!player_client) + return + var/mob/living/the_big_man = new spawn_type(get_turf(spawn_point)) + the_big_man.fully_replace_character_name(the_big_man.real_name, pick(GLOB.cargorilla_names)) + return the_big_man + +/datum/job/cargo_gorilla/after_spawn(mob/living/spawned, client/player_client) + . = ..() + // Gorilla with a wage, what's he buyin? + var/datum/bank_account/bank_account = new(spawned.real_name, src) + bank_account.payday(STARTING_PAYCHECKS, TRUE) + bank_account.replaceable = FALSE + spawned.add_mob_memory(/datum/memory/key/account, remembered_id = bank_account.account_id) + + var/obj/item/card/id/advanced/cargo_gorilla/gorilla_id = new(spawned.loc) + gorilla_id.registered_name = spawned.name + gorilla_id.update_label() + gorilla_id.registered_account = bank_account + bank_account.bank_cards += gorilla_id + spawned.put_in_hands(gorilla_id, del_on_fail = TRUE) + + to_chat(spawned, span_boldnotice("You are Cargorilla, a pacifist friend of the station and carrier of freight.")) + to_chat(spawned, span_notice("You can pick up crates by clicking on them, and drop them by clicking on the ground.")) + spawned.mind.special_role = "Cargorilla" diff --git a/code/modules/mob/dead/new_player/latejoin_menu.dm b/code/modules/mob/dead/new_player/latejoin_menu.dm index ca95e7a5c5e..bb871470e93 100644 --- a/code/modules/mob/dead/new_player/latejoin_menu.dm +++ b/code/modules/mob/dead/new_player/latejoin_menu.dm @@ -80,6 +80,8 @@ GLOBAL_DATUM_INIT(latejoin_menu, /datum/latejoin_menu, new) ) if(job_availability != JOB_AVAILABLE) + if (job_datum.job_flags & JOB_HIDE_WHEN_EMPTY) + continue job_data["unavailable_reason"] = get_job_unavailable_error_message(job_availability, job_datum.title) if(job_datum.total_positions < 0) @@ -95,6 +97,7 @@ GLOBAL_DATUM_INIT(latejoin_menu, /datum/latejoin_menu, new) /datum/latejoin_menu/ui_static_data(mob/user) var/list/departments = list() + var/mob/dead/new_player/owner = user for(var/datum/job_department/department as anything in SSjob.joinable_departments) var/list/department_jobs = list() @@ -108,6 +111,8 @@ GLOBAL_DATUM_INIT(latejoin_menu, /datum/latejoin_menu, new) //Jobs under multiple departments should only be displayed if this is their first department or the command department if(LAZYLEN(job_datum.departments_list) > 1 && job_datum.departments_list[1] != department.type && !(job_datum.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND)) continue + if((job_datum.job_flags & JOB_HIDE_WHEN_EMPTY) && owner.IsJobUnavailable(job_datum.title, latejoin = TRUE) != JOB_AVAILABLE) + continue var/list/job_data = list( "command" = !!(job_datum.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND), diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm index 1e17938c5fd..64cff6780df 100644 --- a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm @@ -158,31 +158,4 @@ ADD_TRAIT(src, TRAIT_PACIFISM, INNATE_TRAIT) AddComponent(/datum/component/crate_carrier) -/mob/living/basic/gorilla/cargorilla/death(gibbed) - var/datum/component/potential_component = GetComponent(/datum/component/ghost_direct_control) - if(!QDELETED(potential_component)) - qdel(potential_component) - return ..() - -/** - * Poll ghosts for control of the gorilla. Not added in init because we only want to poll when the round starts. - * Preferably in future we can replace this with a popup on the lobby to queue to become a gorilla. - */ -/mob/living/basic/gorilla/cargorilla/proc/poll_for_gorilla() - AddComponent(\ - /datum/component/ghost_direct_control,\ - poll_candidates = TRUE,\ - poll_length = 30 SECONDS,\ - role_name = "Cargorilla",\ - assumed_control_message = "You are Cargorilla, a pacifist friend of the station and carrier of freight.",\ - poll_ignore_key = POLL_IGNORE_CARGORILLA,\ - after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\ - ) - -/// Called once a ghost assumes control -/mob/living/basic/gorilla/cargorilla/proc/became_player_controlled() - mind.set_assigned_role(SSjob.GetJobType(/datum/job/cargo_technician)) - mind.special_role = "Cargorilla" - to_chat(src, span_notice("You can pick up crates by clicking on them, and drop them by clicking on the ground.")) - #undef GORILLA_HANDS_LAYER diff --git a/code/modules/mob/living/basic/pets/sloth.dm b/code/modules/mob/living/basic/pets/sloth.dm index 0b1546ccf93..125a3a7b97d 100644 --- a/code/modules/mob/living/basic/pets/sloth.dm +++ b/code/modules/mob/living/basic/pets/sloth.dm @@ -44,11 +44,12 @@ GLOBAL_DATUM(cargo_sloth, /mob/living/basic/sloth) AddElement(/datum/element/ai_retaliate) AddComponent(/datum/component/tree_climber) - if(!mapload || isnull(GLOB.cargo_sloth) || !is_station_level(z)) + if(!mapload || !isnull(GLOB.cargo_sloth) || !is_station_level(z)) return // If someone adds non-cargo sloths to maps we'll have a problem but we're fine for now GLOB.cargo_sloth = src + GLOB.gorilla_start += get_turf(src) /mob/living/basic/sloth/Destroy() if(GLOB.cargo_sloth == src) diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm index 49539920920..6d6505d1dc5 100644 --- a/code/modules/tooltip/tooltip.dm +++ b/code/modules/tooltip/tooltip.dm @@ -107,22 +107,22 @@ Notes: //Open a tooltip for user, at a location based on params //Theme is a CSS class in tooltip.html, by default this wrapper chooses a CSS class based on the user's UI_style (Midnight, Plasmafire, Retro, etc) //Includes sanity.checks -/proc/openToolTip(mob/user = null, atom/movable/tip_src = null, params = null,title = "",content = "",theme = "") - if(istype(user)) - if(user.client && user.client.tooltips) - var/ui_style = user.client?.prefs?.read_preference(/datum/preference/choiced/ui_style) - if(!theme && ui_style) - theme = lowertext(ui_style) - if(!theme) - theme = "default" - user.client.tooltips.show(tip_src, params,title,content,theme) +/proc/openToolTip(mob/user = null, atom/movable/tip_src = null, params = null, title = "", content = "", theme = "") + if(!istype(user) || !user.client?.tooltips) + return + var/ui_style = user.client?.prefs?.read_preference(/datum/preference/choiced/ui_style) + if(!theme && ui_style) + theme = lowertext(ui_style) + if(!theme) + theme = "default" + user.client.tooltips.show(tip_src, params, title, content, theme) //Arbitrarily close a user's tooltip //Includes sanity checks. /proc/closeToolTip(mob/user) - if(istype(user)) - if(user.client && user.client.tooltips) - user.client.tooltips.hide() + if(!istype(user) || !user.client?.tooltips) + return + user.client.tooltips.hide() diff --git a/icons/hud/lobby/signup_button.dmi b/icons/hud/lobby/signup_button.dmi new file mode 100644 index 00000000000..4ff31a316c2 Binary files /dev/null and b/icons/hud/lobby/signup_button.dmi differ diff --git a/strings/names/cargorilla.txt b/strings/names/cargorilla.txt new file mode 100644 index 00000000000..ec135f5ca4d --- /dev/null +++ b/strings/names/cargorilla.txt @@ -0,0 +1,7 @@ +Cala +Cerchak +Citrus +Coco +Grodd +Paperwork +Winston diff --git a/tgstation.dme b/tgstation.dme index 2145498002e..43f1d3bd343 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1729,6 +1729,7 @@ #include "code\datums\skills\mining.dm" #include "code\datums\station_traits\_station_trait.dm" #include "code\datums\station_traits\admin_panel.dm" +#include "code\datums\station_traits\job_traits.dm" #include "code\datums\station_traits\negative_traits.dm" #include "code\datums\station_traits\neutral_traits.dm" #include "code\datums\station_traits\positive_traits.dm" @@ -4116,6 +4117,7 @@ #include "code\modules\jobs\job_types\bartender.dm" #include "code\modules\jobs\job_types\botanist.dm" #include "code\modules\jobs\job_types\captain.dm" +#include "code\modules\jobs\job_types\cargo_gorilla.dm" #include "code\modules\jobs\job_types\cargo_technician.dm" #include "code\modules\jobs\job_types\chemist.dm" #include "code\modules\jobs\job_types\chief_engineer.dm" diff --git a/tgui/packages/tgui/interfaces/common/JobToIcon.ts b/tgui/packages/tgui/interfaces/common/JobToIcon.ts index 1d3d8f7b344..402ca05a848 100644 --- a/tgui/packages/tgui/interfaces/common/JobToIcon.ts +++ b/tgui/packages/tgui/interfaces/common/JobToIcon.ts @@ -10,6 +10,7 @@ export const JOB2ICON = { Bitrunner: 'gamepad', Botanist: 'seedling', Captain: 'crown', + 'Cargo Gorilla': 'paw', 'Cargo Technician': 'box', 'CentCom Commander': 'star', 'CentCom Head Intern': 'pen-fancy',