mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-18 21:53:22 +00:00
357 lines
15 KiB
Plaintext
357 lines
15 KiB
Plaintext
#define RANDOM_EVENT_ADMIN_INTERVENTION_TIME (3 MINUTES) // SKYRAT EDIT CHANGE - ORIGINAL: #define RANDOM_EVENT_ADMIN_INTERVENTION_TIME (10 SECONDS)
|
|
|
|
//this singleton datum is used by the events controller to dictate how it selects events
|
|
/datum/round_event_control
|
|
var/name //The human-readable name of the event
|
|
var/category //The category of the event
|
|
var/description //The description of the event
|
|
var/typepath //The typepath of the event datum /datum/round_event
|
|
|
|
var/weight = 10 //The weight this event has in the random-selection process.
|
|
//Higher weights are more likely to be picked.
|
|
//10 is the default weight. 20 is twice more likely; 5 is half as likely as this default.
|
|
//0 here does NOT disable the event, it just makes it extremely unlikely
|
|
|
|
var/earliest_start = 20 MINUTES //The earliest world.time that an event can start (round-duration in deciseconds) default: 20 mins
|
|
var/min_players = 0 //The minimum amount of alive, non-AFK human players on server required to start the event.
|
|
|
|
var/occurrences = 0 //How many times this event has occurred
|
|
var/max_occurrences = 20 //The maximum number of times this event can occur (naturally), it can still be forced.
|
|
//By setting this to 0 you can effectively disable an event.
|
|
|
|
var/holidayID = "" //string which should be in the SSeventss.holidays list if you wish this event to be holiday-specific
|
|
//anything with a (non-null) holidayID which does not match holiday, cannot run.
|
|
var/wizardevent = FALSE
|
|
var/alert_observers = TRUE //should we let the ghosts know this event is firing
|
|
//should be disabled on events that fire a lot
|
|
|
|
/// Minimum wizard rituals at which to trigger this event, inclusive
|
|
var/min_wizard_trigger_potency = NEVER_TRIGGERED_BY_WIZARDS
|
|
/// Maximum wizard rituals at which to trigger this event, inclusive
|
|
var/max_wizard_trigger_potency = NEVER_TRIGGERED_BY_WIZARDS
|
|
|
|
var/triggering //admin cancellation
|
|
|
|
/// Whether or not dynamic should hijack this event
|
|
var/dynamic_should_hijack = FALSE
|
|
|
|
/// Datum that will handle admin options for forcing the event.
|
|
/// If there are no options, just leave it as an empty list.
|
|
var/list/datum/event_admin_setup/admin_setup = list()
|
|
/// Flags dictating whether this event should be run on certain kinds of map
|
|
var/map_flags = NONE
|
|
|
|
/datum/round_event_control/New()
|
|
if(config && !wizardevent) // Magic is unaffected by configs
|
|
earliest_start = CEILING(earliest_start * CONFIG_GET(number/events_min_time_mul), 1)
|
|
min_players = CEILING(min_players * CONFIG_GET(number/events_min_players_mul), 1)
|
|
if(!length(admin_setup))
|
|
return
|
|
var/list/admin_setup_types = admin_setup.Copy()
|
|
admin_setup.Cut()
|
|
for(var/admin_setup_type in admin_setup_types)
|
|
admin_setup += new admin_setup_type(src)
|
|
|
|
/datum/round_event_control/wizard
|
|
category = EVENT_CATEGORY_WIZARD
|
|
wizardevent = TRUE
|
|
|
|
/// Returns true if event can run in current map
|
|
/datum/round_event_control/proc/valid_for_map()
|
|
if (!map_flags)
|
|
return TRUE
|
|
if (SSmapping.is_planetary())
|
|
if (map_flags & EVENT_SPACE_ONLY)
|
|
return FALSE
|
|
else
|
|
if (map_flags & EVENT_PLANETARY_ONLY)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
// Checks if the event can be spawned. Used by event controller and "false alarm" event.
|
|
// Admin-created events override this.
|
|
/datum/round_event_control/proc/can_spawn_event(players_amt, allow_magic = FALSE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(occurrences >= max_occurrences)
|
|
return FALSE
|
|
if(!(roundstart ^ SSticker.HasRoundStarted())) // BUBBER EDIT: Roundstart checks added
|
|
return FALSE
|
|
if(weight == 0) // BUBBER EDIT: Weight check added
|
|
return FALSE
|
|
if(!allow_magic && wizardevent != SSevents.wizardmode)
|
|
return FALSE
|
|
if(players_amt < min_players)
|
|
return FALSE
|
|
if(holidayID && !check_holidays(holidayID))
|
|
return FALSE
|
|
if(EMERGENCY_ESCAPED_OR_ENDGAMED)
|
|
return FALSE
|
|
if(ispath(typepath, /datum/round_event/ghost_role) && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT))
|
|
return FALSE
|
|
|
|
if (dynamic_should_hijack && SSdynamic.random_event_hijacked != HIJACKED_NOTHING)
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/datum/round_event_control/proc/preRunEvent()
|
|
if(!ispath(typepath, /datum/round_event))
|
|
return EVENT_CANT_RUN
|
|
|
|
if (SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_RANDOM_EVENT, src) & CANCEL_PRE_RANDOM_EVENT)
|
|
return EVENT_INTERRUPTED
|
|
|
|
triggering = TRUE
|
|
|
|
// We sleep HERE, in pre-event setup (because there's no sense doing it in run_event() since the event is already running!) for the given amount of time to make an admin has enough time to cancel an event un-fitting of the present round.
|
|
|
|
// if(alert_observers) BUBBER EDIT REMOVAL - No effect
|
|
// message_admins("Random Event triggering in [DisplayTimeText(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)]: [name]. (<a href='?src=[REF(src)];cancel=1'>CANCEL</a>) (<a href='?src=[REF(src)];different_event=1'>SOMETHING ELSE</a>)") // SKYRAT EDIT REMOVAL
|
|
// sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME) // SKYRAT EDIT REMOVAL
|
|
|
|
// SKYRAT EDIT ADDITION BEGIN - Event notification Makes an attention-grabbing sound, gives admins two notifications spread over RANDOM_EVENT_ADMIN_INTERVENTION_TIME instead of just the one.
|
|
// SKYRAT EDIT ADDITION BEGIN - Event notification
|
|
// BUBBER EDIT START - Only delay on roundstart
|
|
|
|
// if(alert_observers)
|
|
// message_admins("Random Event triggering in [DisplayTimeText(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)]: [name]. (<a href='?src=[REF(src)];cancel=1'>CANCEL</a>) (<a href='?src=[REF(src)];different_event=1'>SOMETHING ELSE</a>)") // SKYRAT EDIT REMOVAL
|
|
// sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME) // SKYRAT EDIT REMOVAL
|
|
|
|
// SKYRAT EDIT ADDITION BEGIN - Event notification Makes an attention-grabbing sound, gives admins two notifications spread over RANDOM_EVENT_ADMIN_INTERVENTION_TIME instead of just the one.
|
|
// BUBBER EDIT START - Only delay on roundstart
|
|
/* if(!SSticker.HasRoundStarted()) // BUBBER EDIT - We only want ROUNDSTART DELAYS, not double delays!
|
|
message_admins("<font color='[COLOR_ADMIN_PINK]'>Random Event triggering in [DisplayTimeText(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)]: [name]. (\
|
|
<a href='?src=[REF(src)];cancel=1'>CANCEL</a> | \
|
|
<a href='?src=[REF(src)];something_else=1'>SOMETHING ELSE</a>)</font>")
|
|
for(var/client/staff as anything in GLOB.admins)
|
|
if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification))
|
|
SEND_SOUND(staff, sound('sound/misc/server-ready.ogg'))
|
|
sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME * 0.5)
|
|
|
|
if(triggering)
|
|
message_admins("<font color='[COLOR_ADMIN_PINK]'>Random Event triggering in [DisplayTimeText(RANDOM_EVENT_ADMIN_INTERVENTION_TIME * 0.5)]: [name]. (\
|
|
<a href='?src=[REF(src)];cancel=1'>CANCEL</a> | \
|
|
<a href='?src=[REF(src)];different_event=1'>SOMETHING ELSE</a></font>")
|
|
sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME * 0.5)
|
|
// SKYRAT EDIT ADDITION END - Event notification
|
|
var/players_amt = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE)
|
|
if(!can_spawn_event(players_amt))
|
|
message_admins("Second pre-condition check for [name] failed, rerolling...")
|
|
SSevents.spawnEvent(excluded_event = src)
|
|
return EVENT_INTERRUPTED */
|
|
if(!triggering)
|
|
return EVENT_CANCELLED //admin cancelled
|
|
triggering = FALSE
|
|
return EVENT_READY
|
|
|
|
|
|
/datum/round_event_control/Topic(href, href_list)
|
|
..()
|
|
if(href_list["cancel"])
|
|
if(!triggering)
|
|
to_chat(usr, span_admin("You are too late to cancel that event"))
|
|
return
|
|
triggering = FALSE
|
|
message_admins("[key_name_admin(usr)] cancelled event [name].")
|
|
log_admin_private("[key_name(usr)] cancelled event [name].")
|
|
SSblackbox.record_feedback("tally", "event_admin_cancelled", 1, typepath)
|
|
if(href_list["different_event"])
|
|
if(!triggering)
|
|
to_chat(usr, span_admin("Too late to change events now!"))
|
|
return
|
|
triggering = FALSE
|
|
message_admins("[key_name_admin(usr)] chose to have event [name] rolled into a different event.")
|
|
log_admin_private("[key_name(usr)] rerolled event [name].")
|
|
SSblackbox.record_feedback("tally", "event_admin_rerolled", 1, typepath)
|
|
SSevents.spawnEvent(excluded_event = src)
|
|
|
|
/*
|
|
Runs the event
|
|
* Arguments:
|
|
* - random: shows if the event was triggered randomly, or by on purpose by an admin or an item
|
|
* - announce_chance_override: if the value is not null, overrides the announcement chance when an admin calls an event
|
|
*/
|
|
/datum/round_event_control/proc/run_event(random = FALSE, announce_chance_override = null, admin_forced = FALSE, event_cause)
|
|
/*
|
|
* We clear our signals first so we don't cancel a wanted event by accident,
|
|
* the majority of time the admin will probably want to cancel a single midround spawned random events
|
|
* and not multiple events called by others admins
|
|
* * In the worst case scenario we can still recall a event which we cancelled by accident, which is much better then to have a unwanted event
|
|
*/
|
|
UnregisterSignal(SSdcs, COMSIG_GLOB_RANDOM_EVENT)
|
|
var/datum/round_event/round_event = new typepath(TRUE, src)
|
|
if(admin_forced && length(admin_setup))
|
|
//not part of the signal because it's conditional and relies on usr heavily
|
|
for(var/datum/event_admin_setup/admin_setup_datum in admin_setup)
|
|
admin_setup_datum.apply_to_event(round_event)
|
|
SEND_SIGNAL(src, COMSIG_CREATED_ROUND_EVENT, round_event)
|
|
round_event.setup()
|
|
round_event.current_players = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1)
|
|
occurrences++
|
|
|
|
SSevents.previously_run += src //SKYRAT EDIT ADDITION
|
|
|
|
if(announce_chance_override != null)
|
|
round_event.announce_chance = announce_chance_override
|
|
|
|
testing("[time2text(world.time, "hh:mm:ss")] [round_event.type]")
|
|
triggering = TRUE
|
|
|
|
if(!triggering)
|
|
RegisterSignal(SSdcs, COMSIG_GLOB_RANDOM_EVENT, PROC_REF(stop_random_event))
|
|
round_event.cancel_event = TRUE
|
|
return round_event
|
|
|
|
triggering = FALSE
|
|
log_game("[random ? "Random" : "Forced"] Event triggering: [name] ([typepath]).")
|
|
|
|
if(alert_observers)
|
|
round_event.announce_deadchat(random, event_cause)
|
|
|
|
SSblackbox.record_feedback("tally", "event_ran", 1, "[round_event]")
|
|
return round_event
|
|
|
|
//Returns the component for the listener
|
|
/datum/round_event_control/proc/stop_random_event()
|
|
SIGNAL_HANDLER
|
|
return CANCEL_RANDOM_EVENT
|
|
|
|
/datum/round_event //NOTE: Times are measured in master controller ticks!
|
|
var/processing = TRUE
|
|
var/datum/round_event_control/control
|
|
|
|
/// When in the lifetime to call start().
|
|
/// This is in seconds - so 1 = ~2 seconds in.
|
|
var/start_when = 0
|
|
/// When in the lifetime to call announce(). If you don't want it to announce use announce_chance, below.
|
|
/// This is in seconds - so 1 = ~2 seconds in.
|
|
var/announce_when = 0
|
|
/// Probability of announcing, used in prob(), 0 to 100, default 100. Called in process, and for a second time in the ion storm event.
|
|
var/announce_chance = 100
|
|
/// When in the lifetime the event should end.
|
|
/// This is in seconds - so 1 = ~2 seconds in.
|
|
var/end_when = 0
|
|
|
|
/// How long the event has existed. You don't need to change this.
|
|
var/activeFor = 0
|
|
/// Amount of of alive, non-AFK human players on server at the time of event start
|
|
var/current_players = 0
|
|
/// Can be faked by fake news event.
|
|
var/fakeable = TRUE
|
|
/// Whether a admin wants this event to be cancelled
|
|
var/cancel_event = FALSE
|
|
|
|
//Called first before processing.
|
|
//Allows you to setup your event, such as randomly
|
|
//setting the start_when and or announce_when variables.
|
|
//Only called once.
|
|
//EDIT: if there's anything you want to override within the new() call, it will not be overridden by the time this proc is called.
|
|
//It will only have been overridden by the time we get to announce() start() tick() or end() (anything but setup basically).
|
|
//This is really only for setting defaults which can be overridden later when New() finishes.
|
|
/datum/round_event/proc/setup()
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
return
|
|
|
|
///Announces the event name to deadchat, override this if what an event should show to deadchat is different to its event name.
|
|
/datum/round_event/proc/announce_deadchat(random, cause)
|
|
deadchat_broadcast(" has just been[random ? " randomly" : ""] triggered[cause ? " by [cause]" : ""]!", "<b>[control.name]</b>", message_type=DEADCHAT_ANNOUNCEMENT) //STOP ASSUMING IT'S BADMINS!
|
|
|
|
//Called when the tick is equal to the start_when variable.
|
|
//Allows you to start before announcing or vice versa.
|
|
//Only called once.
|
|
/datum/round_event/proc/start()
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
return
|
|
|
|
//Called after something followable has been spawned by an event
|
|
//Provides ghosts a follow link to an atom if possible
|
|
//Only called once.
|
|
/datum/round_event/proc/announce_to_ghosts(atom/atom_of_interest)
|
|
if(control.alert_observers)
|
|
if (atom_of_interest)
|
|
notify_ghosts(
|
|
"[control.name] has an object of interest: [atom_of_interest]!",
|
|
source = atom_of_interest,
|
|
)
|
|
return
|
|
|
|
//Called when the tick is equal to the announce_when variable.
|
|
//Allows you to announce before starting or vice versa.
|
|
//Only called once.
|
|
/datum/round_event/proc/announce(fake)
|
|
return
|
|
|
|
//Called on or after the tick counter is equal to start_when.
|
|
//You can include code related to your event or add your own
|
|
//time stamped events.
|
|
//Called more than once.
|
|
/datum/round_event/proc/tick()
|
|
return
|
|
|
|
//Called on or after the tick is equal or more than end_when
|
|
//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.
|
|
/datum/round_event/proc/end()
|
|
return
|
|
|
|
|
|
|
|
//Do not override this proc, instead use the appropriate procs.
|
|
//This proc will handle the calls to the appropriate procs.
|
|
/datum/round_event/process()
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
if(!processing)
|
|
return
|
|
|
|
if(SEND_GLOBAL_SIGNAL(COMSIG_GLOB_RANDOM_EVENT, src) & CANCEL_RANDOM_EVENT)
|
|
processing = FALSE
|
|
kill()
|
|
return
|
|
|
|
if(activeFor == start_when)
|
|
processing = FALSE
|
|
start()
|
|
processing = TRUE
|
|
|
|
if(activeFor == announce_when && prob(announce_chance))
|
|
processing = FALSE
|
|
announce(FALSE)
|
|
processing = TRUE
|
|
|
|
if(start_when < activeFor && activeFor < end_when)
|
|
processing = FALSE
|
|
tick()
|
|
processing = TRUE
|
|
|
|
if(activeFor == end_when)
|
|
processing = FALSE
|
|
end()
|
|
processing = TRUE
|
|
|
|
// Everything is done, let's clean up.
|
|
if(activeFor >= end_when && activeFor >= announce_when && activeFor >= start_when)
|
|
processing = FALSE
|
|
kill()
|
|
|
|
activeFor++
|
|
|
|
|
|
//Garbage collects the event by removing it from the global events list,
|
|
//which should be the only place it's referenced.
|
|
//Called when start(), announce() and end() has all been called.
|
|
/datum/round_event/proc/kill()
|
|
SSevents.running -= src
|
|
|
|
|
|
//Sets up the event then adds the event to the the list of running events
|
|
/datum/round_event/New(my_processing = TRUE, datum/round_event_control/event_controller)
|
|
control = event_controller
|
|
processing = my_processing
|
|
SSevents.running += src
|
|
return ..()
|
|
|
|
#undef RANDOM_EVENT_ADMIN_INTERVENTION_TIME
|