mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-21 07:26:31 +01:00
<!-- 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. -->
156 lines
6.7 KiB
Plaintext
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."))
|