mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2025-12-16 13:12:22 +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_AI -22
|
||||
#define INIT_ORDER_JOB -23
|
||||
#define INIT_ORDER_GAME_MASTER -24
|
||||
|
||||
|
||||
// Subsystem fire priority, from lowest to highest priority
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SUBSYSTEM_DEF(events)
|
||||
name = "Events"
|
||||
name = "Events (Legacy)"
|
||||
wait = 20
|
||||
|
||||
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.Weaken(rand(10,25))
|
||||
M.updatehealth()
|
||||
SSgame_master.adjust_danger(-20)
|
||||
apply_brain_damage(M, 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
|
||||
if(blob_core)
|
||||
blob_core.update_icon()
|
||||
level_seven_blob_announcement(blob_core)
|
||||
|
||||
..(newloc)
|
||||
|
||||
|
||||
@@ -125,18 +125,20 @@
|
||||
activeFor++
|
||||
|
||||
//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(isRunning)
|
||||
isRunning = 0
|
||||
end()
|
||||
|
||||
endedAt = world.time
|
||||
if(!external_use)
|
||||
SSevents.active_events -= 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
|
||||
if(!external_use)
|
||||
SSevents.active_events += src
|
||||
|
||||
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/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)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//var/global/account_hack_attempted = 0
|
||||
|
||||
GLOBAL_VAR_INIT(account_hack_attempted, 0)
|
||||
|
||||
/datum/event/money_hacker
|
||||
var/datum/money_account/affected_account
|
||||
endWhen = 100
|
||||
@@ -10,7 +12,7 @@
|
||||
if(all_money_accounts.len)
|
||||
affected_account = pick(all_money_accounts)
|
||||
|
||||
account_hack_attempted = 1
|
||||
GLOB.account_hack_attempted = 1
|
||||
else
|
||||
kill()
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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/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/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.
|
||||
@@ -25,3 +26,24 @@
|
||||
|
||||
/datum/gm_action/proc/announce()
|
||||
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
|
||||
name = "rogue drones"
|
||||
departments = list(DEPARTMENT_SECURITY)
|
||||
chaotic = 60
|
||||
chaotic = 20
|
||||
chaos_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
length = 20 MINUTES
|
||||
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")
|
||||
|
||||
/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"
|
||||
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL, DEPARTMENT_EVERYONE)
|
||||
chaotic = 30
|
||||
chaos_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
|
||||
severity = 1
|
||||
|
||||
@@ -26,11 +27,13 @@
|
||||
spawncount = rand(4 * severity, 6 * severity)
|
||||
|
||||
/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)
|
||||
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()
|
||||
..()
|
||||
@@ -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_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
|
||||
// the round.
|
||||
|
||||
/datum/game_master
|
||||
/datum/game_master_old
|
||||
var/suspended = TRUE // If true, it will not do anything.
|
||||
var/ignore_time_restrictions = FALSE// Useful for debugging without needing to wait 20 minutes each time.
|
||||
var/list/available_actions = list() // A list of 'actions' that the GM has access to, to spice up a round, such as events.
|
||||
@@ -15,7 +15,7 @@
|
||||
var/next_action = 0 // Minimum amount of time of nothingness until the GM can pick something again.
|
||||
var/last_department_used = null // If an event was done for a specific department, it is written here, so it doesn't do it again.
|
||||
|
||||
/datum/game_master/New()
|
||||
/datum/game_master_old/New()
|
||||
..()
|
||||
available_actions = init_subtypes(/datum/gm_action)
|
||||
for(var/datum/gm_action/action in available_actions)
|
||||
@@ -32,7 +32,7 @@
|
||||
else
|
||||
sleep(30 SECONDS)
|
||||
|
||||
/datum/game_master/process()
|
||||
/datum/game_master_old/process()
|
||||
if(ticker && ticker.current_state == GAME_STATE_PLAYING && !suspended)
|
||||
adjust_staleness(1)
|
||||
adjust_danger(-1)
|
||||
@@ -49,7 +49,7 @@
|
||||
start_action()
|
||||
|
||||
// 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)
|
||||
log_debug("Game Master unable to start event: Ticker is nonexistant, or the game is not ongoing.")
|
||||
return FALSE
|
||||
@@ -73,7 +73,7 @@
|
||||
return FALSE
|
||||
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.
|
||||
return
|
||||
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.")
|
||||
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.start()
|
||||
action.announce()
|
||||
@@ -105,7 +105,7 @@
|
||||
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?
|
||||
log_debug("Game Master failed to find any active departments.")
|
||||
return list()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// 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
|
||||
danger = round( CLAMP(danger + amt, 0, 1000), 0.1)
|
||||
|
||||
// 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
|
||||
staleness = round( CLAMP(staleness + amt, -50, 200), 0.1)
|
||||
@@ -21,3 +21,36 @@
|
||||
if(assess_player_activity(L) >= cutoff)
|
||||
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))
|
||||
|
||||
if(mind)
|
||||
SSgame_master.adjust_danger(gibbed ? 40 : 20)
|
||||
for(var/mob/observer/dead/O in mob_list)
|
||||
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>")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
GLOBAL_LIST_EMPTY(all_turbines)
|
||||
|
||||
/obj/machinery/power/generator
|
||||
name = "thermoelectric generator"
|
||||
desc = "It's a high efficiency thermoelectric generator."
|
||||
@@ -27,12 +29,14 @@
|
||||
/obj/machinery/power/generator/Initialize()
|
||||
soundloop = new(list(src), FALSE)
|
||||
desc = initial(desc) + " Rated for [round(max_power/1000)] kW."
|
||||
GLOB.all_turbines += src
|
||||
spawn(1)
|
||||
reconnect()
|
||||
return ..()
|
||||
|
||||
/obj/machinery/power/generator/Destroy()
|
||||
QDEL_NULL(soundloop)
|
||||
GLOB.all_turbines -= src
|
||||
return ..()
|
||||
|
||||
//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\circuits.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\holomaps.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\grill.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\game_master.dm"
|
||||
#include "code\modules\gamemaster\helpers.dm"
|
||||
#include "code\modules\gamemaster\actions\action.dm"
|
||||
#include "code\modules\gamemaster\actions\atmos_leak.dm"
|
||||
#include "code\modules\gamemaster\actions\blob.dm"
|
||||
#include "code\modules\gamemaster\actions\brand_intelligence.dm"
|
||||
#include "code\modules\gamemaster\actions\camera_damage.dm"
|
||||
#include "code\modules\gamemaster\actions\canister_leak.dm"
|
||||
#include "code\modules\gamemaster\actions\carp_migration.dm"
|
||||
#include "code\modules\gamemaster\actions\comms_blackout.dm"
|
||||
#include "code\modules\gamemaster\actions\drill_announcement.dm"
|
||||
#include "code\modules\gamemaster\actions\dust.dm"
|
||||
#include "code\modules\gamemaster\actions\electrical_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\gamemaster\event2\event.dm"
|
||||
#include "code\modules\gamemaster\event2\meta.dm"
|
||||
#include "code\modules\gamemaster\event2\events\cargo\shipping_error.dm"
|
||||
#include "code\modules\gamemaster\event2\events\engineering\camera_damage.dm"
|
||||
#include "code\modules\gamemaster\event2\events\engineering\canister_leak.dm"
|
||||
#include "code\modules\gamemaster\event2\events\engineering\grid_check.dm"
|
||||
#include "code\modules\gamemaster\event2\events\engineering\spacevine.dm"
|
||||
#include "code\modules\gamemaster\event2\events\everyone\comms_blackout.dm"
|
||||
#include "code\modules\gamemaster\event2\events\everyone\solar_storm.dm"
|
||||
#include "code\modules\gamemaster\event2\events\legacy\legacy.dm"
|
||||
#include "code\modules\gamemaster\event2\events\security\drill_announcement.dm"
|
||||
#include "code\modules\gamemaster\event2\events\security\security_advisement.dm"
|
||||
#include "code\modules\gamemaster\event2\events\synthetic\ion_storm.dm"
|
||||
#include "code\modules\games\cah.dm"
|
||||
#include "code\modules\games\cah_black_cards.dm"
|
||||
#include "code\modules\games\cah_white_cards.dm"
|
||||
@@ -2861,7 +2828,7 @@
|
||||
#include "code\ZAS\Zone.dm"
|
||||
#include "interface\interface.dm"
|
||||
#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\surface_submaps\mountains\mountains.dm"
|
||||
#include "maps\submaps\surface_submaps\mountains\mountains_areas.dm"
|
||||
|
||||
Reference in New Issue
Block a user