Files
Bubberstation/code/game/gamemodes/dynamic/ruleset_picking.dm
SkyratBot dcd7be8673 [MIRROR] Tweaks Dynamic Latejoin Ruleset Execution [MDB IGNORE] (#21188)
* Tweaks Dynamic Latejoin Ruleset Execution (#75428)

## About The Pull Request

### Before:

Dynamic wants a latejoin.

Dynamic will collate all latejoin rulesets that are eligible to execute.

The latejoin-er is guaranteed to become an antagonist, so long as any
ruleset is valid.

It runs down the list of rulesets and tries to apply it until it
succeeds

Then the latejoin injection timer is incremented for the next time.

<Details>

<Summary> This leads to the following:  </Summary>

![image](https://github.com/tgstation/tgstation/assets/51863163/904b81d2-033e-41d5-9073-860ccff8e6ca)

</Details>

### After ( with this pr ):

Dynamic wants a latejoin.

Dynamic will collate all latejoin rulesets that are eligible to execute.

Dynamic will then select **one** of the valid rulesets, based on weight,
to attempt to apply to the incoming latejoiner.

If the latejoiner is not a valid candidate, such as not having that
antagonist enabled in their preferences, it will not reroll the ruleset.
Do not pass go, do not collect 200 dollars, do not get antag.

The latejoin injection timer will not be incremented, so the very next
latejoin will be checked the same, until one eventually succeeds.

![image](https://github.com/tgstation/tgstation/assets/51863163/77194f37-0781-4c76-82b9-21693f7ff557)

## Why It's Good For The Game

Dynamic latejoin handling is kinda poor, I believe Mothblocks has talked
about this a bit before but hasn't done much to it yet.

Compared to roundstart and latejoin, which can select out of everyone
which has the preference enabled, latejoin rulesets are not spoiled for
choice, which makes weighting the rulesets useless.

If you only have traitor enabled, the only latejoin antag you can be is
traitor. And if dynamic wants a latejoin, while you can only be traitor,
**you will ALWAYS become a traitor**. The weight of the traitor ruleset
or any other ruleset doesn't matter, because at the end of the day, the
only valid ruleset is traitor.

This makes latejoins much less of a guaranteed thing, and will
(hopefully) spread it out a bit more, reducing likelihood of rarer
latejoin antags.

## Changelog

🆑 Melbert
balance: You are now slightly less likely, statistically, to get an
antag role on latejoin
fix: Fixed latejoin antags being logged as "midround" antags
/🆑

* Tweaks Dynamic Latejoin Ruleset Execution

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
2023-05-15 14:17:14 +01:00

159 lines
6.4 KiB
Plaintext

#define ADMIN_CANCEL_MIDROUND_TIME (180 SECONDS) //SKYRAT EDIT - ORIGINAL 10 SECONDS
///
///
/**
* From a list of rulesets, returns one based on weight and availability.
* Mutates the list that is passed into it to remove invalid rules.
*
* * max_allowed_attempts - Allows you to configure how many times the proc will attempt to pick a ruleset before giving up.
*/
/datum/game_mode/dynamic/proc/pick_ruleset(list/drafted_rules, max_allowed_attempts = INFINITY)
if (only_ruleset_executed)
log_dynamic("FAIL: only_ruleset_executed")
return null
if(!length(drafted_rules))
log_dynamic("FAIL: pick ruleset supplied with an empty list of drafted rules.")
return null
var/attempts = 0
while (attempts < max_allowed_attempts)
attempts++
var/datum/dynamic_ruleset/rule = pick_weight(drafted_rules)
if (!rule)
var/list/leftover_rules = list()
for (var/leftover_rule in drafted_rules)
leftover_rules += "[leftover_rule]"
log_dynamic("FAIL: No rulesets left to pick. Leftover rules: [leftover_rules.Join(", ")]")
return null
if (check_blocking(rule.blocking_rules, executed_rules))
log_dynamic("FAIL: [rule] can't execute as another rulset is blocking it.")
drafted_rules -= rule
if(drafted_rules.len <= 0)
return null
continue
else if (
rule.flags & HIGH_IMPACT_RULESET \
&& threat_level < GLOB.dynamic_stacking_limit \
&& GLOB.dynamic_no_stacking \
&& high_impact_ruleset_executed \
)
log_dynamic("FAIL: [rule] can't execute as a high impact ruleset was already executed.")
drafted_rules -= rule
if(drafted_rules.len <= 0)
return null
continue
return rule
return null
/// Executes a random midround ruleset from the list of drafted rules.
/datum/game_mode/dynamic/proc/pick_midround_rule(list/drafted_rules, description)
log_dynamic("Rolling [drafted_rules.len] [description]")
var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules)
if (isnull(rule))
return null
current_midround_rulesets = drafted_rules - rule
midround_injection_timer_id = addtimer(
CALLBACK(src, PROC_REF(execute_midround_rule), rule), \
ADMIN_CANCEL_MIDROUND_TIME, \
TIMER_STOPPABLE, \
)
// SKYRAT EDIT REMOVAL BEGIN - Event notification
/**
log_dynamic("[rule] ruleset executing...")
message_admins("DYNAMIC: Executing midround ruleset [rule] in [DisplayTimeText(ADMIN_CANCEL_MIDROUND_TIME)]. \
<a href='?src=[REF(src)];cancelmidround=[midround_injection_timer_id]'>CANCEL</a> | \
<a href='?src=[REF(src)];differentmidround=[midround_injection_timer_id]'>SOMETHING ELSE</a>")
return rule
*/
// SKYRAT EDIT REMOVAL END - Event notification
// SKYRAT EDIT ADDITION BEGIN - Event notification
message_admins("<font color='[COLOR_ADMIN_PINK]'>Dynamic Event triggering in [DisplayTimeText(ADMIN_CANCEL_MIDROUND_TIME)]: [rule]. (\
<a href='?src=[REF(src)];cancelmidround=[midround_injection_timer_id]'>CANCEL</a> | \
<a href='?src=[REF(src)];differentmidround=[midround_injection_timer_id]'>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(ADMIN_CANCEL_MIDROUND_TIME * 0.5)
if(!midround_injection_timer_id == null)
message_admins("<font color='[COLOR_ADMIN_PINK]'>Dynamic Event triggering in [DisplayTimeText(ADMIN_CANCEL_MIDROUND_TIME * 0.5)]: [rule]. (\
<a href='?src=[REF(src)];cancelmidround=[midround_injection_timer_id]'>CANCEL</a> | \
<a href='?src=[REF(src)];differentmidround=[midround_injection_timer_id]'>SOMETHING ELSE</a>)</font>")
return rule
// SKYRAT EDIT ADDITION END - Event notification
/// Fired after admins do not cancel a midround injection.
/datum/game_mode/dynamic/proc/execute_midround_rule(datum/dynamic_ruleset/rule)
current_midround_rulesets = null
midround_injection_timer_id = null
if (!rule.repeatable)
midround_rules = remove_from_list(midround_rules, rule.type)
addtimer(CALLBACK(src, PROC_REF(execute_midround_latejoin_rule), rule), rule.delay)
/// Mainly here to facilitate delayed rulesets. All midround/latejoin rulesets are executed with a timered callback to this proc.
/datum/game_mode/dynamic/proc/execute_midround_latejoin_rule(sent_rule)
var/datum/dynamic_ruleset/rule = sent_rule
spend_midround_budget(rule.cost, threat_log, "[worldtime2text()]: [rule.ruletype] [rule.name]")
rule.pre_execute(GLOB.alive_player_list.len)
if (rule.execute())
log_dynamic("Injected a [rule.ruletype] ruleset [rule.name].")
if(rule.flags & HIGH_IMPACT_RULESET)
high_impact_ruleset_executed = TRUE
else if(rule.flags & ONLY_RULESET)
only_ruleset_executed = TRUE
if(rule.ruletype == LATEJOIN_RULESET)
var/mob/M = pick(rule.candidates)
message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
log_dynamic("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
executed_rules += rule
rule.candidates.Cut()
if (rule.persistent)
current_rules += rule
new_snapshot(rule)
return TRUE
rule.clean_up()
stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
return FALSE
/// Fired when an admin cancels the current midround injection.
/datum/game_mode/dynamic/proc/admin_cancel_midround(mob/user, timer_id)
if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id))
to_chat(user, span_notice("Too late!"))
return
log_admin("[key_name(user)] cancelled the next midround injection.")
message_admins("[key_name(user)] cancelled the next midround injection.")
midround_injection_timer_id = null
current_midround_rulesets = null
/// Fired when an admin requests a different midround injection.
/datum/game_mode/dynamic/proc/admin_different_midround(mob/user, timer_id)
if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id))
to_chat(user, span_notice("Too late!"))
return
midround_injection_timer_id = null
if (isnull(current_midround_rulesets) || current_midround_rulesets.len == 0)
log_admin("[key_name(user)] asked for a different midround injection, but there were none left.")
message_admins("[key_name(user)] asked for a different midround injection, but there were none left.")
return
log_admin("[key_name(user)] asked for a different midround injection.")
message_admins("[key_name(user)] asked for a different midround injection.")
pick_midround_rule(current_midround_rulesets, "different midround rulesets")
#undef ADMIN_CANCEL_MIDROUND_TIME