mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2025-12-16 21:22:40 +00:00
Event system rewrite started.
This commit is contained in:
@@ -70,6 +70,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
|
|||||||
#define INIT_ORDER_CIRCUIT -21
|
#define INIT_ORDER_CIRCUIT -21
|
||||||
#define INIT_ORDER_AI -22
|
#define INIT_ORDER_AI -22
|
||||||
#define INIT_ORDER_JOB -23
|
#define INIT_ORDER_JOB -23
|
||||||
|
#define INIT_ORDER_GAME_MASTER -24
|
||||||
|
|
||||||
|
|
||||||
// Subsystem fire priority, from lowest to highest priority
|
// Subsystem fire priority, from lowest to highest priority
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
SUBSYSTEM_DEF(events)
|
SUBSYSTEM_DEF(events)
|
||||||
name = "Events"
|
name = "Events (Legacy)"
|
||||||
wait = 20
|
wait = 20
|
||||||
|
|
||||||
var/list/datum/event/active_events = list()
|
var/list/datum/event/active_events = list()
|
||||||
|
|||||||
35
code/controllers/subsystems/events2.dm
Normal file
35
code/controllers/subsystems/events2.dm
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// This is a simple ticker for the new event system.
|
||||||
|
// The logic that determines what events get chosen is held inside a seperate subsystem.
|
||||||
|
|
||||||
|
SUBSYSTEM_DEF(event_ticker)
|
||||||
|
name = "Events (Ticker)"
|
||||||
|
wait = 2 SECONDS
|
||||||
|
runlevels = RUNLEVEL_GAME
|
||||||
|
|
||||||
|
// List of `/datum/event2/event`s that are currently active, and receiving process() ticks.
|
||||||
|
var/list/active_events = list()
|
||||||
|
|
||||||
|
// List of `/datum/event2/event`s that finished, and are here for showing at roundend, if that's desired.
|
||||||
|
var/list/finished_events = list()
|
||||||
|
|
||||||
|
// Process active events.
|
||||||
|
/datum/controller/subsystem/event_ticker/fire(resumed)
|
||||||
|
for(var/E in active_events)
|
||||||
|
var/datum/event2/event/event = E
|
||||||
|
event.process()
|
||||||
|
if(event.finished)
|
||||||
|
event_finished(event)
|
||||||
|
|
||||||
|
// Starts an event, independent of the GM system.
|
||||||
|
// This means it will always run, and won't affect the GM system in any way, e.g. not putting the event off limits after one use.
|
||||||
|
/datum/controller/subsystem/event_ticker/proc/start_event(event_type)
|
||||||
|
var/datum/event2/event/E = new event_type(src)
|
||||||
|
E.execute()
|
||||||
|
event_started(E)
|
||||||
|
|
||||||
|
/datum/controller/subsystem/event_ticker/proc/event_started(datum/event2/event/E)
|
||||||
|
active_events += E
|
||||||
|
|
||||||
|
/datum/controller/subsystem/event_ticker/proc/event_finished(datum/event2/event/E)
|
||||||
|
active_events -= E
|
||||||
|
finished_events += E
|
||||||
494
code/controllers/subsystems/game_master.dm
Normal file
494
code/controllers/subsystems/game_master.dm
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
// 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 events to take in order to add spice or variety to
|
||||||
|
// the round.
|
||||||
|
|
||||||
|
// This subsystem holds the logic that chooses events. Actual event processing is handled in a seperate subsystem.
|
||||||
|
SUBSYSTEM_DEF(game_master)
|
||||||
|
name = "Events (Game Master)"
|
||||||
|
wait = 1 MINUTE
|
||||||
|
runlevels = RUNLEVEL_GAME
|
||||||
|
|
||||||
|
// The GM object is what actually chooses events.
|
||||||
|
// It's held in a seperate object for better encapsulation, and allows for different 'flavors' of GMs to be made, that choose events differently.
|
||||||
|
var/datum/game_master/GM = null
|
||||||
|
var/game_master_type = /datum/game_master/default
|
||||||
|
|
||||||
|
var/list/available_events = list() // A list of meta event objects.
|
||||||
|
|
||||||
|
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/next_event = 0 // Minimum amount of time of nothingness until the GM can pick something again.
|
||||||
|
|
||||||
|
var/debug_messages = FALSE // If true, debug information is written to `log_debug()`.
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/Initialize()
|
||||||
|
var/list/subtypes = subtypesof(/datum/event2/meta)
|
||||||
|
for(var/T in subtypes)
|
||||||
|
var/datum/event2/meta/M = new T(src)
|
||||||
|
if(!M.name)
|
||||||
|
continue
|
||||||
|
available_events += M
|
||||||
|
|
||||||
|
GM = new game_master_type(src)
|
||||||
|
|
||||||
|
if(config && !config.enable_game_master)
|
||||||
|
can_fire = FALSE
|
||||||
|
|
||||||
|
// Remove when finished.
|
||||||
|
debug_gm()
|
||||||
|
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/fire(resumed)
|
||||||
|
adjust_staleness(1)
|
||||||
|
adjust_danger(-1)
|
||||||
|
|
||||||
|
var/global_afk = metric.assess_all_living_mobs()
|
||||||
|
global_afk = abs(global_afk - 100)
|
||||||
|
global_afk = round(global_afk / 100, 0.1)
|
||||||
|
adjust_staleness(global_afk) // Staleness increases faster if more people are less active.
|
||||||
|
|
||||||
|
if(GM.ignore_time_restrictions || next_event < world.time)
|
||||||
|
if(prob(staleness) && pre_event_checks())
|
||||||
|
do_event_decision()
|
||||||
|
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/do_event_decision()
|
||||||
|
log_game_master("Going to choose an event.")
|
||||||
|
var/datum/event2/meta/event_picked = GM.choose_event()
|
||||||
|
if(event_picked)
|
||||||
|
run_event(event_picked)
|
||||||
|
next_event = world.time + rand(GM.decision_cooldown_lower_bound, GM.decision_cooldown_upper_bound)
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/debug_gm()
|
||||||
|
can_fire = TRUE
|
||||||
|
staleness = 100
|
||||||
|
debug_messages = TRUE
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/run_event(datum/event2/meta/chosen_event)
|
||||||
|
var/datum/event2/event/E = chosen_event.make_event()
|
||||||
|
SSevent_ticker.event_started(E)
|
||||||
|
adjust_danger(chosen_event.chaos)
|
||||||
|
adjust_staleness(-(10 + chosen_event.chaos)) // More chaotic events reduce staleness more, e.g. a 25 chaos event will reduce it by 35.
|
||||||
|
|
||||||
|
|
||||||
|
// Tell the game master that something dangerous happened, e.g. someone dying, station explosions.
|
||||||
|
/datum/controller/subsystem/game_master/proc/adjust_danger(amount)
|
||||||
|
amount *= GM.danger_modifier
|
||||||
|
danger = round(between(0, danger + amount, 1000), 0.1)
|
||||||
|
|
||||||
|
// Tell the game master that things are getting boring if positive, or something interesting if negative..
|
||||||
|
/datum/controller/subsystem/game_master/proc/adjust_staleness(amount)
|
||||||
|
amount *= GM.staleness_modifier
|
||||||
|
staleness = round( between(-20, staleness + amount, 100), 0.1)
|
||||||
|
|
||||||
|
// These are ran before committing to an event.
|
||||||
|
// Returns TRUE if the system is allowed to procede, otherwise returns FALSE.
|
||||||
|
/datum/controller/subsystem/game_master/proc/pre_event_checks(quiet = FALSE)
|
||||||
|
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
|
||||||
|
if(!quiet)
|
||||||
|
log_game_master("Unable to start event: Ticker is nonexistant, or the game is not ongoing.")
|
||||||
|
return FALSE
|
||||||
|
if(GM.ignore_time_restrictions)
|
||||||
|
return TRUE
|
||||||
|
if(next_event > world.time) // Sanity.
|
||||||
|
if(!quiet)
|
||||||
|
log_game_master("Unable to start event: Time until next event is approximately [round((next_event - world.time) / (1 MINUTE))] minute(s)")
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
// Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round.
|
||||||
|
var/mills = round_duration_in_ticks
|
||||||
|
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.
|
||||||
|
// if(!quiet)
|
||||||
|
// log_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.
|
||||||
|
if(!quiet)
|
||||||
|
log_game_master("Unable to start event: It is too late.")
|
||||||
|
return FALSE
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/choose_game_master(mob/user)
|
||||||
|
var/list/subtypes = subtypesof(/datum/game_master)
|
||||||
|
var/new_gm_path = input(user, "What kind of Game Master do you want?", "New Game Master", /datum/game_master/default) as null|anything in subtypes
|
||||||
|
if(new_gm_path)
|
||||||
|
log_and_message_admins("has swapped the current GM ([GM.type]) for a new GM ([new_gm_path]).")
|
||||||
|
GM = new new_gm_path(src)
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/log_game_master(message)
|
||||||
|
if(debug_messages)
|
||||||
|
log_debug("GAME MASTER: [message]")
|
||||||
|
|
||||||
|
|
||||||
|
// This object makes the actual decisions.
|
||||||
|
/datum/game_master
|
||||||
|
var/datum/controller/subsystem/game_master/ticker = null
|
||||||
|
// Multiplier for how much 'danger' is accumulated. Higer generally makes it possible for more dangerous events to be picked.
|
||||||
|
var/danger_modifier = 1.0
|
||||||
|
|
||||||
|
// Ditto. Higher numbers generally result in more events occuring in a round.
|
||||||
|
var/staleness_modifier = 1.0
|
||||||
|
|
||||||
|
var/decision_cooldown_lower_bound = 5 MINUTES // Lower bound for how long to wait until -the potential- for another event being decided.
|
||||||
|
var/decision_cooldown_upper_bound = 20 MINUTES // Same, but upper bound.
|
||||||
|
|
||||||
|
var/ignore_time_restrictions = FALSE // Useful for debugging without needing to wait 20 minutes each time.
|
||||||
|
var/ignore_round_chaos = FALSE // If true, the system will happily choose back to back intense events like meteors and blobs, Dwarf Fortress style.
|
||||||
|
|
||||||
|
/datum/game_master/New(datum/controller/subsystem/game_master/new_ticker)
|
||||||
|
ticker = new_ticker
|
||||||
|
|
||||||
|
// Push button, receive event.
|
||||||
|
// Returns a selected event datum.
|
||||||
|
/datum/game_master/proc/choose_event()
|
||||||
|
|
||||||
|
/datum/game_master/proc/log_game_master(message)
|
||||||
|
SSgame_master.log_game_master(message)
|
||||||
|
|
||||||
|
// The default game master tries to choose events with these goals in mind.
|
||||||
|
// * Don't choose an event if the crew can't take it. E.g. no meteors after half of the crew died.
|
||||||
|
// * Try to involve lots of people, particuarly in active departments.
|
||||||
|
// * Avoid giving events to the same department multiple times in a row.
|
||||||
|
/datum/game_master/default
|
||||||
|
// If an event was done for a specific department, it is written here, so it doesn't do it again.
|
||||||
|
var/last_department_used = null
|
||||||
|
|
||||||
|
|
||||||
|
/datum/game_master/default/choose_event()
|
||||||
|
log_game_master("Now starting event decision.")
|
||||||
|
|
||||||
|
var/list/most_active_departments = metric.assess_all_departments(3, list(last_department_used))
|
||||||
|
var/list/best_events = decide_best_events(most_active_departments)
|
||||||
|
|
||||||
|
if(LAZYLEN(best_events))
|
||||||
|
log_game_master("Got [best_events.len] choice\s for the next event.")
|
||||||
|
var/list/weighted_events = list()
|
||||||
|
|
||||||
|
for(var/E in best_events)
|
||||||
|
var/datum/event2/meta/event = E
|
||||||
|
weighted_events[event] = event.get_weight()
|
||||||
|
|
||||||
|
var/datum/event2/meta/choice = pickweight(weighted_events)
|
||||||
|
|
||||||
|
if(choice)
|
||||||
|
log_game_master("[choice.name] was chosen, and is now being ran.")
|
||||||
|
last_department_used = LAZYACCESS(choice.departments, 1)
|
||||||
|
return choice
|
||||||
|
|
||||||
|
/datum/game_master/default/proc/decide_best_events(list/most_active_departments)
|
||||||
|
if(!LAZYLEN(most_active_departments)) // Server's empty?
|
||||||
|
log_game_master("Game Master failed to find any active departments.")
|
||||||
|
return list()
|
||||||
|
|
||||||
|
var/list/best_events = list()
|
||||||
|
if(most_active_departments.len >= 2)
|
||||||
|
var/list/top_two = list(most_active_departments[1], most_active_departments[2])
|
||||||
|
best_events = filter_events_by_departments(top_two)
|
||||||
|
|
||||||
|
if(LAZYLEN(best_events)) // We found something for those two, let's do it.
|
||||||
|
return best_events
|
||||||
|
|
||||||
|
// Otherwise we probably couldn't find something for the second highest group, so let's ignore them.
|
||||||
|
best_events = filter_events_by_departments(most_active_departments[1])
|
||||||
|
|
||||||
|
if(LAZYLEN(best_events))
|
||||||
|
return best_events
|
||||||
|
|
||||||
|
// At this point we should expand our horizons.
|
||||||
|
best_events = filter_events_by_departments(list(DEPARTMENT_EVERYONE))
|
||||||
|
|
||||||
|
if(LAZYLEN(best_events))
|
||||||
|
return best_events
|
||||||
|
|
||||||
|
// Just give a random event if for some reason it still can't make up its mind.
|
||||||
|
best_events = filter_events_by_departments()
|
||||||
|
|
||||||
|
if(LAZYLEN(best_events))
|
||||||
|
return best_events
|
||||||
|
|
||||||
|
log_game_master("Game Master failed to find a suitable event, something very wrong is going on.")
|
||||||
|
return list()
|
||||||
|
|
||||||
|
// Filters the available events down to events for specific departments.
|
||||||
|
// Pass DEPARTMENT_EVERYONE if you want events that target the general population, like gravity failure.
|
||||||
|
// If no list is passed, all the events will be returned.
|
||||||
|
/datum/game_master/default/proc/filter_events_by_departments(list/departments)
|
||||||
|
. = list()
|
||||||
|
for(var/E in ticker.available_events)
|
||||||
|
var/datum/event2/meta/event = E
|
||||||
|
if(!event.enabled)
|
||||||
|
continue
|
||||||
|
if(event.chaotic_threshold && !ignore_round_chaos)
|
||||||
|
if(ticker.danger > event.chaotic_threshold)
|
||||||
|
continue
|
||||||
|
// An event has to involve all of these departments to pass.
|
||||||
|
var/viable = TRUE
|
||||||
|
if(LAZYLEN(departments))
|
||||||
|
for(var/department in departments)
|
||||||
|
if(!LAZYFIND(departments, department))
|
||||||
|
viable = FALSE
|
||||||
|
break
|
||||||
|
if(viable)
|
||||||
|
. += event
|
||||||
|
|
||||||
|
|
||||||
|
// The `old_like` game master tries to act like the old system, choosing events without any specific goals.
|
||||||
|
// * Has no goals, and instead operates purely off of the weights of the events it has.
|
||||||
|
// * Does not react to danger at all.
|
||||||
|
/datum/game_master/old_like/choose_event()
|
||||||
|
var/list/weighted_events = list()
|
||||||
|
for(var/E in ticker.available_events)
|
||||||
|
var/datum/event2/meta/event = E
|
||||||
|
weighted_events[event] = event.get_weight()
|
||||||
|
|
||||||
|
var/datum/event2/meta/choice = pickweight(weighted_events)
|
||||||
|
|
||||||
|
if(choice)
|
||||||
|
log_game_master("[choice.name] was chosen, and is now being ran.")
|
||||||
|
return choice
|
||||||
|
|
||||||
|
// The `super_random` game master chooses events purely at random, ignoring weights entirely.
|
||||||
|
// * Has no goals, and instead chooses randomly, ignoring weights.
|
||||||
|
// * Does not react to danger at all.
|
||||||
|
/datum/game_master/super_random/choose_event()
|
||||||
|
return pick(ticker.available_events)
|
||||||
|
|
||||||
|
// The `brutal` game master tries to run dangerous events frequently.
|
||||||
|
// * Chaotic events have their weights artifically boosted.
|
||||||
|
// * Ignores accumulated danger.
|
||||||
|
/datum/game_master/brutal
|
||||||
|
ignore_round_chaos = TRUE
|
||||||
|
|
||||||
|
/datum/game_master/brutal/choose_event()
|
||||||
|
var/list/weighted_events = list()
|
||||||
|
for(var/E in ticker.available_events)
|
||||||
|
var/datum/event2/meta/event = E
|
||||||
|
weighted_events[event] = event.get_weight() + (event.chaos * 2)
|
||||||
|
|
||||||
|
var/datum/event2/meta/choice = pickweight(weighted_events)
|
||||||
|
|
||||||
|
if(choice)
|
||||||
|
log_game_master("[choice.name] was chosen, and is now being ran.")
|
||||||
|
return choice
|
||||||
|
|
||||||
|
/client/proc/show_gm_status()
|
||||||
|
set category = "Debug"
|
||||||
|
set name = "Show GM Status"
|
||||||
|
set desc = "Shows you what the GM is thinking. If only that existed in real life..."
|
||||||
|
|
||||||
|
if(check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||||
|
SSgame_master.interact(usr)
|
||||||
|
else
|
||||||
|
to_chat(usr, span("warning", "You do not have sufficent rights to view the GM panel, sorry."))
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/interact(var/client/user)
|
||||||
|
if(!user)
|
||||||
|
return
|
||||||
|
|
||||||
|
// Using lists for string tree conservation.
|
||||||
|
var/list/dat = list("<html><head><title>Automated Game Master Event System</title></head><body>")
|
||||||
|
|
||||||
|
// Makes the system turn on or off.
|
||||||
|
dat += href(src, list("toggle" = 1), "\[Toggle GM\]")
|
||||||
|
dat += " | "
|
||||||
|
|
||||||
|
// Makes the system not care about staleness or being near round-end.
|
||||||
|
dat += href(src, list("toggle_time_restrictions" = 1), "\[Toggle Time Restrictions\]")
|
||||||
|
dat += " | "
|
||||||
|
|
||||||
|
// Makes the system not care about how chaotic the round might be.
|
||||||
|
dat += href(src, list("toggle_chaos_throttle" = 1), "\[Toggle Chaos Throttling\]")
|
||||||
|
dat += " | "
|
||||||
|
|
||||||
|
// Makes the system immediately choose an event, while still bound to factors like danger, weights, and department staffing.
|
||||||
|
dat += href(src, list("force_choose_event" = 1), "\[Force Event Decision\]")
|
||||||
|
dat += "<br>"
|
||||||
|
|
||||||
|
// Swaps out the current GM for a new one with different ideas on what a good event might be.
|
||||||
|
dat += href(src, list("change_gm" = 1), "\[Change GM\]")
|
||||||
|
dat += "<br>"
|
||||||
|
|
||||||
|
dat += "Current GM Type: [GM.type]<br>"
|
||||||
|
dat += "State: [can_fire ? "Active": "Inactive"]<br>"
|
||||||
|
dat += "Status: [pre_event_checks(TRUE) ? "Ready" : "Suppressed"]<br><br>"
|
||||||
|
|
||||||
|
dat += "Staleness: [staleness] "
|
||||||
|
dat += href(src, list("set_staleness" = 1), "\[Set\]")
|
||||||
|
dat += "<br>"
|
||||||
|
dat += "<i>Staleness is an estimate of how boring the round might be, and if an event should be done. It is increased passively over time, \
|
||||||
|
and increases faster if people are AFK. It deceases when events and certain 'interesting' things happen in the round.</i><br>"
|
||||||
|
|
||||||
|
dat += "Danger: [danger] "
|
||||||
|
dat += href(src, list("set_danger" = 1), "\[Set\]")
|
||||||
|
dat += "<br>"
|
||||||
|
dat += "<i>Danger is an estimate of how chaotic the round has been so far. It is decreased passively over time, and is increased by having \
|
||||||
|
certain chaotic events be selected, or chaotic things happen in the round. A sufficently high amount of danger will make the system \
|
||||||
|
avoid using destructive events, to avoid pushing the station over the edge.</i><br>"
|
||||||
|
|
||||||
|
dat += "<h2>Player Activity:</h2>"
|
||||||
|
|
||||||
|
dat += "<table border='1' style='width:100%'>"
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th>Category</th>"
|
||||||
|
dat += "<th>Activity Percentage</th>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>All Living Mobs</td>"
|
||||||
|
dat += "<td>[metric.assess_all_living_mobs()]%</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>All Ghosts</td>"
|
||||||
|
dat += "<td>[metric.assess_all_dead_mobs()]%</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th colspan='2'>Departments</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
for(var/D in metric.departments)
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>[D]</td>"
|
||||||
|
dat += "<td>[metric.assess_department(D)]%</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th colspan='2'>Players</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
for(var/P in player_list)
|
||||||
|
var/mob/M = P
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>[M] ([M.ckey])</td>"
|
||||||
|
dat += "<td>[metric.assess_player_activity(M)]%</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
dat += "</table>"
|
||||||
|
|
||||||
|
dat += "<h2>Events available:</h2>"
|
||||||
|
|
||||||
|
dat += "<table border='1' style='width:100%'>"
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th>Event Name</th>"
|
||||||
|
dat += "<th>Involved Departments</th>"
|
||||||
|
dat += "<th>Chaos</th>"
|
||||||
|
dat += "<th>Current Weight</th>"
|
||||||
|
dat += "<th>Buttons</th>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
for(var/E in available_events)
|
||||||
|
var/datum/event2/meta/event = E
|
||||||
|
dat += "<tr>"
|
||||||
|
if(!event.enabled)
|
||||||
|
dat += "<td><strike>[event.name]</strike></td>"
|
||||||
|
else
|
||||||
|
dat += "<td>[event.name]</td>"
|
||||||
|
dat += "<td>[english_list(event.departments)]</td>"
|
||||||
|
dat += "<td>[event.chaos]</td>"
|
||||||
|
dat += "<td>[event.get_weight()]</td>"
|
||||||
|
dat += "<td>[href(event, list("force" = 1), "\[Force\]")] [href(event, list("toggle" = 1), "\[Toggle\]")]</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
dat += "</table>"
|
||||||
|
|
||||||
|
dat += "<h2>Events active:</h2>"
|
||||||
|
|
||||||
|
dat += "Current time: [world.time]"
|
||||||
|
dat += "<table border='1' style='width:100%'>"
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th>Event Type</th>"
|
||||||
|
dat += "<th>Time Started</th>"
|
||||||
|
dat += "<th>Time to Announce</th>"
|
||||||
|
dat += "<th>Time to End</th>"
|
||||||
|
dat += "<th>Announced</th>"
|
||||||
|
dat += "<th>Started</th>"
|
||||||
|
dat += "<th>Ended</th>"
|
||||||
|
dat += "<th>Buttons</th>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
for(var/E in SSevent_ticker.active_events)
|
||||||
|
var/datum/event2/event/event = E
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>[event.type]</td>"
|
||||||
|
dat += "<td>[event.time_started]</td>"
|
||||||
|
dat += "<td>[event.time_to_announce ? event.time_to_announce : "NULL"]</td>"
|
||||||
|
dat += "<td>[event.time_to_end ? event.time_to_end : "NULL"]</td>"
|
||||||
|
dat += "<td>[event.announced ? "Yes" : "No"]</td>"
|
||||||
|
dat += "<td>[event.started ? "Yes" : "No"]</td>"
|
||||||
|
dat += "<td>[event.ended ? "Yes" : "No"]</td>"
|
||||||
|
dat += "<td>[href(event, list("abort" = 1), "\[Abort\]")]</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
dat += "</table>"
|
||||||
|
dat += "</body></html>"
|
||||||
|
|
||||||
|
dat += "<h2>Events completed:</h2>"
|
||||||
|
|
||||||
|
dat += "<table border='1' style='width:100%'>"
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th>Event Type</th>"
|
||||||
|
dat += "<th>Start Time</th>"
|
||||||
|
dat += "<th>Finish Time</th>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
for(var/E in SSevent_ticker.finished_events)
|
||||||
|
var/datum/event2/event/event = E
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>[event.type]</td>"
|
||||||
|
dat += "<td>[event.time_started]</td>"
|
||||||
|
dat += "<td>[event.time_finished]</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "</body></html>"
|
||||||
|
|
||||||
|
var/datum/browser/popup = new(user, "game_master_debug", "Automated Game Master Event System", 800, 500, src)
|
||||||
|
popup.set_content(dat.Join())
|
||||||
|
popup.open()
|
||||||
|
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/Topic(href, href_list)
|
||||||
|
if(..())
|
||||||
|
return
|
||||||
|
|
||||||
|
if(href_list["close"]) // Needed or the window re-opens after closing, making it last forever.
|
||||||
|
return
|
||||||
|
|
||||||
|
if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||||
|
message_admins("[usr] has attempted to modify the Game Master values without sufficent privilages.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if(href_list["toggle"])
|
||||||
|
can_fire = !can_fire
|
||||||
|
message_admins("GM was [!can_fire ? "dis" : "en"]abled by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["toggle_time_restrictions"])
|
||||||
|
GM.ignore_time_restrictions = !GM.ignore_time_restrictions
|
||||||
|
message_admins("GM event time restrictions was [GM.ignore_time_restrictions ? "dis" : "en"]abled by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["toggle_chaos_throttle"])
|
||||||
|
GM.ignore_round_chaos = !GM.ignore_round_chaos
|
||||||
|
message_admins("GM event chaos restrictions was [GM.ignore_round_chaos ? "dis" : "en"]abled by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["force_choose_event"])
|
||||||
|
do_event_decision()
|
||||||
|
message_admins("[usr.key] forced the Game Master to choose an event immediately.")
|
||||||
|
|
||||||
|
if(href_list["change_gm"])
|
||||||
|
choose_game_master(usr)
|
||||||
|
|
||||||
|
if(href_list["set_staleness"])
|
||||||
|
var/amount = input(usr, "How much staleness should there be?", "Game Master") as null|num
|
||||||
|
if(!isnull(amount))
|
||||||
|
staleness = amount
|
||||||
|
message_admins("GM staleness was set to [amount] by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["set_danger"])
|
||||||
|
var/amount = input(usr, "How much danger should there be?", "Game Master") as null|num
|
||||||
|
if(!isnull(amount))
|
||||||
|
danger = amount
|
||||||
|
message_admins("GM danger was set to [amount] by [usr.key].")
|
||||||
|
|
||||||
|
interact(usr) // To refresh the UI.
|
||||||
402
code/controllers/subsystems/game_master_old.dm
Normal file
402
code/controllers/subsystems/game_master_old.dm
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
// 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(game_master_old)
|
||||||
|
name = "Events (Game Master)"
|
||||||
|
wait = 2 SECONDS
|
||||||
|
init_order = INIT_ORDER_GAME_MASTER
|
||||||
|
flags = SS_KEEP_TIMING
|
||||||
|
runlevels = RUNLEVEL_GAME
|
||||||
|
|
||||||
|
// List of `/datum/event2/event`s that are currently active, and receiving process() ticks.
|
||||||
|
var/list/active_events = list()
|
||||||
|
|
||||||
|
// List of `/datum/event2/event`s that finished, and are here for showing at roundend, if that's desired.
|
||||||
|
var/list/finished_events = list()
|
||||||
|
|
||||||
|
// List of `/datum/event2/meta`s that hold meta-information about events.
|
||||||
|
var/list/all_meta_events = list()
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
// Multiplier for how much 'danger' is accumulated. Higer generally makes it possible for more dangerous events to be picked.
|
||||||
|
var/danger_modifier = 1.0
|
||||||
|
|
||||||
|
// Ditto. Higher numbers generally result in more events occuring in a round.
|
||||||
|
var/staleness_modifier = 1.0
|
||||||
|
|
||||||
|
// If an event was done for a specific department, it is written here, so it doesn't do it again.
|
||||||
|
var/last_department_used = null
|
||||||
|
|
||||||
|
var/next_action = 0 // Minimum amount of time of nothingness until the GM can pick something again.
|
||||||
|
var/decision_cooldown_lower_bound = 5 MINUTES // Lower bound for how long to wait until -the potential- for another action being decided.
|
||||||
|
var/decision_cooldown_upper_bound = 20 MINUTES // Same, but upper bound.
|
||||||
|
|
||||||
|
var/ignore_time_restrictions = FALSE// Useful for debugging without needing to wait 20 minutes each time.
|
||||||
|
var/ignore_round_chaos = FALSE // If true, the system will happily choose back to back intense events like meteors and blobs, Dwarf Fortress style.
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/Initialize()
|
||||||
|
// available_actions = init_subtypes(/datum/gm_action)
|
||||||
|
// for(var/A in available_actions)
|
||||||
|
// var/datum/gm_action/action = A
|
||||||
|
// action.gm = src
|
||||||
|
|
||||||
|
var/list/subtypes = subtypesof(/datum/event2/meta)
|
||||||
|
for(var/T in subtypes)
|
||||||
|
var/datum/event2/meta/M = new T(src)
|
||||||
|
all_meta_events += M
|
||||||
|
|
||||||
|
if(config && !config.enable_game_master)
|
||||||
|
can_fire = FALSE
|
||||||
|
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/fire(resumed)
|
||||||
|
// Process active events.
|
||||||
|
for(var/E in active_events)
|
||||||
|
var/datum/event2/event/event = E
|
||||||
|
event.process()
|
||||||
|
if(event.finished)
|
||||||
|
event_finished(event)
|
||||||
|
|
||||||
|
// Decide if a new event is a good idea, and if so, which one.
|
||||||
|
if(times_fired % (1 MINUTE / wait) == 0) // Run once a minute, even if `wait` gets changed in the future or something.
|
||||||
|
adjust_staleness(1)
|
||||||
|
adjust_danger(-1)
|
||||||
|
|
||||||
|
var/global_afk = metric.assess_all_living_mobs()
|
||||||
|
global_afk = abs(global_afk - 100)
|
||||||
|
global_afk = round(global_afk / 100, 0.1)
|
||||||
|
adjust_staleness(global_afk) // Staleness increases faster if more people are less active.
|
||||||
|
|
||||||
|
if(ignore_time_restrictions || next_action < world.time)
|
||||||
|
if(prob(staleness) && pre_action_checks())
|
||||||
|
log_debug("Game Master is going to start something.")
|
||||||
|
start_action()
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/event_started(datum/event2/event/E)
|
||||||
|
active_events += E
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/event_finished(datum/event2/event/E)
|
||||||
|
active_events -= E
|
||||||
|
finished_events += E
|
||||||
|
|
||||||
|
|
||||||
|
// These are ran before committing to an action or event.
|
||||||
|
// Returns TRUE if the system is allowed to procede, otherwise returns FALSE.
|
||||||
|
/datum/controller/subsystem/game_master/proc/pre_action_checks(quiet = FALSE)
|
||||||
|
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
|
||||||
|
if(!quiet)
|
||||||
|
log_debug("Game Master unable to start event: Ticker is nonexistant, or the game is not ongoing.")
|
||||||
|
return FALSE
|
||||||
|
if(ignore_time_restrictions)
|
||||||
|
return TRUE
|
||||||
|
if(next_action > world.time) // Sanity.
|
||||||
|
if(!quiet)
|
||||||
|
log_debug("Game Master unable to start event: Time until next action is approximately [round((next_action - world.time) / (1 MINUTE))] minute(s)")
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
// Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round.
|
||||||
|
var/mills = round_duration_in_ticks
|
||||||
|
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.
|
||||||
|
// if(!quiet)
|
||||||
|
// log_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.
|
||||||
|
if(!quiet)
|
||||||
|
log_debug("Game Master unable to start event: It is too late.")
|
||||||
|
return FALSE
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/start_action()
|
||||||
|
log_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/A in best_actions)
|
||||||
|
var/datum/gm_action/action = A
|
||||||
|
if(danger >= action.chaos_threshold)
|
||||||
|
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("[choice.name] was chosen by the Game Master, and is now being ran.")
|
||||||
|
run_action(choice)
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/run_action(datum/gm_action/action)
|
||||||
|
log_debug("[action.name] is being ran.")
|
||||||
|
action.set_up()
|
||||||
|
action.start()
|
||||||
|
action.announce()
|
||||||
|
if(action.chaotic)
|
||||||
|
danger += action.chaotic
|
||||||
|
|
||||||
|
next_action = world.time + rand(decision_cooldown_lower_bound, decision_cooldown_upper_bound)
|
||||||
|
last_department_used = LAZYACCESS(action.departments, 1)
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/decide_best_action(list/most_active_departments)
|
||||||
|
if(!LAZYLEN(most_active_departments))// Server's empty?
|
||||||
|
log_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/A in available_actions)
|
||||||
|
var/datum/gm_action/action = A
|
||||||
|
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("[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/A in available_actions)
|
||||||
|
var/datum/gm_action/action = A
|
||||||
|
if(!action.enabled)
|
||||||
|
continue
|
||||||
|
if(most_active_departments[1] in action.departments)
|
||||||
|
best_actions.Add(action)
|
||||||
|
log_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/A in available_actions)
|
||||||
|
var/datum/gm_action/action = A
|
||||||
|
if(!action.enabled)
|
||||||
|
continue
|
||||||
|
if(DEPARTMENT_EVERYONE in action.departments)
|
||||||
|
best_actions.Add(action)
|
||||||
|
log_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/A in available_actions)
|
||||||
|
var/datum/gm_action/action = A
|
||||||
|
if(!action.enabled)
|
||||||
|
continue
|
||||||
|
best_actions.Add(action)
|
||||||
|
log_debug("[action.name] is being considered because everything else failed.")
|
||||||
|
|
||||||
|
if(best_actions.len) // Finally, perhaps?
|
||||||
|
return best_actions
|
||||||
|
|
||||||
|
log_debug("Game Master failed to find a suitable event, something very wrong is going on.")
|
||||||
|
return list()
|
||||||
|
|
||||||
|
|
||||||
|
// Tell the game master that something dangerous happened, e.g. someone dying, station explosions.
|
||||||
|
/datum/controller/subsystem/game_master/proc/adjust_danger(amount)
|
||||||
|
amount *= danger_modifier
|
||||||
|
danger = round(between(0, danger + amount, 1000), 0.1)
|
||||||
|
|
||||||
|
// Tell the game master that things are getting boring if positive, or something interesting if negative..
|
||||||
|
/datum/controller/subsystem/game_master/proc/adjust_staleness(amount)
|
||||||
|
amount *= staleness_modifier
|
||||||
|
staleness = round( between(-20, staleness + amount, 100), 0.1)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Admin UI
|
||||||
|
*/
|
||||||
|
|
||||||
|
/client/proc/show_gm_status()
|
||||||
|
set category = "Debug"
|
||||||
|
set name = "Show GM Status"
|
||||||
|
set desc = "Shows you what the GM is thinking. If only that existed in real life..."
|
||||||
|
|
||||||
|
if(check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||||
|
SSgame_master.interact(usr)
|
||||||
|
else
|
||||||
|
to_chat(usr, span("warning", "You do not have sufficent rights to view the GM panel, sorry."))
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/proc/interact(var/client/user)
|
||||||
|
if(!user)
|
||||||
|
return
|
||||||
|
|
||||||
|
// Using lists for string tree conservation.
|
||||||
|
var/list/dat = list("<html><head><title>Automated Game Master Event System</title></head><body>")
|
||||||
|
|
||||||
|
// Makes the system turn on or off.
|
||||||
|
dat += href(src, list("toggle" = 1), "\[Toggle GM\]")
|
||||||
|
dat += " | "
|
||||||
|
|
||||||
|
// Makes the system not care about staleness or being near round-end.
|
||||||
|
dat += href(src, list("toggle_time_restrictions" = 1), "\[Toggle Time Restrictions\]")
|
||||||
|
dat += " | "
|
||||||
|
|
||||||
|
// Makes the system not care about how chaotic the round might be.
|
||||||
|
dat += href(src, list("toggle_chaos_throttle" = 1), "\[Toggle Chaos Throttling\]")
|
||||||
|
dat += " | "
|
||||||
|
|
||||||
|
// Makes the system immediately choose an event, while still bound to factors like danger, weights, and department staffing.
|
||||||
|
dat += href(src, list("force_choose_event" = 1), "\[Force Event Decision\]")
|
||||||
|
dat += "<br>"
|
||||||
|
|
||||||
|
dat += "State: [can_fire ? "Active": "Inactive"]<br>"
|
||||||
|
dat += "Status: [pre_action_checks(TRUE) ? "Ready" : "Suppressed"]<br><br>"
|
||||||
|
|
||||||
|
dat += "Staleness: [staleness] "
|
||||||
|
dat += href(src, list("set_staleness" = 1), "\[Set\]")
|
||||||
|
dat += "<br>"
|
||||||
|
dat += "<i>Staleness is an estimate of how boring the round might be, and if an event should be done. It is increased passively over time, \
|
||||||
|
and increases faster if people are AFK. It deceases when events and certain 'interesting' things happen in the round.</i><br>"
|
||||||
|
|
||||||
|
dat += "Danger: [danger] "
|
||||||
|
dat += href(src, list("set_danger" = 1), "\[Set\]")
|
||||||
|
dat += "<br>"
|
||||||
|
dat += "<i>Danger is an estimate of how chaotic the round has been so far. It is decreased passively over time, and is increased by having \
|
||||||
|
certain chaotic events be selected, or chaotic things happen in the round. A sufficently high amount of danger will make the system \
|
||||||
|
avoid using destructive events, to avoid pushing the station over the edge.</i><br>"
|
||||||
|
|
||||||
|
dat += "<h2>Player Activity:</h2>"
|
||||||
|
|
||||||
|
dat += "<table border='1' style='width:100%'>"
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th>Category</th>"
|
||||||
|
dat += "<th>Activity Percentage</th>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>All Living Mobs</td>"
|
||||||
|
dat += "<td>[metric.assess_all_living_mobs()]%</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>All Ghosts</td>"
|
||||||
|
dat += "<td>[metric.assess_all_dead_mobs()]%</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th colspan='2'>Departments</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
for(var/D in metric.departments)
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>[D]</td>"
|
||||||
|
dat += "<td>[metric.assess_department(D)]%</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th colspan='2'>Players</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
for(var/P in player_list)
|
||||||
|
var/mob/M = P
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>[M] ([M.ckey])</td>"
|
||||||
|
dat += "<td>[metric.assess_player_activity(M)]%</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "</table>"
|
||||||
|
|
||||||
|
dat += "<h2>Actions available:</h2>"
|
||||||
|
dat += "<table border='1' style='width:100%'>"
|
||||||
|
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<th>Action Name</th>"
|
||||||
|
dat += "<th>Involved Departments</th>"
|
||||||
|
dat += "<th>Chaos</th>"
|
||||||
|
dat += "<th>Current Weight</th>"
|
||||||
|
dat += "<th>Buttons</th>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
for(var/A in available_actions)
|
||||||
|
var/datum/gm_action/action = A
|
||||||
|
dat += "<tr>"
|
||||||
|
dat += "<td>[action.name]</td>"
|
||||||
|
dat += "<td>[english_list(action.departments)]</td>"
|
||||||
|
dat += "<td>[action.chaotic]</td>"
|
||||||
|
dat += "<td>[action.get_weight()]</td>"
|
||||||
|
dat += "<td>[href(action, list("force" = 1), "\[Force\]")] [href(action, list("toggle" = 1), "\[Toggle\]")]</td>"
|
||||||
|
dat += "</tr>"
|
||||||
|
|
||||||
|
dat += "</table>"
|
||||||
|
|
||||||
|
dat += "</body></html>"
|
||||||
|
|
||||||
|
var/datum/browser/popup = new(user, "game_master_debug", "Automated Game Master Event System", 800, 500, src)
|
||||||
|
popup.set_content(dat.Join())
|
||||||
|
popup.open()
|
||||||
|
|
||||||
|
// HTML += "Actions available;<br>"
|
||||||
|
// for(var/datum/gm_action/action in available_actions)
|
||||||
|
// if(action.enabled == FALSE)
|
||||||
|
// continue
|
||||||
|
// HTML += "[action.name] ([english_list(action.departments)]) (weight: [action.get_weight()]) <a href='?src=\ref[action];force=1'>\[Force\]</a> <br>"
|
||||||
|
|
||||||
|
// HTML += "<br>"
|
||||||
|
// HTML += "All living mobs activity: [metric.assess_all_living_mobs()]%<br>"
|
||||||
|
// HTML += "All ghost activity: [metric.assess_all_dead_mobs()]%<br>"
|
||||||
|
//
|
||||||
|
// HTML += "<br>"
|
||||||
|
// HTML += "Departmental activity;<br>"
|
||||||
|
// for(var/department in metric.departments)
|
||||||
|
// HTML += " [department] : [metric.assess_department(department)]%<br>"
|
||||||
|
//
|
||||||
|
// HTML += "<br>"
|
||||||
|
// HTML += "Activity of players;<br>"
|
||||||
|
// for(var/mob/player in player_list)
|
||||||
|
// HTML += " [player] ([player.key]) : [metric.assess_player_activity(player)]%<br>"
|
||||||
|
//
|
||||||
|
// HTML +="</body></html>"
|
||||||
|
// user << browse(HTML, "window=log;size=400x450;border=1;can_resize=1;can_close=1;can_minimize=1")
|
||||||
|
|
||||||
|
|
||||||
|
/datum/controller/subsystem/game_master/Topic(href, href_list)
|
||||||
|
if(..())
|
||||||
|
return
|
||||||
|
|
||||||
|
if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||||
|
message_admins("[usr] has attempted to modify the Game Master values without being an admin.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if(href_list["toggle"])
|
||||||
|
can_fire = !can_fire
|
||||||
|
message_admins("GM was [!can_fire ? "dis" : "en"]abled by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["toggle_time_restrictions"])
|
||||||
|
ignore_time_restrictions = !ignore_time_restrictions
|
||||||
|
message_admins("GM event time restrictions was [ignore_time_restrictions ? "dis" : "en"]abled by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["toggle_chaos_throttle"])
|
||||||
|
ignore_round_chaos = !ignore_round_chaos
|
||||||
|
message_admins("GM event chaos restrictions was [ignore_round_chaos ? "dis" : "en"]abled by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["force_choose_event"])
|
||||||
|
start_action()
|
||||||
|
message_admins("[usr.key] forced the Game Master to choose an event immediately.")
|
||||||
|
|
||||||
|
if(href_list["set_staleness"])
|
||||||
|
var/amount = input(usr, "How much staleness should there be?", "Game Master") as null|num
|
||||||
|
if(!isnull(amount))
|
||||||
|
staleness = amount
|
||||||
|
message_admins("GM staleness was set to [amount] by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["set_danger"])
|
||||||
|
var/amount = input(usr, "How much danger should there be?", "Game Master") as null|num
|
||||||
|
if(!isnull(amount))
|
||||||
|
danger = amount
|
||||||
|
message_admins("GM danger was set to [amount] by [usr.key].")
|
||||||
|
|
||||||
|
interact(usr) // To refresh the UI.
|
||||||
@@ -496,6 +496,7 @@
|
|||||||
M.emote("gasp")
|
M.emote("gasp")
|
||||||
M.Weaken(rand(10,25))
|
M.Weaken(rand(10,25))
|
||||||
M.updatehealth()
|
M.updatehealth()
|
||||||
|
SSgame_master.adjust_danger(-20)
|
||||||
apply_brain_damage(M, deadtime)
|
apply_brain_damage(M, deadtime)
|
||||||
|
|
||||||
/obj/item/weapon/shockpaddles/proc/apply_brain_damage(mob/living/carbon/human/H, var/deadtime)
|
/obj/item/weapon/shockpaddles/proc/apply_brain_damage(mob/living/carbon/human/H, var/deadtime)
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
/proc/level_seven_blob_announcement(var/obj/structure/blob/core/B)
|
|
||||||
if(!B || !B.overmind)
|
|
||||||
return
|
|
||||||
var/datum/blob_type/blob = B.overmind.blob_type // Shortcut so we don't need to delve into three variables every time.
|
|
||||||
var/list/lines = list()
|
|
||||||
|
|
||||||
lines += "Confirmed outbreak of level [7 + blob.difficulty] biohazard aboard [station_name()]. All personnel must contain the outbreak."
|
|
||||||
|
|
||||||
if(blob.difficulty >= BLOB_DIFFICULTY_MEDIUM) // Tell them what kind of blob it is if it's tough.
|
|
||||||
lines += "The biohazard has been identified as a '[blob.name]'."
|
|
||||||
|
|
||||||
if(blob.difficulty >= BLOB_DIFFICULTY_HARD) // If it's really hard then tell them where it is so the response occurs faster.
|
|
||||||
lines += "It is suspected to have originated from \the [get_area(B)]."
|
|
||||||
|
|
||||||
if(blob.difficulty >= BLOB_DIFFICULTY_SUPERHARD)
|
|
||||||
lines += "Extreme caution is advised."
|
|
||||||
|
|
||||||
command_announcement.Announce(lines.Join("\n"), "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
|
|
||||||
@@ -40,7 +40,6 @@ var/list/overminds = list()
|
|||||||
color = blob_type.complementary_color
|
color = blob_type.complementary_color
|
||||||
if(blob_core)
|
if(blob_core)
|
||||||
blob_core.update_icon()
|
blob_core.update_icon()
|
||||||
level_seven_blob_announcement(blob_core)
|
|
||||||
|
|
||||||
..(newloc)
|
..(newloc)
|
||||||
|
|
||||||
|
|||||||
@@ -125,18 +125,20 @@
|
|||||||
activeFor++
|
activeFor++
|
||||||
|
|
||||||
//Called when start(), announce() and end() has all been called.
|
//Called when start(), announce() and end() has all been called.
|
||||||
/datum/event/proc/kill()
|
/datum/event/proc/kill(external_use = FALSE)
|
||||||
// If this event was forcefully killed run end() for individual cleanup
|
// If this event was forcefully killed run end() for individual cleanup
|
||||||
if(isRunning)
|
if(isRunning)
|
||||||
isRunning = 0
|
isRunning = 0
|
||||||
end()
|
end()
|
||||||
|
|
||||||
endedAt = world.time
|
endedAt = world.time
|
||||||
|
if(!external_use)
|
||||||
SSevents.active_events -= src
|
SSevents.active_events -= src
|
||||||
SSevents.event_complete(src)
|
SSevents.event_complete(src)
|
||||||
|
|
||||||
/datum/event/New(var/datum/event_meta/EM)
|
/datum/event/New(var/datum/event_meta/EM, external_use = FALSE)
|
||||||
// event needs to be responsible for this, as stuff like APLUs currently make their own events for curious reasons
|
// event needs to be responsible for this, as stuff like APLUs currently make their own events for curious reasons
|
||||||
|
if(!external_use)
|
||||||
SSevents.active_events += src
|
SSevents.active_events += src
|
||||||
|
|
||||||
event_meta = EM
|
event_meta = EM
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ var/list/event_last_fired = list()
|
|||||||
|
|
||||||
possibleEvents[/datum/event/pda_spam] = max(min(25, player_list.len) * 4, 200)
|
possibleEvents[/datum/event/pda_spam] = max(min(25, player_list.len) * 4, 200)
|
||||||
possibleEvents[/datum/event/money_lotto] = max(min(5, player_list.len), 50)
|
possibleEvents[/datum/event/money_lotto] = max(min(5, player_list.len), 50)
|
||||||
if(account_hack_attempted)
|
if(GLOB.account_hack_attempted)
|
||||||
possibleEvents[/datum/event/money_hacker] = max(min(25, player_list.len) * 4, 200)
|
possibleEvents[/datum/event/money_hacker] = max(min(25, player_list.len) * 4, 200)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
//var/global/account_hack_attempted = 0
|
//var/global/account_hack_attempted = 0
|
||||||
|
|
||||||
|
GLOBAL_VAR_INIT(account_hack_attempted, 0)
|
||||||
|
|
||||||
/datum/event/money_hacker
|
/datum/event/money_hacker
|
||||||
var/datum/money_account/affected_account
|
var/datum/money_account/affected_account
|
||||||
endWhen = 100
|
endWhen = 100
|
||||||
@@ -10,7 +12,7 @@
|
|||||||
if(all_money_accounts.len)
|
if(all_money_accounts.len)
|
||||||
affected_account = pick(all_money_accounts)
|
affected_account = pick(all_money_accounts)
|
||||||
|
|
||||||
account_hack_attempted = 1
|
GLOB.account_hack_attempted = 1
|
||||||
else
|
else
|
||||||
kill()
|
kill()
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
var/enabled = TRUE // If not enabled, this action is never taken.
|
var/enabled = TRUE // If not enabled, this action is never taken.
|
||||||
var/departments = list() // What kinds of departments are affected by this action. Multiple departments can be listed.
|
var/departments = list() // What kinds of departments are affected by this action. Multiple departments can be listed.
|
||||||
var/chaotic = 0 // A number showing how chaotic the action may be. If danger is high, the GM will avoid it.
|
var/chaotic = 0 // A number showing how chaotic the action may be. If danger is high, the GM will avoid it.
|
||||||
|
var/chaos_threshold = 0 // If the GM's danger score is at this number or higher, the event won't get picked.
|
||||||
var/reusable = FALSE // If true, the event does not become disabled upon being used. Should be used sparingly.
|
var/reusable = FALSE // If true, the event does not become disabled upon being used. Should be used sparingly.
|
||||||
var/observers_used = FALSE // Determines if the GM should check if ghosts are available before using this.
|
var/observers_used = FALSE // Determines if the GM should check if ghosts are available before using this.
|
||||||
var/length = 0 // Determines how long the event lasts, until end() is called.
|
var/length = 0 // Determines how long the event lasts, until end() is called.
|
||||||
@@ -25,3 +26,24 @@
|
|||||||
|
|
||||||
/datum/gm_action/proc/announce()
|
/datum/gm_action/proc/announce()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
/datum/gm_action/proc/should_end()
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/datum/gm_action/Topic(href, href_list)
|
||||||
|
if(..())
|
||||||
|
return
|
||||||
|
|
||||||
|
if(!is_admin(usr))
|
||||||
|
message_admins("[usr] has attempted to force an event without being an admin.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if(href_list["force"])
|
||||||
|
// gm.run_action(src)
|
||||||
|
message_admins("GM event [name] was forced by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["toggle"])
|
||||||
|
enabled = !enabled
|
||||||
|
message_admins("GM event [name] was toggled [enabled ? "on" : "off"] by [usr.key].")
|
||||||
|
|
||||||
|
SSgame_master.interact(usr) // To refresh the UI.
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
/datum/gm_action/security_drill
|
|
||||||
name = "security drills"
|
|
||||||
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE)
|
|
||||||
|
|
||||||
/datum/gm_action/security_drill/announce()
|
|
||||||
spawn(rand(1 MINUTE, 2 MINUTES))
|
|
||||||
command_announcement.Announce("[pick("A NanoTrasen security director", "A Vir-Gov correspondant", "Local Sif authoritiy")] has advised the enactment of [pick("a rampant wildlife", "a fire", "a hostile boarding", "a nonstandard", "a bomb", "an emergent intelligence")] drill with the personnel onboard \the [station_name()].", "Security Advisement")
|
|
||||||
|
|
||||||
/datum/gm_action/security_drill/get_weight()
|
|
||||||
return max(-20, 10 + gm.staleness - (gm.danger * 2)) + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 5) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 1.5)
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/datum/gm_action/security_drill
|
||||||
|
name = "security drills"
|
||||||
|
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE)
|
||||||
|
chaos_threshold = 20 // To avoid CentCom from mandating a drill while people are dying left and right.
|
||||||
|
|
||||||
|
/datum/gm_action/security_drill/announce()
|
||||||
|
command_announcement.Announce("[pick("A NanoTrasen security director", "A Vir-Gov correspondant", "Local Sif authoritiy")] \
|
||||||
|
has advised the enactment of [pick("a rampant wildlife", "a fire", "a hostile boarding", "a nonstandard", \
|
||||||
|
"a bomb", "an emergent intelligence")] drill with the personnel onboard \the [station_name()].", "Security Advisement")
|
||||||
|
|
||||||
|
/datum/gm_action/security_drill/get_weight()
|
||||||
|
var/sec = metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||||
|
var/everyone = metric.count_people_in_department(DEPARTMENT_EVERYONE)
|
||||||
|
|
||||||
|
if(!sec) // If there's no security, then there is no drill.
|
||||||
|
return 0
|
||||||
|
if(everyone - sec < 0) // If there's no non-sec, then there is no drill.
|
||||||
|
return 0
|
||||||
|
|
||||||
|
// Each security player adds +5 weight, while non-security adds +1.5.
|
||||||
|
return (sec * 5) + ((everyone - sec) * 1.5)
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/datum/gm_action/rogue_drone
|
/datum/gm_action/rogue_drone
|
||||||
name = "rogue drones"
|
name = "rogue drones"
|
||||||
departments = list(DEPARTMENT_SECURITY)
|
departments = list(DEPARTMENT_SECURITY)
|
||||||
chaotic = 60
|
chaotic = 20
|
||||||
|
chaos_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||||
length = 20 MINUTES
|
length = 20 MINUTES
|
||||||
var/list/drones_list = list()
|
var/list/drones_list = list()
|
||||||
|
|
||||||
@@ -60,4 +61,6 @@
|
|||||||
command_announcement.Announce("We're disappointed at the loss of the drones, but the survivors have been recovered.", "Rogue drone alert")
|
command_announcement.Announce("We're disappointed at the loss of the drones, but the survivors have been recovered.", "Rogue drone alert")
|
||||||
|
|
||||||
/datum/gm_action/rogue_drone/get_weight()
|
/datum/gm_action/rogue_drone/get_weight()
|
||||||
return 20 + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 10) + (metric.count_all_space_mobs() * 30)
|
. = 20 // Start with a base weight of 20, since this event does provide some value even if no sec is around.
|
||||||
|
. += metric.count_people_in_department(DEPARTMENT_SECURITY) * 10 // Each sec adds 10.
|
||||||
|
. += metric.count_all_space_mobs() * 30 // Each player currently EVA adds 30.
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "spider infestation"
|
name = "spider infestation"
|
||||||
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL, DEPARTMENT_EVERYONE)
|
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL, DEPARTMENT_EVERYONE)
|
||||||
chaotic = 30
|
chaotic = 30
|
||||||
|
chaos_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||||
|
|
||||||
severity = 1
|
severity = 1
|
||||||
|
|
||||||
@@ -26,11 +27,13 @@
|
|||||||
spawncount = rand(4 * severity, 6 * severity)
|
spawncount = rand(4 * severity, 6 * severity)
|
||||||
|
|
||||||
/datum/gm_action/spider_infestation/announce()
|
/datum/gm_action/spider_infestation/announce()
|
||||||
command_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg')
|
command_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. \
|
||||||
|
Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg')
|
||||||
|
|
||||||
if(severity >= EVENT_LEVEL_MAJOR)
|
if(severity >= EVENT_LEVEL_MAJOR)
|
||||||
spawn(rand(600, 3000))
|
spawn(rand(600, 3000))
|
||||||
command_announcement.Announce("Unidentified lifesigns previously detected coming aboard [station_name()] have been classified as a swarm of arachnids. Extreme caution is advised.", "Arachnid Alert")
|
command_announcement.Announce("Unidentified lifesigns previously detected coming aboard [station_name()] \
|
||||||
|
have been classified as a swarm of arachnids. Extreme caution is advised.", "Arachnid Alert")
|
||||||
|
|
||||||
/datum/gm_action/spider_infestation/start()
|
/datum/gm_action/spider_infestation/start()
|
||||||
..()
|
..()
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/client/proc/show_gm_status()
|
|
||||||
set category = "Debug"
|
|
||||||
set name = "Show GM Status"
|
|
||||||
set desc = "Shows you what the GM is thinking. If only that existed in real life..."
|
|
||||||
|
|
||||||
game_master.interact(usr)
|
|
||||||
|
|
||||||
/datum/game_master/proc/interact(var/client/user)
|
|
||||||
if(!user)
|
|
||||||
return
|
|
||||||
|
|
||||||
var/HTML = "<html><head><title>Game Master AI</title></head><body>"
|
|
||||||
|
|
||||||
HTML += "<a href='?src=\ref[src];toggle_time_restrictions=1'>\[Toggle Time Restrictions\]</a> | \
|
|
||||||
<a href='?src=\ref[src];suspend=1'>\[Toggle GM\]</a> | \
|
|
||||||
<a href='?src=\ref[src];force_choose_event=1'>\[Force Event Decision\]</a><br>"
|
|
||||||
|
|
||||||
HTML += "Status: [pre_action_checks() ? "Ready" : "Suppressed"]<br><br>"
|
|
||||||
|
|
||||||
HTML += "Staleness: [staleness] <a href='?src=\ref[src];adjust_staleness=1'>\[Adjust\]</a><br>"
|
|
||||||
HTML += "Danger: [danger] <a href='?src=\ref[src];adjust_danger=1'>\[Adjust\]</a><br><br>"
|
|
||||||
|
|
||||||
HTML += "Actions available;<br>"
|
|
||||||
for(var/datum/gm_action/action in available_actions)
|
|
||||||
if(action.enabled == FALSE)
|
|
||||||
continue
|
|
||||||
HTML += "[action.name] ([english_list(action.departments)]) (weight: [action.get_weight()]) <a href='?src=\ref[action];force=1'>\[Force\]</a> <br>"
|
|
||||||
|
|
||||||
HTML += "<br>"
|
|
||||||
HTML += "All living mobs activity: [metric.assess_all_living_mobs()]%<br>"
|
|
||||||
HTML += "All ghost activity: [metric.assess_all_dead_mobs()]%<br>"
|
|
||||||
|
|
||||||
HTML += "<br>"
|
|
||||||
HTML += "Departmental activity;<br>"
|
|
||||||
for(var/department in metric.departments)
|
|
||||||
HTML += " [department] : [metric.assess_department(department)]%<br>"
|
|
||||||
|
|
||||||
HTML += "<br>"
|
|
||||||
HTML += "Activity of players;<br>"
|
|
||||||
for(var/mob/player in player_list)
|
|
||||||
HTML += " [player] ([player.key]) : [metric.assess_player_activity(player)]%<br>"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HTML +="</body></html>"
|
|
||||||
user << browse(HTML, "window=log;size=400x450;border=1;can_resize=1;can_close=1;can_minimize=1")
|
|
||||||
|
|
||||||
/datum/game_master/Topic(href, href_list)
|
|
||||||
if(..())
|
|
||||||
return
|
|
||||||
|
|
||||||
if(!is_admin(usr))
|
|
||||||
message_admins("[usr] has attempted to modify the Game Master values without being an admin.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if(href_list["toggle_time_restrictions"])
|
|
||||||
ignore_time_restrictions = !ignore_time_restrictions
|
|
||||||
message_admins("GM event time restrictions was [ignore_time_restrictions ? "dis" : "en"]abled by [usr.key].")
|
|
||||||
|
|
||||||
if(href_list["force_choose_event"])
|
|
||||||
start_action()
|
|
||||||
message_admins("[usr.key] forced the Game Master to choose an event immediately.")
|
|
||||||
|
|
||||||
if(href_list["suspend"])
|
|
||||||
suspended = !suspended
|
|
||||||
message_admins("GM was [suspended ? "dis" : "en"]abled by [usr.key].")
|
|
||||||
|
|
||||||
if(href_list["adjust_staleness"])
|
|
||||||
var/amount = input(usr, "How much staleness should be added or subtracted?", "Game Master") as null|num
|
|
||||||
if(amount)
|
|
||||||
adjust_staleness(amount)
|
|
||||||
message_admins("GM staleness was adjusted by [amount] by [usr.key].")
|
|
||||||
|
|
||||||
if(href_list["adjust_danger"])
|
|
||||||
var/amount = input(usr, "How much danger should be added or subtracted?", "Game Master") as null|num
|
|
||||||
if(amount)
|
|
||||||
adjust_danger(amount)
|
|
||||||
message_admins("GM danger was adjusted by [amount] by [usr.key].")
|
|
||||||
|
|
||||||
interact(usr) // To refresh the UI.
|
|
||||||
|
|
||||||
/datum/gm_action/Topic(href, href_list)
|
|
||||||
if(..())
|
|
||||||
return
|
|
||||||
|
|
||||||
if(!is_admin(usr))
|
|
||||||
message_admins("[usr] has attempted to force an event without being an admin.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if(href_list["force"])
|
|
||||||
gm.run_action(src)
|
|
||||||
message_admins("GM event [name] was forced by [usr.key].")
|
|
||||||
@@ -1 +1,5 @@
|
|||||||
#define EVENT_BASELINE_WEIGHT 200
|
#define EVENT_BASELINE_WEIGHT 200
|
||||||
|
|
||||||
|
#define EVENT_CHAOS_THRESHOLD_HIGH_IMPACT 25
|
||||||
|
#define EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT 50
|
||||||
|
#define EVENT_CHAOS_THRESHOLD_LOW_IMPACT 100
|
||||||
185
code/modules/gamemaster/event2/event.dm
Normal file
185
code/modules/gamemaster/event2/event.dm
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// This object holds the code that is needed to execute an event.
|
||||||
|
// Code for judging whether doing that event is a good idea or not belongs inside its meta event object.
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Important: DO NOT `sleep()` in any of the procs here, or the GM will get stuck. Use callbacks insead.
|
||||||
|
Also please don't use spawn(), but use callbacks instead.
|
||||||
|
|
||||||
|
Note that there is an important distinction between an event being ended, and an event being finished.
|
||||||
|
- Ended is for when the actual event is over, regardless of whether an announcement happened or not.
|
||||||
|
- Finished is for when both the event itself is over, and it was announced. The event will stop being
|
||||||
|
processed after it is finished.
|
||||||
|
|
||||||
|
For an event to finish, it must have done two things:
|
||||||
|
- Go through its entire cycle, of start() -> end(), and
|
||||||
|
- Have the event be announced.
|
||||||
|
If an event has ended, but the announcement didn't happen, the event will not be finished.
|
||||||
|
This allows for events that have their announcement happen after the end itself.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
/datum/event2/event
|
||||||
|
var/announced = FALSE // Is set to TRUE when `announce()` is called by `process()`.
|
||||||
|
var/started = FALSE // Is set to TRUE when `start()` is called by `process()`.
|
||||||
|
var/ended = FALSE // Is set to TRUE when `end()` is called by `process()`.
|
||||||
|
var/finished = FALSE // Is set to TRUE when `ended` and `announced` are TRUE.
|
||||||
|
|
||||||
|
// `world.time`s when this event started, and finished, for bookkeeping.
|
||||||
|
var/time_started = null
|
||||||
|
var/time_finished = null
|
||||||
|
|
||||||
|
// If these are set, the announcement will be delayed by a random time between the lower and upper bounds.
|
||||||
|
// If the upper bound is not defined, then it will use the lower bound instead.
|
||||||
|
// Note that this is independant of the event itself, so you can have the announcement happen long after the event ended.
|
||||||
|
// This may not work if should_announce() is overrided.
|
||||||
|
var/announce_delay_lower_bound = null
|
||||||
|
var/announce_delay_upper_bound = null
|
||||||
|
|
||||||
|
// If these are set, the event will be delayed by a random time between the lower and upper bounds.
|
||||||
|
// If the upper bound is not defined, then it will use the lower bound instead.
|
||||||
|
// This may not work if should_start() is overrided.
|
||||||
|
var/start_delay_lower_bound = null
|
||||||
|
var/start_delay_upper_bound = null
|
||||||
|
|
||||||
|
// If these are set, the event will automatically end at a random time between the lower and upper bounds.
|
||||||
|
// If the upper bound is not defined, then it will use the lower bound instead.
|
||||||
|
// This may not work if should_end() is overrided.
|
||||||
|
var/length_lower_bound = null
|
||||||
|
var/length_upper_bound = null
|
||||||
|
|
||||||
|
// Set automatically, don't touch.
|
||||||
|
var/time_to_start = null
|
||||||
|
var/time_to_announce = null
|
||||||
|
var/time_to_end = null
|
||||||
|
|
||||||
|
// Returns the name of where the event is taking place.
|
||||||
|
// In the future this might be handy for off-station events.
|
||||||
|
/datum/event2/event/proc/location_name()
|
||||||
|
return station_name()
|
||||||
|
|
||||||
|
// Returns the z-levels that are involved with the event.
|
||||||
|
// In the future this might be handy for off-station events.
|
||||||
|
/datum/event2/event/proc/get_location_z_levels()
|
||||||
|
return using_map.station_levels
|
||||||
|
|
||||||
|
// Starts the event.
|
||||||
|
/datum/event2/event/proc/execute()
|
||||||
|
time_started = world.time
|
||||||
|
|
||||||
|
if(announce_delay_lower_bound)
|
||||||
|
time_to_announce = world.time + rand(announce_delay_lower_bound, announce_delay_upper_bound ? announce_delay_upper_bound : announce_delay_lower_bound)
|
||||||
|
|
||||||
|
if(start_delay_lower_bound)
|
||||||
|
time_to_start = world.time + rand(start_delay_lower_bound, start_delay_upper_bound ? start_delay_upper_bound : start_delay_lower_bound)
|
||||||
|
|
||||||
|
if(length_lower_bound)
|
||||||
|
time_to_end = world.time + rand(length_lower_bound, length_upper_bound ? length_upper_bound : length_lower_bound)
|
||||||
|
|
||||||
|
set_up()
|
||||||
|
|
||||||
|
// Called at the very end of the event's lifecycle, or when aborted.
|
||||||
|
// Don't override this, use `end()` for cleanup instead.
|
||||||
|
/datum/event2/event/proc/finish()
|
||||||
|
finished = TRUE
|
||||||
|
time_finished = world.time
|
||||||
|
|
||||||
|
// Called by admins wanting to stop an event immediately.
|
||||||
|
/datum/event2/event/proc/abort()
|
||||||
|
if(!announced)
|
||||||
|
announce()
|
||||||
|
if(!ended) // `end()` generally has cleanup procs, so call that.
|
||||||
|
end()
|
||||||
|
finish()
|
||||||
|
|
||||||
|
// Called by the GM processer.
|
||||||
|
/datum/event2/event/process()
|
||||||
|
// Handle announcement track.
|
||||||
|
if(!announced && should_announce())
|
||||||
|
announce()
|
||||||
|
announced = TRUE
|
||||||
|
|
||||||
|
// Handle event track.
|
||||||
|
if(!started)
|
||||||
|
if(should_start())
|
||||||
|
start()
|
||||||
|
started = TRUE
|
||||||
|
else
|
||||||
|
wait_tick()
|
||||||
|
|
||||||
|
if(started && !ended)
|
||||||
|
if(should_end())
|
||||||
|
end()
|
||||||
|
ended = TRUE
|
||||||
|
else
|
||||||
|
event_tick()
|
||||||
|
|
||||||
|
// In order to be finished, the event needs to end, and be announced.
|
||||||
|
if(ended && announced)
|
||||||
|
finish()
|
||||||
|
|
||||||
|
/datum/event2/event/Topic(href, href_list)
|
||||||
|
if(..())
|
||||||
|
return
|
||||||
|
|
||||||
|
if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||||
|
message_admins("[usr] has attempted to manipulate an event without sufficent privilages.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if(href_list["abort"])
|
||||||
|
abort()
|
||||||
|
message_admins("Event '[type]' was aborted by [usr.key].")
|
||||||
|
|
||||||
|
SSgame_master.interact(usr) // To refresh the UI.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Procs to Override
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Override this for code to be ran before the event is started.
|
||||||
|
/datum/event2/event/proc/set_up()
|
||||||
|
|
||||||
|
// Called every tick from the GM system, and determines if the announcement should happen.
|
||||||
|
// Override this for special logic on when it should be announced, e.g. after `ended` is set to TRUE,
|
||||||
|
// however be aware that the event cannot finish until this returns TRUE at some point.
|
||||||
|
/datum/event2/event/proc/should_announce()
|
||||||
|
if(!time_to_announce)
|
||||||
|
return TRUE
|
||||||
|
return time_to_announce <= world.time
|
||||||
|
|
||||||
|
// Override this for code that alerts the crew that the event is happening in some form, e.g. a centcom announcement or some other message.
|
||||||
|
// If you want them to not know, you can just not override it.
|
||||||
|
/datum/event2/event/proc/announce()
|
||||||
|
|
||||||
|
// Override for code that runs every few seconds, while the event is waiting for `should_start()` to return TRUE.
|
||||||
|
// Note that events that have `should_start()` return TRUE at the start will never have this proc called.
|
||||||
|
/datum/event2/event/proc/wait_tick()
|
||||||
|
|
||||||
|
// Called every tick from the GM system, and determines if the event should offically start.
|
||||||
|
// Override this for special logic on when it should start.
|
||||||
|
/datum/event2/event/proc/should_start()
|
||||||
|
if(!time_to_start)
|
||||||
|
return TRUE
|
||||||
|
return time_to_start <= world.time
|
||||||
|
|
||||||
|
// Override this for code to do the actual event.
|
||||||
|
/datum/event2/event/proc/start()
|
||||||
|
|
||||||
|
|
||||||
|
// Override for code that runs every few seconds, while the event is waiting for `should_end()` to return TRUE.
|
||||||
|
// Note that events that have `should_end()` return TRUE at the start will never have this proc called.
|
||||||
|
/datum/event2/event/proc/event_tick()
|
||||||
|
|
||||||
|
|
||||||
|
// Called every tick from the GM system, and determines if the event should end.
|
||||||
|
// If this returns TRUE at the very start, then the event ends instantly and `tick()` will never be called.
|
||||||
|
// Override this for special logic on when it should end, e.g. blob core has to die before event ends.
|
||||||
|
/datum/event2/event/proc/should_end()
|
||||||
|
if(!time_to_end)
|
||||||
|
return TRUE
|
||||||
|
return time_to_end <= world.time
|
||||||
|
|
||||||
|
// Override this for code to run when the event is over, e.g. cleanup.
|
||||||
|
/datum/event2/event/proc/end()
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/datum/event2/meta/shipping_error
|
||||||
|
name = "shipping error"
|
||||||
|
departments = list(DEPARTMENT_CARGO)
|
||||||
|
chaos = -10 // A helpful event.
|
||||||
|
reusable = TRUE
|
||||||
|
event_type = /datum/event2/event/shipping_error
|
||||||
|
|
||||||
|
/datum/event2/meta/shipping_error/get_weight()
|
||||||
|
return metric.count_people_in_department(DEPARTMENT_CARGO) * 40
|
||||||
|
|
||||||
|
/datum/event2/event/shipping_error/start()
|
||||||
|
var/datum/supply_order/O = new /datum/supply_order()
|
||||||
|
O.ordernum = supply_controller.ordernum
|
||||||
|
O.object = supply_controller.supply_pack[pick(supply_controller.supply_pack)]
|
||||||
|
O.ordered_by = random_name(pick(MALE,FEMALE), species = "Human")
|
||||||
|
supply_controller.shoppinglist += O
|
||||||
163
code/modules/gamemaster/event2/events/engineering/blob.dm
Normal file
163
code/modules/gamemaster/event2/events/engineering/blob.dm
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/datum/event2/meta/blob
|
||||||
|
name = "blob"
|
||||||
|
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL)
|
||||||
|
chaos = 50
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT
|
||||||
|
event_type = /datum/event2/event/blob
|
||||||
|
|
||||||
|
/datum/event2/meta/blob/get_weight()
|
||||||
|
var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING)
|
||||||
|
var/security = metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||||
|
var/medical = metric.count_people_in_department(DEPARTMENT_MEDICAL)
|
||||||
|
|
||||||
|
var/assigned_staff = engineers + security
|
||||||
|
if(engineers || security) // Medical only counts if one of the other two exists, and even then they count as half.
|
||||||
|
assigned_staff += round(medical / 2)
|
||||||
|
|
||||||
|
var/weight = (max(assigned_staff - 2, 0) * 20) // An assigned staff count of 2 must be had to spawn a blob.
|
||||||
|
return weight
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/datum/event2/event/blob
|
||||||
|
|
||||||
|
// This could be made into a GLOB accessible list if needed.
|
||||||
|
var/list/area/excuded = list(
|
||||||
|
/area/submap,
|
||||||
|
/area/shuttle,
|
||||||
|
/area/crew_quarters,
|
||||||
|
/area/holodeck,
|
||||||
|
/area/engineering/engine_room
|
||||||
|
)
|
||||||
|
var/turf/target_turf = null
|
||||||
|
var/area/target_area = null
|
||||||
|
var/spawn_blob_type = /obj/structure/blob/core/random_medium
|
||||||
|
var/obj/structure/blob/core/blob_core = null
|
||||||
|
|
||||||
|
/datum/event2/event/blob/set_up()
|
||||||
|
var/list/area/grand_list_of_areas = get_station_areas(excluded)
|
||||||
|
|
||||||
|
for(var/i in 1 to 10)
|
||||||
|
var/area/A = pick(grand_list_of_areas)
|
||||||
|
if(is_area_occupied(A))
|
||||||
|
log_debug("Blob infestation event: Rejected [A] because it is occupied.")
|
||||||
|
continue
|
||||||
|
var/list/turfs = list()
|
||||||
|
for(var/turf/simulated/floor/F in A)
|
||||||
|
if(turf_clear(F))
|
||||||
|
turfs += F
|
||||||
|
if(turfs.len == 0)
|
||||||
|
log_debug("Blob infestation event: Rejected [A] because it has no clear turfs.")
|
||||||
|
continue
|
||||||
|
target_area = A
|
||||||
|
target_turf = pick(turfs)
|
||||||
|
|
||||||
|
if(!target_area)
|
||||||
|
log_debug("Blob infestation event: Giving up after too many failures to pick target area")
|
||||||
|
|
||||||
|
/datum/event2/event/blob/start()
|
||||||
|
blob_core = new spawn_blob_type(target_turf)
|
||||||
|
|
||||||
|
/datum/event2/event/blob/announce()
|
||||||
|
spawn(rand(600, 3000)) // 1-5 minute leeway for the blob to go un-detected.
|
||||||
|
command_announcement.Announce("Confirmed outbreak of level 7 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
/datum/gm_action/blob
|
||||||
|
name = "blob infestation"
|
||||||
|
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL)
|
||||||
|
chaotic = 25
|
||||||
|
|
||||||
|
var/list/area/excluded = list(
|
||||||
|
/area/submap,
|
||||||
|
/area/shuttle,
|
||||||
|
/area/crew_quarters,
|
||||||
|
/area/holodeck,
|
||||||
|
/area/engineering/engine_room
|
||||||
|
)
|
||||||
|
|
||||||
|
var/area/target_area // Chosen target area
|
||||||
|
var/turf/target_turf // Chosen target turf in target_area
|
||||||
|
|
||||||
|
var/obj/structure/blob/core/Blob
|
||||||
|
var/spawn_blob_type = /obj/structure/blob/core/random_medium
|
||||||
|
|
||||||
|
/datum/gm_action/blob/set_up()
|
||||||
|
severity = pickweight(EVENT_LEVEL_MUNDANE = 4,
|
||||||
|
EVENT_LEVEL_MODERATE = 2,
|
||||||
|
EVENT_LEVEL_MAJOR = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var/list/area/grand_list_of_areas = get_station_areas(excluded)
|
||||||
|
|
||||||
|
for(var/i in 1 to 10)
|
||||||
|
var/area/A = pick(grand_list_of_areas)
|
||||||
|
if(is_area_occupied(A))
|
||||||
|
log_debug("Blob infestation event: Rejected [A] because it is occupied.")
|
||||||
|
continue
|
||||||
|
var/list/turfs = list()
|
||||||
|
for(var/turf/simulated/floor/F in A)
|
||||||
|
if(turf_clear(F))
|
||||||
|
turfs += F
|
||||||
|
if(turfs.len == 0)
|
||||||
|
log_debug("Blob infestation event: Rejected [A] because it has no clear turfs.")
|
||||||
|
continue
|
||||||
|
target_area = A
|
||||||
|
target_turf = pick(turfs)
|
||||||
|
|
||||||
|
if(!target_area)
|
||||||
|
log_debug("Blob infestation event: Giving up after too many failures to pick target area")
|
||||||
|
|
||||||
|
/datum/gm_action/blob/start()
|
||||||
|
..()
|
||||||
|
var/turf/T
|
||||||
|
|
||||||
|
if(severity == EVENT_LEVEL_MUNDANE || !target_area || !target_turf)
|
||||||
|
T = pick(blobstart)
|
||||||
|
else if(severity == EVENT_LEVEL_MODERATE)
|
||||||
|
T = target_turf
|
||||||
|
else
|
||||||
|
T = target_turf
|
||||||
|
spawn_blob_type = /obj/structure/blob/core/random_hard
|
||||||
|
|
||||||
|
Blob = new spawn_blob_type(T)
|
||||||
|
|
||||||
|
/datum/gm_action/blob/announce()
|
||||||
|
spawn(rand(600, 3000)) // 1-5 minute leeway for the blob to go un-detected.
|
||||||
|
command_announcement.Announce("Confirmed outbreak of level 7 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
|
||||||
|
|
||||||
|
/datum/gm_action/blob/get_weight()
|
||||||
|
var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING)
|
||||||
|
var/security = metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||||
|
var/medical = metric.count_people_in_department(DEPARTMENT_MEDICAL)
|
||||||
|
|
||||||
|
var/assigned_staff = engineers + security
|
||||||
|
if(engineers || security) // Medical only counts if one of the other two exists, and even then they count as half.
|
||||||
|
assigned_staff += round(medical / 2)
|
||||||
|
|
||||||
|
var/weight = (max(assigned_staff - 2, 0) * 20) // An assigned staff count of 2 must be had to spawn a blob.
|
||||||
|
return weight
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
/proc/level_seven_blob_announcement(var/obj/structure/blob/core/B)
|
||||||
|
if(!B || !B.overmind)
|
||||||
|
return
|
||||||
|
var/datum/blob_type/blob = B.overmind.blob_type // Shortcut so we don't need to delve into three variables every time.
|
||||||
|
var/list/lines = list()
|
||||||
|
|
||||||
|
lines += "Confirmed outbreak of level [7 + blob.difficulty] biohazard aboard [station_name()]. All personnel must contain the outbreak."
|
||||||
|
|
||||||
|
if(blob.difficulty >= BLOB_DIFFICULTY_MEDIUM) // Tell them what kind of blob it is if it's tough.
|
||||||
|
lines += "The biohazard has been identified as a '[blob.name]'."
|
||||||
|
|
||||||
|
if(blob.difficulty >= BLOB_DIFFICULTY_HARD) // If it's really hard then tell them where it is so the response occurs faster.
|
||||||
|
lines += "It is suspected to have originated from \the [get_area(B)]."
|
||||||
|
|
||||||
|
if(blob.difficulty >= BLOB_DIFFICULTY_SUPERHARD)
|
||||||
|
lines += "Extreme caution is advised."
|
||||||
|
|
||||||
|
command_announcement.Announce(lines.Join("\n"), "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
|
||||||
|
*/
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/datum/event2/meta/camera_damage
|
||||||
|
name = "random camera damage"
|
||||||
|
departments = list(DEPARTMENT_SYNTHETIC, DEPARTMENT_ENGINEERING)
|
||||||
|
chaos = 5
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||||
|
reusable = TRUE
|
||||||
|
event_type = /datum/event2/event/camera_damage
|
||||||
|
|
||||||
|
/datum/event2/meta/camera_damage/get_weight()
|
||||||
|
return 30 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) + (metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 40)
|
||||||
|
|
||||||
|
/datum/event2/event/camera_damage
|
||||||
|
var/camera_range = 7
|
||||||
|
|
||||||
|
/datum/event2/event/camera_damage/start()
|
||||||
|
var/obj/machinery/camera/C = acquire_random_camera()
|
||||||
|
if(!C)
|
||||||
|
return
|
||||||
|
|
||||||
|
for(var/obj/machinery/camera/cam in range(camera_range, C))
|
||||||
|
if(is_valid_camera(cam))
|
||||||
|
cam.wires.UpdateCut(CAMERA_WIRE_POWER, 0)
|
||||||
|
if(prob(25))
|
||||||
|
cam.wires.UpdateCut(CAMERA_WIRE_ALARM, 0)
|
||||||
|
|
||||||
|
/datum/event2/event/camera_damage/proc/acquire_random_camera(var/remaining_attempts = 5)
|
||||||
|
if(!cameranet.cameras.len)
|
||||||
|
return
|
||||||
|
if(!remaining_attempts)
|
||||||
|
return
|
||||||
|
|
||||||
|
var/obj/machinery/camera/C = pick(cameranet.cameras)
|
||||||
|
if(is_valid_camera(C))
|
||||||
|
return C
|
||||||
|
// It is very important to use --var and not var-- for recursive calls, as var-- will cause an infinite loop.
|
||||||
|
return acquire_random_camera(--remaining_attempts)
|
||||||
|
|
||||||
|
/datum/event2/event/camera_damage/proc/is_valid_camera(var/obj/machinery/camera/C)
|
||||||
|
// Only return a functional camera, not installed in a silicon/hardsuit/circuit/etc, and that exists somewhere players have access
|
||||||
|
var/turf/T = get_turf(C)
|
||||||
|
return T && C.can_use() && istype(C.loc, /turf) && (T.z in using_map.player_levels)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// This event chooses a random canister on player levels and breaks it, releasing its contents!
|
||||||
|
//
|
||||||
|
|
||||||
|
/datum/event2/meta/canister_leak
|
||||||
|
name = "canister leak"
|
||||||
|
departments = list(DEPARTMENT_ENGINEERING)
|
||||||
|
chaos = 10
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||||
|
reusable = TRUE
|
||||||
|
event_type = /datum/event2/event/canister_leak
|
||||||
|
|
||||||
|
/datum/event2/meta/canister_leak/get_weight()
|
||||||
|
return metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 30
|
||||||
|
|
||||||
|
/datum/event2/event/canister_leak/start()
|
||||||
|
// List of all non-destroyed canisters on station levels
|
||||||
|
var/list/all_canisters = list()
|
||||||
|
for(var/obj/machinery/portable_atmospherics/canister/C in machines)
|
||||||
|
if(!C.destroyed && (C.z in using_map.station_levels) && C.air_contents.total_moles >= MOLES_CELLSTANDARD)
|
||||||
|
all_canisters += C
|
||||||
|
var/obj/machinery/portable_atmospherics/canister/C = pick(all_canisters)
|
||||||
|
log_debug("canister_leak event: Canister [C] ([C.x],[C.y],[C.z]) destroyed.")
|
||||||
|
C.health = 0
|
||||||
|
C.healthcheck()
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// New grid check event:
|
||||||
|
// Very similar to the old one, power goes out in most of the station, however the new feature is the ability for engineering to
|
||||||
|
// get power back on sooner, if they are able to reach a special machine and initiate a manual reboot. If no one is able to do so,
|
||||||
|
// it will reboot itself after a few minutes, just like the old one. Bad things happen if there is no grid checker machine protecting
|
||||||
|
// the powernet when this event fires.
|
||||||
|
|
||||||
|
/datum/event2/meta/grid_check
|
||||||
|
name = "grid check"
|
||||||
|
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_EVERYONE)
|
||||||
|
chaos = 10
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||||
|
reusable = TRUE
|
||||||
|
event_type = /datum/event2/event/grid_check
|
||||||
|
|
||||||
|
// Having the turbines be way over their rated limit makes grid checks more likely.
|
||||||
|
/datum/event2/meta/grid_check/proc/get_overpower()
|
||||||
|
var/highest_overpower = 0
|
||||||
|
for(var/T in GLOB.all_turbines)
|
||||||
|
var/obj/machinery/power/generator/turbine = T
|
||||||
|
var/overpower = max((turbine.effective_gen / turbine.max_power) - 1, 0)
|
||||||
|
if(overpower > highest_overpower)
|
||||||
|
highest_overpower = overpower
|
||||||
|
return highest_overpower
|
||||||
|
|
||||||
|
/datum/event2/meta/grid_check/get_weight()
|
||||||
|
return 50 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) + (50 * get_overpower())
|
||||||
|
|
||||||
|
/datum/event2/event/grid_check
|
||||||
|
var/obj/machinery/power/generator/engine // The turbine that will send a power spike.
|
||||||
|
|
||||||
|
/datum/event2/event/grid_check/set_up()
|
||||||
|
// Find the turbine being pushed the most.
|
||||||
|
var/obj/machinery/power/generator/most_stressed_turbine = null
|
||||||
|
for(var/T in GLOB.all_turbines)
|
||||||
|
var/obj/machinery/power/generator/turbine = T
|
||||||
|
if(!most_stressed_turbine)
|
||||||
|
most_stressed_turbine = turbine
|
||||||
|
else if(turbine.effective_gen > most_stressed_turbine.effective_gen)
|
||||||
|
most_stressed_turbine = turbine
|
||||||
|
engine = most_stressed_turbine
|
||||||
|
|
||||||
|
/datum/event2/event/grid_check/start()
|
||||||
|
// This sets off a chain of events that lead to the actual grid check (or perhaps worse).
|
||||||
|
// First, the Supermatter engine makes a power spike.
|
||||||
|
if(engine)
|
||||||
|
engine.power_spike()
|
||||||
|
// After that, the engine checks if a grid checker exists on the same powernet, and if so, it triggers a blackout.
|
||||||
|
// If not, lots of stuff breaks. See code/modules/power/generator.dm for that piece of code.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/datum/event2/meta/spacevine
|
||||||
|
name = "space-vine infestation"
|
||||||
|
departments = list(DEPARTMENT_ENGINEERING)
|
||||||
|
chaos = 10 // There's a really rare chance of vines getting something awful like phoron atmosphere but thats not really controllable.
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||||
|
event_type = /datum/event2/event/spacevine
|
||||||
|
|
||||||
|
/datum/event2/meta/spacevine/get_weight()
|
||||||
|
return 20 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/datum/event2/event/spacevine/announce()
|
||||||
|
level_seven_announcement()
|
||||||
|
|
||||||
|
/datum/event2/event/spacevine/start()
|
||||||
|
spacevine_infestation()
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/datum/event2/meta/comms_blackout
|
||||||
|
name = "communications blackout"
|
||||||
|
departments = list(DEPARTMENT_EVERYONE) // It's not an engineering event because engineering can't do anything to help . . . for now.
|
||||||
|
chaos = 10
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||||
|
reusable = TRUE
|
||||||
|
event_type = /datum/event2/event/comms_blackout
|
||||||
|
|
||||||
|
/datum/event2/meta/comms_blackout/get_weight()
|
||||||
|
return 50 + metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/datum/event2/event/comms_blackout/announce()
|
||||||
|
if(prob(33))
|
||||||
|
command_announcement.Announce("Ionospheric anomalies detected. \
|
||||||
|
Temporary telecommunication failure imminent. Please contact you-BZZT", new_sound = 'sound/misc/interference.ogg')
|
||||||
|
// AIs will always know if there's a comm blackout, rogue AIs could then lie about comm blackouts in the future while they shutdown comms
|
||||||
|
for(var/mob/living/silicon/ai/A in player_list)
|
||||||
|
to_chat(A, "<br>")
|
||||||
|
to_chat(A, "<span class='warning'><b>Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT</b></span>")
|
||||||
|
to_chat(A, "<br>")
|
||||||
|
|
||||||
|
/datum/event2/event/comms_blackout/start()
|
||||||
|
// One in two chance for the radios to turn i%t# t&_)#%, which can be more alarming than radio silence.
|
||||||
|
if(prob(50))
|
||||||
|
for(var/obj/machinery/telecomms/processor/P in telecomms_list)
|
||||||
|
P.emp_act(1)
|
||||||
|
// Otherwise just shut everything down.
|
||||||
|
else
|
||||||
|
for(var/obj/machinery/telecomms/T in telecomms_list)
|
||||||
|
T.emp_act(1)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/datum/event2/meta/solar_storm
|
||||||
|
name = "solar storm"
|
||||||
|
reusable = TRUE
|
||||||
|
event_type = /datum/event2/event/solar_storm
|
||||||
|
|
||||||
|
/datum/event2/meta/solar_storm/get_weight()
|
||||||
|
return 20 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10) + (metric.count_all_space_mobs() * 30)
|
||||||
|
|
||||||
|
|
||||||
|
/datum/event2/event/solar_storm
|
||||||
|
start_delay_lower_bound = 1 MINUTE
|
||||||
|
start_delay_upper_bound = 1 MINUTE
|
||||||
|
length_lower_bound = 2 MINUTES
|
||||||
|
length_upper_bound = 4 MINUTES
|
||||||
|
var/base_solar_gen_rate = null
|
||||||
|
|
||||||
|
/datum/event2/event/solar_storm/announce()
|
||||||
|
command_announcement.Announce("A solar storm has been detected approaching \the [station_name()]. \
|
||||||
|
Please halt all EVA activites immediately and return to the interior of the station.", "Anomaly Alert", new_sound = 'sound/AI/radiation.ogg')
|
||||||
|
adjust_solar_output(1.5)
|
||||||
|
|
||||||
|
/datum/event2/event/solar_storm/start()
|
||||||
|
command_announcement.Announce("The solar storm has reached the station. Please refain from EVA and remain inside the station until it has passed.", "Anomaly Alert")
|
||||||
|
adjust_solar_output(5)
|
||||||
|
|
||||||
|
/datum/event2/event/solar_storm/event_tick()
|
||||||
|
radiate()
|
||||||
|
|
||||||
|
/datum/event2/event/solar_storm/end()
|
||||||
|
command_announcement.Announce("The solar storm has passed the station. It is now safe to resume EVA activities. \
|
||||||
|
Please report to medbay if you experience any unusual symptoms.", "Anomaly Alert")
|
||||||
|
adjust_solar_output(1)
|
||||||
|
|
||||||
|
/datum/event2/event/solar_storm/proc/adjust_solar_output(var/mult = 1)
|
||||||
|
if(isnull(base_solar_gen_rate))
|
||||||
|
base_solar_gen_rate = GLOB.solar_gen_rate
|
||||||
|
GLOB.solar_gen_rate = mult * base_solar_gen_rate
|
||||||
|
|
||||||
|
/datum/event2/event/solar_storm/proc/radiate()
|
||||||
|
// Note: Too complicated to be worth trying to use the radiation system for this. Its only in space anyway, so we make an exception in this case.
|
||||||
|
for(var/mob/living/L in player_list)
|
||||||
|
var/turf/T = get_turf(L)
|
||||||
|
if(!T)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if(!istype(T.loc,/area/space) && !istype(T,/turf/space)) //Make sure you're in a space area or on a space turf
|
||||||
|
continue
|
||||||
|
|
||||||
|
//Todo: Apply some burn damage from the heat of the sun. Until then, enjoy some moderate radiation.
|
||||||
|
L.rad_act(rand(15, 30))
|
||||||
54
code/modules/gamemaster/event2/events/legacy.dm
Normal file
54
code/modules/gamemaster/event2/events/legacy.dm
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// This is a somewhat special type of event, that bridges to the old event datum and makes it work with the new system.
|
||||||
|
// This is possible because the new datum is mostly superset of the old one.
|
||||||
|
/datum/event2/event/legacy
|
||||||
|
var/datum/event/legacy_event = null
|
||||||
|
var/tick_count = 0 // Used to emulate legacy's `activeFor` tick counter.
|
||||||
|
|
||||||
|
/datum/event2/meta/legacy/get_weight()
|
||||||
|
return 50
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/process()
|
||||||
|
..()
|
||||||
|
tick_count++
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/set_up()
|
||||||
|
legacy_event = new legacy_event(null, external_use = TRUE)
|
||||||
|
legacy_event.setup()
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/should_announce()
|
||||||
|
return tick_count >= legacy_event.announceWhen
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/announce()
|
||||||
|
legacy_event.announce()
|
||||||
|
|
||||||
|
|
||||||
|
// Legacy events don't tick before they start, so we don't need to do `wait_tick()`.
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/should_start()
|
||||||
|
return tick_count >= legacy_event.startWhen
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/start()
|
||||||
|
legacy_event.start()
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/event_tick()
|
||||||
|
legacy_event.tick()
|
||||||
|
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/should_end()
|
||||||
|
return tick_count >= legacy_event.endWhen
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/end()
|
||||||
|
legacy_event.end()
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/finish()
|
||||||
|
legacy_event.kill(external_use = TRUE)
|
||||||
|
..()
|
||||||
|
|
||||||
|
// Testing.
|
||||||
|
/datum/event2/meta/legacy_gravity
|
||||||
|
name = "gravity (legacy)"
|
||||||
|
reusable = TRUE
|
||||||
|
event_type = /datum/event2/event/legacy/gravity
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/gravity
|
||||||
|
legacy_event = /datum/event/gravity
|
||||||
61
code/modules/gamemaster/event2/events/legacy/legacy.dm
Normal file
61
code/modules/gamemaster/event2/events/legacy/legacy.dm
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// This is a somewhat special type of event, that bridges to the old event datum and makes it work with the new system.
|
||||||
|
// It acts as a compatability layer between the old event, and the new GM system.
|
||||||
|
// This is possible because the new datum is mostly a superset of the old one.
|
||||||
|
/datum/event2/event/legacy
|
||||||
|
var/datum/event/legacy_event = null
|
||||||
|
|
||||||
|
// Used to emulate legacy's `activeFor` tick counter.
|
||||||
|
var/tick_count = 0
|
||||||
|
|
||||||
|
// How 'severe' the legacy event should be. This should only be used for legacy events, as severity is an outdated concept for the GM system.
|
||||||
|
var/severity = EVENT_LEVEL_MODERATE
|
||||||
|
|
||||||
|
/datum/event2/meta/legacy/get_weight()
|
||||||
|
return 50
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/process()
|
||||||
|
..()
|
||||||
|
tick_count++
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/set_up()
|
||||||
|
legacy_event = new legacy_event(null, external_use = TRUE)
|
||||||
|
legacy_event.severity = severity
|
||||||
|
legacy_event.setup()
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/should_announce()
|
||||||
|
return tick_count >= legacy_event.announceWhen
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/announce()
|
||||||
|
legacy_event.announce()
|
||||||
|
|
||||||
|
|
||||||
|
// Legacy events don't tick before they start, so we don't need to do `wait_tick()`.
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/should_start()
|
||||||
|
return tick_count >= legacy_event.startWhen
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/start()
|
||||||
|
legacy_event.start()
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/event_tick()
|
||||||
|
legacy_event.tick()
|
||||||
|
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/should_end()
|
||||||
|
return tick_count >= legacy_event.endWhen
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/end()
|
||||||
|
legacy_event.end()
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/finish()
|
||||||
|
legacy_event.kill(external_use = TRUE)
|
||||||
|
..()
|
||||||
|
|
||||||
|
// Testing.
|
||||||
|
/datum/event2/meta/legacy_gravity
|
||||||
|
name = "gravity (legacy)"
|
||||||
|
reusable = TRUE
|
||||||
|
event_type = /datum/event2/event/legacy/gravity
|
||||||
|
|
||||||
|
/datum/event2/event/legacy/gravity
|
||||||
|
legacy_event = /datum/event/gravity
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/datum/event2/meta/security_drill
|
||||||
|
name = "security drill"
|
||||||
|
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE)
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT // Don't run if we just got hit by meteors.
|
||||||
|
event_type = /datum/event2/event/security_drill
|
||||||
|
|
||||||
|
/datum/event2/meta/security_drill/get_weight()
|
||||||
|
var/sec = metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||||
|
var/everyone = metric.count_people_in_department(DEPARTMENT_EVERYONE)
|
||||||
|
|
||||||
|
if(!sec) // If there's no security, then there is no drill.
|
||||||
|
return 0
|
||||||
|
if(everyone - sec < 0) // If there's no non-sec, then there is no drill.
|
||||||
|
return 0
|
||||||
|
|
||||||
|
// Each security player adds +5 weight, while non-security adds +1.5.
|
||||||
|
return (sec * 5) + ((everyone - sec) * 1.5)
|
||||||
|
|
||||||
|
/datum/event2/event/security_drill/announce()
|
||||||
|
command_announcement.Announce("[pick("A NanoTrasen security director", "A Vir-Gov correspondant", "Local Sif authoritiy")] \
|
||||||
|
has advised the enactment of [pick("a rampant wildlife", "a fire", "a hostile boarding", \
|
||||||
|
"a bomb", "an emergent intelligence")] drill with the personnel onboard \the [location_name()].", "Security Advisement")
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/datum/event2/meta/security_screening
|
||||||
|
name = "security screening"
|
||||||
|
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE)
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT // So this won't get called in the middle of a crisis.
|
||||||
|
event_type = /datum/event2/event/security_screening
|
||||||
|
|
||||||
|
/datum/event2/meta/security_screening/get_weight()
|
||||||
|
. = 0
|
||||||
|
var/sec = metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||||
|
if(!sec < 2)
|
||||||
|
return 0 // Can't screen with no security.
|
||||||
|
. += sec * 10
|
||||||
|
. += metric.count_people_in_department(DEPARTMENT_EVERYONE) * 2
|
||||||
|
|
||||||
|
// Having ""suspecious"" people present makes this more likely to be picked.
|
||||||
|
var/suspicious_people = 0
|
||||||
|
suspicious_people += metric.count_all_of_specific_species(SPECIES_PROMETHEAN) * 20
|
||||||
|
suspicious_people += metric.count_all_of_specific_species(SPECIES_UNATHI) * 10
|
||||||
|
suspicious_people += metric.count_all_of_specific_species(SPECIES_ZADDAT) * 10
|
||||||
|
suspicious_people += metric.count_all_of_specific_species(SPECIES_SKRELL) * 5 // Not sure why skrell are so high.
|
||||||
|
suspicious_people += metric.count_all_of_specific_species(SPECIES_TAJ) * 5
|
||||||
|
suspicious_people += metric.count_all_of_specific_species(SPECIES_TESHARI) * 5
|
||||||
|
suspicious_people += metric.count_all_of_specific_species(SPECIES_HUMAN_VATBORN) * 5
|
||||||
|
suspicious_people += metric.count_all_FBPs_of_kind(FBP_DRONE) * 20
|
||||||
|
suspicious_people += metric.count_all_FBPs_of_kind(FBP_POSI) * 10
|
||||||
|
if(!suspicious_people)
|
||||||
|
return 0
|
||||||
|
. += suspicious_people
|
||||||
|
|
||||||
|
/datum/event2/event/security_screening
|
||||||
|
var/victim = null
|
||||||
|
var/list/species_weights = list(
|
||||||
|
SPECIES_SKRELL = 9,
|
||||||
|
SPECIES_UNATHI = 15,
|
||||||
|
SPECIES_HUMAN_VATBORN = 6,
|
||||||
|
SPECIES_TESHARI = 2,
|
||||||
|
SPECIES_TAJ = 3,
|
||||||
|
SPECIES_DIONA = 1,
|
||||||
|
SPECIES_ZADDAT = 25,
|
||||||
|
SPECIES_PROMETHEAN = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
var/list/synth_weights = list(
|
||||||
|
FBP_CYBORG = 15,
|
||||||
|
FBP_DRONE = 30,
|
||||||
|
FBP_POSI = 25
|
||||||
|
)
|
||||||
|
|
||||||
|
/datum/event2/event/security_screening/set_up()
|
||||||
|
var/list/end_weights = list()
|
||||||
|
|
||||||
|
// First pass makes popular things more likely to get picked, e.g. 5 prommies vs 1 drone.
|
||||||
|
for(var/species_name in species_weights)
|
||||||
|
var/give_weight = 0
|
||||||
|
for(var/datum/data/record/R in data_core.general)
|
||||||
|
if(R.fields["species"] == species_name)
|
||||||
|
give_weight += species_weights[species_name]
|
||||||
|
|
||||||
|
end_weights[species_name] = give_weight
|
||||||
|
|
||||||
|
for(var/bot_type in synth_weights)
|
||||||
|
var/give_weight = 0
|
||||||
|
for(var/datum/data/record/R in data_core.general)
|
||||||
|
if(R.fields["brain_type"] == bot_type)
|
||||||
|
give_weight += synth_weights[bot_type]
|
||||||
|
|
||||||
|
end_weights[bot_type] = give_weight
|
||||||
|
|
||||||
|
// Second pass eliminates things that don't exist on the station.
|
||||||
|
// It's possible to choose something like drones when all the drones are AFK. This prevents that from happening.
|
||||||
|
while(end_weights.len) // Keep at it until we find someone or run out of possibilities.
|
||||||
|
var/victim_chosen = pickweight(end_weights)
|
||||||
|
|
||||||
|
if(victim_chosen in synth_weights)
|
||||||
|
if(metric.count_all_FBPs_of_kind(victim_chosen) > 0)
|
||||||
|
victim = victim_chosen
|
||||||
|
break
|
||||||
|
else
|
||||||
|
if(metric.count_all_of_specific_species(victim_chosen) > 0)
|
||||||
|
victim = victim_chosen
|
||||||
|
break
|
||||||
|
if(!victim)
|
||||||
|
end_weights -= victim_chosen
|
||||||
|
|
||||||
|
if(!victim)
|
||||||
|
log_debug("Security Screening event failed to find anyone to screen. Aborting.")
|
||||||
|
abort()
|
||||||
|
return
|
||||||
|
|
||||||
|
/datum/event2/event/security_screening/announce()
|
||||||
|
command_announcement.Announce("[pick("A nearby Navy vessel", "A Solar official", "A Vir-Gov official", "A NanoTrasen board director")] has \
|
||||||
|
requested the screening of [pick("every other", "every", "suspicious", "willing")] [victim] \
|
||||||
|
personnel onboard \the [location_name()].", "Security Advisement")
|
||||||
58
code/modules/gamemaster/event2/events/synthetic/ion_storm.dm
Normal file
58
code/modules/gamemaster/event2/events/synthetic/ion_storm.dm
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/datum/event2/meta/ion_storm
|
||||||
|
name = "ion storm"
|
||||||
|
departments = list(DEPARTMENT_SYNTHETIC)
|
||||||
|
chaos = 40
|
||||||
|
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||||
|
event_type = /datum/event2/event/ion_storm
|
||||||
|
|
||||||
|
/datum/event2/meta/ion_storm/get_weight()
|
||||||
|
var/bots = metric.count_people_in_department(DEPARTMENT_SYNTHETIC)
|
||||||
|
var/weight = 5 + (bots * 20)
|
||||||
|
return weight
|
||||||
|
|
||||||
|
|
||||||
|
/datum/event2/event/ion_storm
|
||||||
|
announce_delay_lower_bound = 7 MINUTES
|
||||||
|
announce_delay_upper_bound = 15 MINUTES
|
||||||
|
var/bot_emag_chance = 30 // This is rolled once, instead of once a second for a minute like the old version.
|
||||||
|
|
||||||
|
/datum/event2/event/ion_storm/start()
|
||||||
|
// Ion laws.
|
||||||
|
for(var/mob/living/silicon/target in silicon_mob_list)
|
||||||
|
if(target.z in get_location_z_levels())
|
||||||
|
// Don't ion law drons.
|
||||||
|
if(istype(target, /mob/living/silicon/robot/drone))
|
||||||
|
continue
|
||||||
|
|
||||||
|
// Or borgs with an AI (they'll get their AI's ion law anyways).
|
||||||
|
if(istype(target, /mob/living/silicon/robot))
|
||||||
|
var/mob/living/silicon/robot/R = target
|
||||||
|
if(R.connected_ai)
|
||||||
|
continue
|
||||||
|
|
||||||
|
// Crew member names, and excluding off station antags, are handled by `generate_ion_law()` automatically.
|
||||||
|
var/law = target.generate_ion_law()
|
||||||
|
target.add_ion_law(law)
|
||||||
|
target.show_laws()
|
||||||
|
|
||||||
|
// Emag bots.
|
||||||
|
for(var/mob/living/bot/B in mob_list)
|
||||||
|
if(B.z in get_location_z_levels())
|
||||||
|
if(prob(bot_emag_chance))
|
||||||
|
B.emag_act(1)
|
||||||
|
|
||||||
|
// Messaging server spam filters.
|
||||||
|
// This might be better served as a seperate event since it seems more like a hacker attack than a natural occurance.
|
||||||
|
if(message_servers)
|
||||||
|
for(var/obj/machinery/message_server/MS in message_servers)
|
||||||
|
if(MS.z in get_location_z_levels())
|
||||||
|
MS.spamfilter.Cut()
|
||||||
|
for (var/i = 1, i <= MS.spamfilter_limit, i++)
|
||||||
|
MS.spamfilter += pick("warble","help","almach","ai","liberty","freedom","drugs", "[using_map.station_short]", \
|
||||||
|
"admin","sol","security","meow","_","monkey","-","moron","pizza","message","spam",\
|
||||||
|
"director", "Hello", "Hi!"," ","nuke","crate","taj","xeno")
|
||||||
|
|
||||||
|
/datum/event2/event/ion_storm/announce()
|
||||||
|
if(prob(50))
|
||||||
|
command_announcement.Announce("An ion storm was detected within proximity to \the [location_name()]. \
|
||||||
|
Check all AI controlled equipment for corruption.", "Anomaly Alert", new_sound = 'sound/AI/ionstorm.ogg')
|
||||||
85
code/modules/gamemaster/event2/meta.dm
Normal file
85
code/modules/gamemaster/event2/meta.dm
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// The 'meta' object contains information about its assigned 'action' object, like what departments it will affect.
|
||||||
|
// It is directly held inside the Game Master Event System.
|
||||||
|
|
||||||
|
// The code for actually executing an event should go inside the event object instead.
|
||||||
|
/datum/event2/meta
|
||||||
|
// Name used for organization, shown in the debug verb for the GM system.
|
||||||
|
// If null, the meta event will be discarded when the GM system initializes, so it is safe to use nameless subtypes for inheritence.
|
||||||
|
var/name = null
|
||||||
|
|
||||||
|
// If FALSE, the GM system won't pick this.
|
||||||
|
// Some events set this to FALSE after running, to avoid running twice.
|
||||||
|
var/enabled = TRUE
|
||||||
|
|
||||||
|
// What departments the event attached might affect.
|
||||||
|
var/list/departments = list(DEPARTMENT_EVERYONE)
|
||||||
|
|
||||||
|
// A guess on how disruptive to a round the event might be. If the action is chosen, the GM's
|
||||||
|
// 'danger' score is increased by this number.
|
||||||
|
// Negative numbers could be used to signify helpful events.
|
||||||
|
var/chaos = 0
|
||||||
|
|
||||||
|
// A threshold the GM will use alongside its 'danger' score, to determine if it should pass
|
||||||
|
// over the event associated with this object. The decision is based on
|
||||||
|
var/chaotic_threshold = null
|
||||||
|
|
||||||
|
// If true, the event won't have it's `enabled` var set to FALSE when ran by the GM system.
|
||||||
|
var/reusable = FALSE
|
||||||
|
|
||||||
|
// A reference to the system that initialized us.
|
||||||
|
var/datum/controller/subsystem/game_master/GM = null
|
||||||
|
|
||||||
|
// The type path to the event associated with this meta object.
|
||||||
|
// When the GM chooses this event, a new instance is made.
|
||||||
|
// Seperate instances allow for multiple concurrent events without sharing state, e.g. two blobs.
|
||||||
|
var/event_type = null
|
||||||
|
|
||||||
|
/datum/event2/meta/New(datum/controller/subsystem/game_master/new_gm)
|
||||||
|
GM = new_gm
|
||||||
|
|
||||||
|
/datum/event2/meta/Destroy()
|
||||||
|
GM = null
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
|
||||||
|
// Called by the GM system to actually start an event,
|
||||||
|
// and makes it so events that should only be ran once are made to not be usable again.
|
||||||
|
/datum/event2/meta/proc/make_event()
|
||||||
|
var/datum/event2/event/E = new event_type(GM)
|
||||||
|
E.execute()
|
||||||
|
if(!reusable)
|
||||||
|
enabled = FALSE
|
||||||
|
return E
|
||||||
|
|
||||||
|
// Returns a TRUE or FALSE for if the GM system should be able to pick this event.
|
||||||
|
// Can be extended to check for more than just `enabled` later.
|
||||||
|
/datum/event2/meta/proc/can_pick()
|
||||||
|
return enabled
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Procs to Override
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Returns a number that determines how likely it is for the event to be picked over others.
|
||||||
|
// Individual events should override this for their own weights.
|
||||||
|
/datum/event2/meta/proc/get_weight()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
/datum/event2/meta/Topic(href, href_list)
|
||||||
|
if(..())
|
||||||
|
return
|
||||||
|
|
||||||
|
if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG))
|
||||||
|
message_admins("[usr] has attempted to manipulate an event without sufficent privilages.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if(href_list["force"])
|
||||||
|
SSevent_ticker.start_event(event_type)
|
||||||
|
message_admins("Event '[name]' was forced by [usr.key].")
|
||||||
|
|
||||||
|
if(href_list["toggle"])
|
||||||
|
enabled = !enabled
|
||||||
|
message_admins("Event '[name]' was toggled [enabled ? "on" : "off"] by [usr.key].")
|
||||||
|
|
||||||
|
SSgame_master.interact(usr) // To refresh the UI.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// 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
|
// 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.
|
// the round.
|
||||||
|
|
||||||
/datum/game_master
|
/datum/game_master_old
|
||||||
var/suspended = TRUE // If true, it will not do anything.
|
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/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/list/available_actions = list() // A list of 'actions' that the GM has access to, to spice up a round, such as events.
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
var/next_action = 0 // Minimum amount of time of nothingness until the GM can pick something again.
|
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.
|
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/game_master/New()
|
/datum/game_master_old/New()
|
||||||
..()
|
..()
|
||||||
available_actions = init_subtypes(/datum/gm_action)
|
available_actions = init_subtypes(/datum/gm_action)
|
||||||
for(var/datum/gm_action/action in available_actions)
|
for(var/datum/gm_action/action in available_actions)
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
else
|
else
|
||||||
sleep(30 SECONDS)
|
sleep(30 SECONDS)
|
||||||
|
|
||||||
/datum/game_master/process()
|
/datum/game_master_old/process()
|
||||||
if(ticker && ticker.current_state == GAME_STATE_PLAYING && !suspended)
|
if(ticker && ticker.current_state == GAME_STATE_PLAYING && !suspended)
|
||||||
adjust_staleness(1)
|
adjust_staleness(1)
|
||||||
adjust_danger(-1)
|
adjust_danger(-1)
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
start_action()
|
start_action()
|
||||||
|
|
||||||
// This is run before committing to an action/event.
|
// This is run before committing to an action/event.
|
||||||
/datum/game_master/proc/pre_action_checks()
|
/datum/game_master_old/proc/pre_action_checks()
|
||||||
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
|
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
|
||||||
log_debug("Game Master unable to start event: Ticker is nonexistant, or the game is not ongoing.")
|
log_debug("Game Master unable to start event: Ticker is nonexistant, or the game is not ongoing.")
|
||||||
return FALSE
|
return FALSE
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
return FALSE
|
return FALSE
|
||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
/datum/game_master/proc/start_action()
|
/datum/game_master_old/proc/start_action()
|
||||||
if(!pre_action_checks()) // Make sure we're not doing last minute events, or early events.
|
if(!pre_action_checks()) // Make sure we're not doing last minute events, or early events.
|
||||||
return
|
return
|
||||||
log_debug("Game Master now starting action decision.")
|
log_debug("Game Master now starting action decision.")
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
log_debug("[choice.name] was chosen by the Game Master, and is now being ran.")
|
log_debug("[choice.name] was chosen by the Game Master, and is now being ran.")
|
||||||
run_action(choice)
|
run_action(choice)
|
||||||
|
|
||||||
/datum/game_master/proc/run_action(var/datum/gm_action/action)
|
/datum/game_master_old/proc/run_action(var/datum/gm_action/action)
|
||||||
action.set_up()
|
action.set_up()
|
||||||
action.start()
|
action.start()
|
||||||
action.announce()
|
action.announce()
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
last_department_used = action.departments[1]
|
last_department_used = action.departments[1]
|
||||||
|
|
||||||
|
|
||||||
/datum/game_master/proc/decide_best_action(var/list/most_active_departments)
|
/datum/game_master_old/proc/decide_best_action(var/list/most_active_departments)
|
||||||
if(!most_active_departments.len) // Server's empty?
|
if(!most_active_departments.len) // Server's empty?
|
||||||
log_debug("Game Master failed to find any active departments.")
|
log_debug("Game Master failed to find any active departments.")
|
||||||
return list()
|
return list()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Tell the game master that something dangerous happened, e.g. someone dying.
|
// Tell the game master that something dangerous happened, e.g. someone dying.
|
||||||
/datum/game_master/proc/adjust_danger(var/amt)
|
/datum/game_master_old/proc/adjust_danger(var/amt)
|
||||||
amt = amt * danger_modifier
|
amt = amt * danger_modifier
|
||||||
danger = round( CLAMP(danger + amt, 0, 1000), 0.1)
|
danger = round( CLAMP(danger + amt, 0, 1000), 0.1)
|
||||||
|
|
||||||
// Tell the game master that something interesting happened.
|
// Tell the game master that something interesting happened.
|
||||||
/datum/game_master/proc/adjust_staleness(var/amt)
|
/datum/game_master_old/proc/adjust_staleness(var/amt)
|
||||||
amt = amt * staleness_modifier
|
amt = amt * staleness_modifier
|
||||||
staleness = round( CLAMP(staleness + amt, -50, 200), 0.1)
|
staleness = round( CLAMP(staleness + amt, -50, 200), 0.1)
|
||||||
@@ -21,3 +21,36 @@
|
|||||||
if(assess_player_activity(L) >= cutoff)
|
if(assess_player_activity(L) >= cutoff)
|
||||||
num++
|
num++
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
// Gives a count of how many human mobs of a specific species are on the station.
|
||||||
|
// Note that `ignore_synths` makes this proc ignore posibrains and drones, but NOT cyborgs, as they are still the same species in-universe.
|
||||||
|
/datum/metric/proc/count_all_of_specific_species(species_name, ignore_synths = TRUE, cutoff = 75, respect_z = TRUE)
|
||||||
|
var/num = 0
|
||||||
|
for(var/mob/living/carbon/human/H in player_list)
|
||||||
|
if(respect_z && !(H.z in using_map.station_levels))
|
||||||
|
continue
|
||||||
|
if(ignore_synths && H.isSynthetic() && H.get_FBP_type() != FBP_CYBORG)
|
||||||
|
continue
|
||||||
|
if(H.species.name == species_name)
|
||||||
|
if(assess_player_activity(H) >= cutoff)
|
||||||
|
num++
|
||||||
|
return num
|
||||||
|
|
||||||
|
// Gives a count of how many FBPs of a specific type there are on the station.
|
||||||
|
/datum/metric/proc/count_all_FBPs_of_kind(desired_FBP_class, cutoff = 75, respect_z = TRUE)
|
||||||
|
var/num = 0
|
||||||
|
for(var/mob/living/carbon/human/H in player_list)
|
||||||
|
if(respect_z && !(H.z in using_map.station_levels))
|
||||||
|
continue
|
||||||
|
if(H.get_FBP_type() != desired_FBP_class)
|
||||||
|
continue
|
||||||
|
if(assess_player_activity(H) >= cutoff)
|
||||||
|
num++
|
||||||
|
return num
|
||||||
|
|
||||||
|
// Like above, but for all FBPs.
|
||||||
|
/datum/metric/proc/count_all_FBPs(desired_FBP_class, cutoff = 75, respect_z = TRUE)
|
||||||
|
var/num = count_all_FBPs_of_kind(FBP_CYBORG, cutoff, respect_z)
|
||||||
|
num += count_all_FBPs_of_kind(FBP_POSI, cutoff, respect_z)
|
||||||
|
num += count_all_FBPs_of_kind(FBP_DRONE, cutoff, respect_z)
|
||||||
|
return num
|
||||||
@@ -81,6 +81,7 @@
|
|||||||
callHook("death", list(src, gibbed))
|
callHook("death", list(src, gibbed))
|
||||||
|
|
||||||
if(mind)
|
if(mind)
|
||||||
|
SSgame_master.adjust_danger(gibbed ? 40 : 20)
|
||||||
for(var/mob/observer/dead/O in mob_list)
|
for(var/mob/observer/dead/O in mob_list)
|
||||||
if(O.client && O.client.is_preference_enabled(/datum/client_preference/show_dsay))
|
if(O.client && O.client.is_preference_enabled(/datum/client_preference/show_dsay))
|
||||||
to_chat(O, "<span class='deadsay'><b>[src]</b> has died in <b>[get_area(src)]</b>. [ghost_follow_link(src, O)] </span>")
|
to_chat(O, "<span class='deadsay'><b>[src]</b> has died in <b>[get_area(src)]</b>. [ghost_follow_link(src, O)] </span>")
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
GLOBAL_LIST_EMPTY(all_turbines)
|
||||||
|
|
||||||
/obj/machinery/power/generator
|
/obj/machinery/power/generator
|
||||||
name = "thermoelectric generator"
|
name = "thermoelectric generator"
|
||||||
desc = "It's a high efficiency thermoelectric generator."
|
desc = "It's a high efficiency thermoelectric generator."
|
||||||
@@ -27,12 +29,14 @@
|
|||||||
/obj/machinery/power/generator/Initialize()
|
/obj/machinery/power/generator/Initialize()
|
||||||
soundloop = new(list(src), FALSE)
|
soundloop = new(list(src), FALSE)
|
||||||
desc = initial(desc) + " Rated for [round(max_power/1000)] kW."
|
desc = initial(desc) + " Rated for [round(max_power/1000)] kW."
|
||||||
|
GLOB.all_turbines += src
|
||||||
spawn(1)
|
spawn(1)
|
||||||
reconnect()
|
reconnect()
|
||||||
return ..()
|
return ..()
|
||||||
|
|
||||||
/obj/machinery/power/generator/Destroy()
|
/obj/machinery/power/generator/Destroy()
|
||||||
QDEL_NULL(soundloop)
|
QDEL_NULL(soundloop)
|
||||||
|
GLOB.all_turbines -= src
|
||||||
return ..()
|
return ..()
|
||||||
|
|
||||||
//generators connect in dir and reverse_dir(dir) directions
|
//generators connect in dir and reverse_dir(dir) directions
|
||||||
|
|||||||
65
polaris.dme
65
polaris.dme
@@ -203,6 +203,8 @@
|
|||||||
#include "code\controllers\subsystems\character_setup.dm"
|
#include "code\controllers\subsystems\character_setup.dm"
|
||||||
#include "code\controllers\subsystems\circuits.dm"
|
#include "code\controllers\subsystems\circuits.dm"
|
||||||
#include "code\controllers\subsystems\events.dm"
|
#include "code\controllers\subsystems\events.dm"
|
||||||
|
#include "code\controllers\subsystems\events2.dm"
|
||||||
|
#include "code\controllers\subsystems\game_master.dm"
|
||||||
#include "code\controllers\subsystems\garbage.dm"
|
#include "code\controllers\subsystems\garbage.dm"
|
||||||
#include "code\controllers\subsystems\holomaps.dm"
|
#include "code\controllers\subsystems\holomaps.dm"
|
||||||
#include "code\controllers\subsystems\inactivity.dm"
|
#include "code\controllers\subsystems\inactivity.dm"
|
||||||
@@ -1726,55 +1728,20 @@
|
|||||||
#include "code\modules\food\kitchen\cooking_machines\fryer.dm"
|
#include "code\modules\food\kitchen\cooking_machines\fryer.dm"
|
||||||
#include "code\modules\food\kitchen\cooking_machines\grill.dm"
|
#include "code\modules\food\kitchen\cooking_machines\grill.dm"
|
||||||
#include "code\modules\food\kitchen\cooking_machines\oven.dm"
|
#include "code\modules\food\kitchen\cooking_machines\oven.dm"
|
||||||
#include "code\modules\gamemaster\controller.dm"
|
|
||||||
#include "code\modules\gamemaster\defines.dm"
|
#include "code\modules\gamemaster\defines.dm"
|
||||||
#include "code\modules\gamemaster\game_master.dm"
|
#include "code\modules\gamemaster\event2\event.dm"
|
||||||
#include "code\modules\gamemaster\helpers.dm"
|
#include "code\modules\gamemaster\event2\meta.dm"
|
||||||
#include "code\modules\gamemaster\actions\action.dm"
|
#include "code\modules\gamemaster\event2\events\cargo\shipping_error.dm"
|
||||||
#include "code\modules\gamemaster\actions\atmos_leak.dm"
|
#include "code\modules\gamemaster\event2\events\engineering\camera_damage.dm"
|
||||||
#include "code\modules\gamemaster\actions\blob.dm"
|
#include "code\modules\gamemaster\event2\events\engineering\canister_leak.dm"
|
||||||
#include "code\modules\gamemaster\actions\brand_intelligence.dm"
|
#include "code\modules\gamemaster\event2\events\engineering\grid_check.dm"
|
||||||
#include "code\modules\gamemaster\actions\camera_damage.dm"
|
#include "code\modules\gamemaster\event2\events\engineering\spacevine.dm"
|
||||||
#include "code\modules\gamemaster\actions\canister_leak.dm"
|
#include "code\modules\gamemaster\event2\events\everyone\comms_blackout.dm"
|
||||||
#include "code\modules\gamemaster\actions\carp_migration.dm"
|
#include "code\modules\gamemaster\event2\events\everyone\solar_storm.dm"
|
||||||
#include "code\modules\gamemaster\actions\comms_blackout.dm"
|
#include "code\modules\gamemaster\event2\events\legacy\legacy.dm"
|
||||||
#include "code\modules\gamemaster\actions\drill_announcement.dm"
|
#include "code\modules\gamemaster\event2\events\security\drill_announcement.dm"
|
||||||
#include "code\modules\gamemaster\actions\dust.dm"
|
#include "code\modules\gamemaster\event2\events\security\security_advisement.dm"
|
||||||
#include "code\modules\gamemaster\actions\electrical_storm.dm"
|
#include "code\modules\gamemaster\event2\events\synthetic\ion_storm.dm"
|
||||||
#include "code\modules\gamemaster\actions\electrified_door.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\gravity.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\grid_check.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\infestation.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\ion_storm.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\manifest_malfunction.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\meteor_defense.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\money_hacker.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\money_lotto.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\money_spam.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\planet_weather_change.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\prison_break.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\radiation_storm.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\random_antagonist.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\rogue_drones.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\security_advisement.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\shipping_error.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\solar_storm.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\spacevine.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\spider_infestation.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\spontaneous_appendicitis.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\station_fundraise.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\stowaway.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\supply_conversion.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\supplyrequest.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\surprise_carp_attack.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\surprise_meteor.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\swarmboarder.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\viral_infection.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\viral_outbreak.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\wallrot.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\waste_disposal.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\window_break.dm"
|
|
||||||
#include "code\modules\gamemaster\actions\wormholes.dm"
|
|
||||||
#include "code\modules\games\cah.dm"
|
#include "code\modules\games\cah.dm"
|
||||||
#include "code\modules\games\cah_black_cards.dm"
|
#include "code\modules\games\cah_black_cards.dm"
|
||||||
#include "code\modules\games\cah_white_cards.dm"
|
#include "code\modules\games\cah_white_cards.dm"
|
||||||
@@ -2861,7 +2828,7 @@
|
|||||||
#include "code\ZAS\Zone.dm"
|
#include "code\ZAS\Zone.dm"
|
||||||
#include "interface\interface.dm"
|
#include "interface\interface.dm"
|
||||||
#include "interface\skin.dmf"
|
#include "interface\skin.dmf"
|
||||||
#include "maps\southern_cross\southern_cross.dm"
|
#include "maps\example\example.dm"
|
||||||
#include "maps\submaps\space_submaps\space.dm"
|
#include "maps\submaps\space_submaps\space.dm"
|
||||||
#include "maps\submaps\surface_submaps\mountains\mountains.dm"
|
#include "maps\submaps\surface_submaps\mountains\mountains.dm"
|
||||||
#include "maps\submaps\surface_submaps\mountains\mountains_areas.dm"
|
#include "maps\submaps\surface_submaps\mountains\mountains_areas.dm"
|
||||||
|
|||||||
Reference in New Issue
Block a user