Files
Bubberstation/code/controllers/subsystem/processing/station.dm
Ghom a4e9a56b4c Small refactor on station trait lobby buttons. (#86624)
## About The Pull Request
So, I've been looking into manually loading job traits today, and it
seems the buttons don't appear until you reconnect. Upon further
investigations, it turns out that the code doesn't support showing lobby
buttons outside of SSstation init. To add injury only up to three
buttons can be displayed for some stupid reason (the lack of code for x
offsets), plus the buttons aren't relocated when one is removed, thus
possibly leaving behind an empty gap.

This PR fixes all of that, while removing some crumbs of shitcode from
new players' HUDs and making sure to remove datum traits and references
are removed when the trait is deleted (usually never the case outside
VV).


![immagine](https://github.com/user-attachments/assets/c7e0c481-df7c-41fc-a98c-bee15f0d9ce7)


## Why It's Good For The Game
Lobby buttons should ALWAYS be shown to the player if the relative trait
is loaded, the only exception being the conditions set by the trait
itself (for job traits is the job age and whether the game has started
or not), while the offsets of the lobby buttons should stay synced with
how many are being displayed to the new player at any given time, so if
a button is deleted, the others are relocated to avoid having leaving an
empty gap behind.

Beside, this is necessary for the lobby button for the playable pun pun
to show up during Monkey Day.


## Changelog
N/A, all backend.
2024-09-18 16:16:36 -04:00

185 lines
7.8 KiB
Plaintext

PROCESSING_SUBSYSTEM_DEF(station)
name = "Station"
init_order = INIT_ORDER_STATION
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME
wait = 5 SECONDS
///A list of currently active station traits
var/list/station_traits = list()
///Assoc list of trait type || assoc list of traits with weighted value. Used for picking traits from a specific category.
var/list/selectable_traits_by_types = list(STATION_TRAIT_POSITIVE = list(), STATION_TRAIT_NEUTRAL = list(), STATION_TRAIT_NEGATIVE = list())
///Currently active announcer. Starts as a type but gets initialized after traits are selected
var/datum/centcom_announcer/announcer = /datum/centcom_announcer/default
///A list of trait roles that should be protected from antag
var/list/antag_protected_roles = list()
///A list of trait roles that should never be able to roll antag
var/list/antag_restricted_roles = list()
/// Assosciative list of station goal type -> goal instance
var/list/datum/station_goal/goals_by_type = list()
/datum/controller/subsystem/processing/station/Initialize()
//If doing unit tests we don't do none of that trait shit ya know?
// 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
SSparallax.post_station_setup() //Apply station effects that parallax might have
return SS_INIT_SUCCESS
/datum/controller/subsystem/processing/station/Recover()
station_traits = SSstation.station_traits
selectable_traits_by_types = SSstation.selectable_traits_by_types
announcer = SSstation.announcer
antag_protected_roles = SSstation.antag_protected_roles
antag_restricted_roles = SSstation.antag_restricted_roles
goals_by_type = SSstation.goals_by_type
..()
/// This gets called by SSdynamic during initial gamemode setup.
/// This is done because for a greenshift we want all goals to be generated
/datum/controller/subsystem/processing/station/proc/generate_station_goals(goal_budget)
var/list/possible = subtypesof(/datum/station_goal)
var/goal_weights = 0
var/chosen_goals = list()
var/is_planetary = SSmapping.is_planetary()
while(possible.len && goal_weights < goal_budget)
var/datum/station_goal/picked = pick_n_take(possible)
if(picked::requires_space && is_planetary)
continue
goal_weights += initial(picked.weight)
chosen_goals += picked
for(var/chosen in chosen_goals)
new chosen()
/// Returns all station goals that are currently active
/datum/controller/subsystem/processing/station/proc/get_station_goals()
var/list/goals = list()
for(var/goal_type in goals_by_type)
goals += goals_by_type[goal_type]
return goals
/// Returns a specific station goal by type
/datum/controller/subsystem/processing/station/proc/get_station_goal(goal_type)
return goals_by_type[goal_type]
///Rolls for the amount of traits and adds them to the traits list
/datum/controller/subsystem/processing/station/proc/SetupTraits()
if (CONFIG_GET(flag/forbid_station_traits))
return
if (fexists(FUTURE_STATION_TRAITS_FILE))
var/forced_traits_contents = file2text(FUTURE_STATION_TRAITS_FILE)
fdel(FUTURE_STATION_TRAITS_FILE)
var/list/forced_traits_text_paths = json_decode(forced_traits_contents)
forced_traits_text_paths = SANITIZE_LIST(forced_traits_text_paths)
for (var/trait_text_path in forced_traits_text_paths)
var/station_trait_path = text2path(trait_text_path)
if (!ispath(station_trait_path, /datum/station_trait) || station_trait_path == /datum/station_trait)
var/message = "Invalid station trait path [station_trait_path] was requested in the future station traits!"
log_game(message)
message_admins(message)
continue
setup_trait(station_trait_path)
return
for(var/datum/station_trait/trait_typepath as anything in subtypesof(/datum/station_trait))
// If forced, (probably debugging), just set it up now, keep it out of the pool.
if(initial(trait_typepath.force))
setup_trait(trait_typepath)
continue
if(initial(trait_typepath.abstract_type) == trait_typepath)
continue //Dont add abstract ones to it
if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_PLANETARY) && SSmapping.is_planetary()) // we're on a planet but we can't do planet ;_;
continue
if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_SPACE_BOUND) && !SSmapping.is_planetary()) //we're in space but we can't do space ;_;
continue
if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_REQUIRES_AI) && !CONFIG_GET(flag/allow_ai)) //can't have AI traits without AI
continue
if(ispath(trait_typepath, /datum/station_trait/random_event_weight_modifier)) //Don't add event modifiers for events that can't occur on our map.
var/datum/station_trait/random_event_weight_modifier/random_trait_typepath = trait_typepath
var/datum/round_event_control/event_to_check = initial(random_trait_typepath.event_control_path)
if(event_to_check)
event_to_check = new event_to_check()
if(!event_to_check.valid_for_map())
continue
selectable_traits_by_types[initial(trait_typepath.trait_type)][trait_typepath] = initial(trait_typepath.weight)
var/positive_trait_budget = text2num(pick_weight(CONFIG_GET(keyed_list/positive_station_traits)))
var/neutral_trait_budget = text2num(pick_weight(CONFIG_GET(keyed_list/neutral_station_traits)))
var/negative_trait_budget = text2num(pick_weight(CONFIG_GET(keyed_list/negative_station_traits)))
#ifdef MAP_TEST
positive_trait_budget = 0
neutral_trait_budget = 0
negative_trait_budget = 0
#endif
pick_traits(STATION_TRAIT_POSITIVE, positive_trait_budget)
pick_traits(STATION_TRAIT_NEUTRAL, neutral_trait_budget)
pick_traits(STATION_TRAIT_NEGATIVE, negative_trait_budget)
/**
* Picks traits of a specific category (e.g. bad or good), initializes them, adds them to the list of traits,
* then removes them from possible traits as to not roll twice and subtracts their cost from the budget.
* All until the whole budget is spent or no more traits can be picked with it.
*/
/datum/controller/subsystem/processing/station/proc/pick_traits(trait_sign, budget)
if(!budget)
return
///A list of traits of the same trait sign
var/list/selectable_traits = selectable_traits_by_types[trait_sign]
while(budget)
///Remove any station trait with a cost bigger than the budget
for(var/datum/station_trait/proto_trait as anything in selectable_traits)
if(initial(proto_trait.cost) > budget)
selectable_traits -= proto_trait
///We have spare budget but no trait that can be bought with what's left of it
if(!length(selectable_traits))
return
//Rolls from the table for the specific trait type
var/datum/station_trait/trait_type = pick_weight(selectable_traits)
selectable_traits -= trait_type
budget -= initial(trait_type.cost)
setup_trait(trait_type)
///Creates a given trait of a specific type, while also removing any blacklisted ones from the future pool.
/datum/controller/subsystem/processing/station/proc/setup_trait(datum/station_trait/trait_type)
if(locate(trait_type) in station_traits)
return
var/datum/station_trait/trait_instance = new trait_type()
station_traits += trait_instance
log_game("Station Trait: [trait_instance.name] chosen for this round.")
if(!trait_instance.blacklist)
return
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.show_station_trait_buttons()