Files
Letter NandGitHub 37c6a7d1fc time, is it really that time (#7482)
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request
update time related procs & glob too (i was originally targeting that
lmao).
also **stop** using byondtime for sql stuff, we have `NOW()`!!
<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

## Why It's Good For The Game

<!-- Argue for the merits of your changes and how they benefit the game,
especially if they are controversial and/or far reaching. If you can't
actually explain WHY what you are doing will improve the game, then it
probably isn't good for the game in the first place. -->

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Please note that
maintainers freely reserve the right to remove and add tags should they
deem it appropriate. You can attempt to finagle the system all you want,
but it's best to shoot for clear communication right off the bat. -->

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->
2026-01-13 22:06:30 -05:00

156 lines
6.7 KiB
Plaintext

// This is a sort of successor to the various event systems created over the years. It is designed to be just a tad smarter than the
// previous ones, checking various things like player count, department size and composition, individual player activity,
// individual player (IC) skill, and such, in order to try to choose the best actions to take in order to add spice or variety to
// the round.
SUBSYSTEM_DEF(gamemaster)
name = "Game Master"
wait = 600
var/suspended = TRUE // If true, it will not do anything.
var/ignore_time_restrictions = FALSE// Useful for debugging without needing to wait 20 minutes each time.
var/list/available_actions = list() // A list of 'actions' that the GM has access to, to spice up a round, such as events.
var/danger = 0 // The GM's best guess at how chaotic the round is. High danger makes it hold back.
var/staleness = -20 // Determines liklihood of the GM doing something, increases over time.
var/danger_modifier = 1 // Multiplier for how much 'danger' is accumulated.
var/staleness_modifier = 1 // Ditto. Higher numbers generally result in more events occuring in a round.
var/ticks_completed = 0 // Counts amount of ticks completed. Note that this ticks once a minute.
var/next_action = 0 // Minimum amount of time of nothingness until the GM can pick something again.
var/last_department_used = null // If an event was done for a specific department, it is written here, so it doesn't do it again.
/datum/controller/subsystem/gamemaster/Initialize()
available_actions = init_subtypes(/datum/gm_action)
for(var/datum/gm_action/action in available_actions)
action.gm = src
var/config_setup_delay = TRUE
spawn(0)
while(config_setup_delay)
if(config)
config_setup_delay = FALSE
if(config_legacy.enable_game_master)
suspended = FALSE
else
sleep(30 SECONDS)
return SS_INIT_SUCCESS
/datum/controller/subsystem/gamemaster/fire(resumed)
if(SSticker && SSticker.current_state == GAME_STATE_PLAYING && !suspended)
adjust_staleness(1)
adjust_danger(-1)
ticks_completed++
var/global_afk = metric.assess_all_living_mobs()
global_afk -= 100
global_afk = abs(global_afk)
global_afk = round(global_afk / 100, 0.1)
adjust_staleness(global_afk) // Staleness increases faster if more people are less active.
if(world.time < next_action && prob(staleness * 2) )
log_debug(SPAN_DEBUG("Game Master going to start something."))
start_action()
// This is run before committing to an action/event.
/datum/controller/subsystem/gamemaster/proc/pre_action_checks()
if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING)
log_debug(SPAN_DEBUG("Game Master unable to start event: SSticker is nonexistant, or the game is not ongoing."))
return FALSE
if(suspended)
return FALSE
if(ignore_time_restrictions)
return TRUE
// Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round.
var/mills = STATION_TIME_PASSED()
var/mins = round((mills % 36000) / 600)
var/hours = round(mills / 36000)
if(hours < 1 && mins <= 20) // Don't do anything for the first twenty minutes of the round.
log_debug(SPAN_DEBUG("Game Master unable to start event: It is too early."))
return FALSE
if(hours >= 2 && mins >= 40) // Don't do anything in the last twenty minutes of the round, as well.
log_debug(SPAN_DEBUG("Game Master unable to start event: It is too late."))
return FALSE
return TRUE
/datum/controller/subsystem/gamemaster/proc/start_action()
if(!pre_action_checks()) // Make sure we're not doing last minute events, or early events.
return
log_debug(SPAN_DEBUG("Game Master now starting action decision."))
var/list/most_active_departments = metric.assess_all_departments(3, list(last_department_used))
var/list/best_actions = decide_best_action(most_active_departments)
if(best_actions && best_actions.len)
var/list/weighted_actions = list()
for(var/datum/gm_action/action in best_actions)
if(action.chaotic > danger)
continue // We skip dangerous events when bad stuff is already occuring.
weighted_actions[action] = action.get_weight()
var/datum/gm_action/choice = pickweight(weighted_actions)
if(choice)
log_debug(SPAN_DEBUG("[choice.name] was chosen by the Game Master, and is now being ran."))
INVOKE_ASYNC(src, PROC_REF(run_action), choice)
/datum/controller/subsystem/gamemaster/proc/run_action(var/datum/gm_action/action)
action.set_up()
action.start()
action.announce()
if(action.chaotic)
danger += action.chaotic
if(action.length)
spawn(action.length)
action.end()
next_action = world.time + rand(15 MINUTES, 30 MINUTES)
last_department_used = action.departments[1]
/datum/controller/subsystem/gamemaster/proc/decide_best_action(var/list/most_active_departments)
if(!most_active_departments.len) // Server's empty?
log_debug(SPAN_DEBUG("Game Master failed to find any active departments."))
return list()
var/list/best_actions = list() // List of actions which involve the most active departments.
if(most_active_departments.len >= 2)
for(var/datum/gm_action/action in available_actions)
if(!action.enabled)
continue
// Try to incorporate an action with the top two departments first.
if((most_active_departments[1] in action.departments) && (most_active_departments[2] in action.departments))
best_actions.Add(action)
log_debug(SPAN_DEBUG("[action.name] is being considered because both most active departments are involved."))
if(best_actions.len) // We found something for those two, let's do it.
return best_actions
// Otherwise we probably couldn't find something for the second highest group, so let's ignore them.
for(var/datum/gm_action/action in available_actions)
if(!action.enabled)
continue
if(most_active_departments[1] in action.departments)
best_actions.Add(action)
log_debug(SPAN_DEBUG("[action.name] is being considered because the most active department is involved."))
if(best_actions.len) // Found something for the one guy.
return best_actions
// At this point we should expand our horizons.
for(var/datum/gm_action/action in available_actions)
if(!action.enabled)
continue
if(DEPARTMENT_EVERYONE in action.departments)
best_actions.Add(action)
log_debug(SPAN_DEBUG("[action.name] is being considered because it involves everyone."))
if(best_actions.len) // Finally, perhaps?
return best_actions
// Just give a random event if for some reason it still can't make up its mind.
for(var/datum/gm_action/action in available_actions)
if(!action.enabled)
continue
best_actions.Add(action)
log_debug(SPAN_DEBUG("[action.name] is being considered because everything else failed."))
if(best_actions.len) // Finally, perhaps?
return best_actions
else
log_debug(SPAN_DEBUG("Game Master failed to find a suitable event, something very wrong is going on."))