Files
Bubberstation/code/controllers/subsystem/processing/station.dm
Ghom 06ba9d93eb Pun Pun Station Trait, But it's only active on Monkey Day (14 December, every round) (#86091)
## About The Pull Request
This is a remake of https://github.com/tgstation/tgstation/pull/84501
with a few small changes to avoid mapping issues. This time it's
restricted to a once-in-a-year event, mainly because neither Jacq and
Lemon were ok with the possibility of Pun Pun being posibly playable all
year, however Jacq said he's fine with it being available on the
holiday, while Lemon isn't around on Discord atm so I don't know what
he's to say.

The trait makes pun pun playable and gives them the job of being a
busser (waiter) in the cafeteria. They're clever and can use tools most
other monkeys can't, but cannot be humanized, while also being unable to
speak Galactic Common. Should they roll traitor, they'll be able to buy
syndicate monkey reinforcements and equipment, which it can also use,
being a monkey itself.

## Why It's Good For The Game
This is a fairly unique job, like the Cargo Gorilla, due to the fact
it's not a conventional humanoid crew member nor the AI or a cyborg. I
thought this was a fun idea, though I met some obstacles and the
original PR was DNM'ed and then closed. However, the trait perfectly
fits the theme of the holiday, making a good compromise since Pun Pun
will stay AI-controlled the rest of the year, as Jacquarel and Lemon
want it to be. It can also be added by an admin through VV, whether
they're planning some shenanigean or just want to add something more to
the round.

## Changelog

🆑
add: Pun Pun is a playable crewmember during Monkey Day (14 December).
/🆑
2024-09-12 09:35:37 -07:00

186 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.add_station_trait_buttons()
observer_hud.show_hud(observer_hud.hud_version)