Files
Aurora.3/code/modules/events/event.dm
Batrachophreno 4a63be9448 More Event Variations, Event Balance (#20813)
Some functional updates to events:
**Comms Blackouts:** Now has a variant (min. 6 pop,
engineering-weighted) which gives each machine a 10% chance to take a
little integrity damage. Additionally, Telecomms Processors now factor
their integrity damage into their ability to decompress messages, so
this alternative comms blackout might cause radio transmissions (either
global or for a given department) to start getting a little garbled- not
enough to genuinely impede comms, but enough to justify Engi to fix them
up.

**Prison Break:** Given how rarely we actually have people sitting
around in the prison waiting for breakouts over the course of a given
round, this event has been changed to be more akin to the existing
Containment events- the entire department (minus armory, nuke storage,
checkpoints, etc.) are at risk of lights blown, access issues, etc. Just
a little more variety added to the mix.

**Space Vines:** Minimum 4 pop to fire. Shouldn't matter much in the
grand scheme of things, just reduces annoyance factor for extremely
low-pop if they roll some vines that fuck them up. Will look into
refactoring that event so that the danger factor of the vines increases
with population, and remove the min pop requirement again.

**Drone Revolution:** Minimum 4 pop to fire. Again, shouldn't matter
much in the grand scheme of things but this highest tier event will
seriously fuck up anyone who latejoins and uses the maints to, say, help
set the ship up during low pop! No I'm not salty!

**Electrical Storm:** Refactored this and restored the Mundane and Major
variants. While we want this event to keep Engineers on their toes and
provide openings for people to break regs if they so wished, most of the
event is theater (lots of flickering lights now!) While the Mundane and
Moderate variants are mostly flavorful and will just give Engineers a
few excuses to visit different departments and RP with people, Major
events will be your 'all hands on deck' affairs (as Major events are
ought) and in very rare circumstances could even cascade into more
serious issues. Did a ton of unit testing on this and feel very good
about how each variant actually looks, will make any additional tweaks
if needed once we see how it plays with an actual population!

The Event Probabilities sheet was also very out of date, missing Comet
Expulsion, had the old Viral Outbreak, lots of old values, etc. Updated
all the events, and also updated the % formulas to factor in minimum
population- changing the 'Assumed Personnel' values at the top to a
level below the minimum population for a given event will now correctly
set its probability to 0%, making it clearer what actual odds are given
different pop levels.

I also don't think pop_needed was working correctly: various changes in
there to get it behaving.

Obv this PR grew in scope from outset and touches a small handful of
different things, so lmk anything to mix and match and change and drop.
!review
2025-06-22 18:09:11 +00:00

295 lines
8.3 KiB
Plaintext

/datum/event_meta
var/name = ""
///Whether or not the event is available for random selection at all
var/enabled = TRUE
///The base weight of this event. A zero means it may never fire, but see get_weight()
var/weight = 0
///The minimum weight that this event will have. Only used if non-zero
var/min_weight = 0
///The maximum weight that this event will have. Only use if non-zero
var/max_weight = 0
///The current severity of this event
var/severity = 0
///If TRUE, then the event will not be re-added to the list of available events
var/one_shot = FALSE
///If TRUE, add back to the queue of events upon finishing
var/add_to_queue = TRUE
var/list/role_weights = list()
///Minimum amount of jobs required for the event to fire
var/list/minimum_job_requirement = list()
///Minimum amount of player_list mobs for this to fire
var/pop_requirement = 1
/// A lazylist of gamemodes during which this event won't fire
var/list/excluded_gamemodes
var/datum/event/event_type
/datum/event_meta/New(event_severity, event_name, datum/event/type, event_weight, list/job_weights,
is_one_shot = FALSE, min_event_weight = 0, max_event_weight = 0, list/excluded_roundtypes,
add_to_queue = TRUE, list/minimum_job_requirement_list, pop_needed = 1)
name = event_name
severity = event_severity
event_type = type
one_shot = is_one_shot
weight = event_weight
min_weight = min_event_weight
max_weight = max_event_weight
src.add_to_queue = add_to_queue
pop_requirement = pop_needed
if(job_weights)
role_weights = job_weights
if(minimum_job_requirement_list)
minimum_job_requirement = minimum_job_requirement_list
if(excluded_roundtypes)
excluded_gamemodes = excluded_roundtypes
/datum/event_meta/proc/get_weight(list/active_with_role)
if(!enabled)
return 0
var/n = 0
for (var/mob/living in GLOB.player_list)
n++
if(n <= pop_requirement)
return 0
if(LAZYISIN(excluded_gamemodes, SSticker.mode.name))
// There's no way it'll be run this round anyways.
enabled = FALSE
return 0
var/job_weight = 0
var/minimum_met = TRUE
if(minimum_job_requirement)
for(var/role in minimum_job_requirement)
if(active_with_role[role] >= minimum_job_requirement[role])
minimum_met = TRUE
else
minimum_met = FALSE
if(minimum_met)
for(var/role in role_weights)
if(role in active_with_role)
job_weight += active_with_role[role] * role_weights[role]
var/total_weight = weight + job_weight
// Only min/max the weight if the values are non-zero
if(min_weight && total_weight < min_weight) total_weight = min_weight
if(max_weight && total_weight > max_weight) total_weight = max_weight
return total_weight
/datum/event //NOTE: Times are measured in master controller ticks!
///When in the lifetime to call start()
var/startWhen = 0
///When in the lifetime to call announce()
var/announceWhen = 0
///When in the lifetime the event should end
var/endWhen = 0
///Severity. Lower means less severe, higher means more severe. Does not have to be supported. Is set on New()
var/severity = 0
///How long the event has existed. You don't need to change this
var/activeFor = 0
///If this event is currently running. You should not change this
var/isRunning = TRUE
///When this event started
var/startedAt = 0
///When this event ended
var/endedAt = 0
var/datum/event_meta/event_meta = null
var/list/affecting_z
///If set to TRUE, this event will not be picked for false announcements
///This should really only be used for events that have no announcement
var/no_fake = FALSE
///A lore-suitable name that maintains the mystery, used for faking events
var/ic_name = null
///If TRUE, this event is a dummy instance used for retrieving values, it should not run or add/remove itself from any lists
var/dummy = FALSE
///used for events that run secondary announcements, like releasing maint access
var/two_part = FALSE
var/has_skybox_image = FALSE
var/obj/effect/overmap/visitable/ship/affected_ship
var/announce_to_sensor_console = FALSE
/datum/event/nothing
no_fake = 1
///Called first before processing.
///Allows you to setup your event, such as randomly
///setting the startWhen and or announceWhen variables.
///Only called once.
/datum/event/proc/setup()
return
///Called when the tick is equal to the startWhen variable.
///Allows you to start before announcing or vice versa.
///Only called once.
/datum/event/proc/start()
SHOULD_CALL_PARENT(TRUE)
if(has_skybox_image)
SSskybox.rebuild_skyboxes(affecting_z)
announce_start()
///Called when the tick is equal to the announceWhen variable.
///Allows you to announce before starting or vice versa.
///Only called once.
/datum/event/proc/announce()
return
/datum/event/proc/announce_start()
if(announce_to_sensor_console)
send_sensor_message("Entering [ic_name].")
return FALSE
return TRUE
/datum/event/proc/announce_end(var/faked)
if(announce_to_sensor_console)
send_sensor_message("Exiting [ic_name].")
return FALSE
return TRUE
///Called on or after the tick counter is equal to startWhen.
///You can include code related to your event or add your own
///time stamped events.
///Called more than once.
/datum/event/proc/tick()
return
///Called on or after the tick is equal or more than endWhen
///You can include code related to the event ending.
///Do not place spawn() in here, instead use tick() to check for
///the activeFor variable.
///For example: if(activeFor == myOwnVariable + 30) doStuff()
///Only called once.
///faked indicates this is a false alarm. Used to prevent announcements and other things from happening during false alarms.
/datum/event/proc/end(var/faked)
SHOULD_CALL_PARENT(TRUE)
announce_end(faked)
///Returns the latest point of event processing
/datum/event/proc/lastProcessAt()
return max(startWhen, max(announceWhen, endWhen))
///Do not override this proc, instead use the appropiate procs
///This proc will handle the calls to the appropiate procs
/datum/event/process()
SHOULD_NOT_OVERRIDE(TRUE)
if(activeFor > startWhen && activeFor < endWhen)
tick()
if(activeFor == startWhen)
isRunning = 1
start()
if(activeFor == announceWhen)
announce()
if(activeFor == endWhen)
isRunning = 0
end()
// Everything is done, let's clean up.
if(activeFor >= lastProcessAt())
kill()
activeFor++
///Called when start(), announce() and end() has all been called
/datum/event/proc/kill(failed_to_spawn = FALSE)
// If this event was forcefully killed run end() for individual cleanup
if(!dummy && isRunning)
end()
if(failed_to_spawn)
var/datum/event_container/killed_ec = SSevents.event_containers[severity]
killed_ec.start_event()
isRunning = 0
endedAt = world.time
if(has_skybox_image)
SSskybox.rebuild_skyboxes(affecting_z)
if(!dummy)
SSevents.active_events -= src
SSevents.event_complete(src)
/datum/event/New(datum/event_meta/EM = null, is_dummy = 0, obj/effect/overmap/visitable/ship/overmap_ship, obj/effect/overmap/event/overmap_hazard)
dummy = is_dummy
event_meta = EM
if (event_meta)
severity = event_meta.severity
if(severity < EVENT_LEVEL_MUNDANE) severity = EVENT_LEVEL_MUNDANE
if(severity > EVENT_LEVEL_MAJOR) severity = EVENT_LEVEL_MAJOR
else
severity = EVENT_LEVEL_MODERATE//Fixes runtime errors with admin triggered events
if (dummy)
return
setup()
if(overmap_ship && overmap_hazard)
setup_for_overmap(overmap_ship, overmap_hazard)
if(!affecting_z)
affecting_z = SSmapping.levels_by_trait(ZTRAIT_STATION)
// event needs to be responsible for this, as stuff like APLUs currently make their own events for curious reasons
SSevents.active_events += src
startedAt = world.time
..()
/datum/event/proc/location_name()
if(!SSatlas.current_map.use_overmap)
return station_name()
var/obj/effect/overmap/O = GLOB.map_sectors["[pick(affecting_z)]"]
return O ? O.name : "Unknown Location"
/datum/event/proc/get_skybox_image()
return
/datum/event/proc/setup_for_overmap(obj/effect/overmap/visitable/ship/ship, obj/effect/overmap/event/hazard)
startWhen = 0
endWhen = INFINITY
affecting_z = ship.map_z
affected_ship = ship
announce_to_sensor_console = istype(ship, /obj/effect/overmap/visitable/ship/landable)
if(announce_to_sensor_console)
announceWhen = -1
ic_name = hazard.name
/datum/event/proc/send_sensor_message(message)
for(var/obj/machinery/computer/ship/sensors/console in affected_ship.consoles)
console.display_message(message)