Implements dynamic scaling for spies (#82961)

## About The Pull Request

Implements dynamic scaling for spies.

TL;DR On average less spawn, but there is a chance it scales up to add
even more.

Old

![image](https://github.com/tgstation/tgstation/assets/51863163/5ecdf146-7922-42ea-8051-8a30cddde7a6)

New

![image](https://github.com/tgstation/tgstation/assets/51863163/ce9f51a1-4d99-4fe7-be65-32003fbec605)

## Why It's Good For The Game

Maybe more dynamic rounds? Just for testing really

## Changelog

🆑 Melbert
qol: Spies may spawn in less numbers, but rarely may also spawn in more
numbers.
/🆑
This commit is contained in:
MrMelbert
2024-05-08 17:22:25 -04:00
committed by SkyratBot
parent 32e27e1193
commit f79cf4ea68
2 changed files with 29 additions and 17 deletions
@@ -140,28 +140,41 @@
/// This function is here to ensure the antag ratio is kept under control while scaling up.
/// Returns how much threat to actually spend in the end.
/datum/dynamic_ruleset/proc/scale_up(population, max_scale)
SHOULD_NOT_OVERRIDE(TRUE)
if (!scaling_cost)
return 0
var/antag_fraction = 0
for(var/_ruleset in (SSdynamic.executed_rules + list(src))) // we care about the antags we *will* assign, too
var/datum/dynamic_ruleset/ruleset = _ruleset
antag_fraction += ((1 + ruleset.scaled_times) * ruleset.get_antag_cap(population)) / SSdynamic.roundstart_pop_ready
for(var/datum/dynamic_ruleset/ruleset as anything in (SSdynamic.executed_rules + list(src))) // we care about the antags we *will* assign, too
antag_fraction += ruleset.get_antag_cap_scaling_included(population) / SSdynamic.roundstart_pop_ready
for(var/i in 1 to max_scale)
if(antag_fraction < 0.25)
scaled_times += 1
antag_fraction += get_antag_cap(population) / SSdynamic.roundstart_pop_ready // we added new antags, gotta update the %
antag_fraction += get_scaling_antag_cap(population) / SSdynamic.roundstart_pop_ready // we added new antags, gotta update the %
return scaled_times * scaling_cost
/// Returns how many more antags to add while scaling with a given population.
/// By default rulesets scale linearly, but you can override this to make them scale differently.
/datum/dynamic_ruleset/proc/get_scaling_antag_cap(population)
return get_antag_cap(population)
/// Returns what the antag cap with the given population is.
/datum/dynamic_ruleset/proc/get_antag_cap(population)
SHOULD_NOT_OVERRIDE(TRUE)
if (isnum(antag_cap))
return antag_cap
return CEILING(population / antag_cap["denominator"], 1) + (antag_cap["offset"] || 0)
/// Gets the 'final' antag cap for this ruleset, which is the base cap plus the scaled cap.
/datum/dynamic_ruleset/proc/get_antag_cap_scaling_included(population)
SHOULD_NOT_OVERRIDE(TRUE)
var/base_cap = get_antag_cap(population)
var/modded_cap = scaled_times * get_scaling_antag_cap(population)
return base_cap + modded_cap
/// This is called if persistent variable is true everytime SSTicker ticks.
/datum/dynamic_ruleset/proc/rule_process()
return
@@ -33,8 +33,7 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
/datum/dynamic_ruleset/roundstart/traitor/pre_execute(population)
. = ..()
var/num_traitors = get_antag_cap(population) * (scaled_times + 1)
for (var/i = 1 to num_traitors)
for (var/i in 1 to get_antag_cap_scaling_included(population))
if(candidates.len <= 0)
break
var/mob/M = pick_n_take(candidates)
@@ -121,7 +120,7 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
/datum/dynamic_ruleset/roundstart/traitorbro/pre_execute(population)
. = ..()
for (var/_ in 1 to get_antag_cap(population) * (scaled_times + 1))
for (var/i in 1 to get_antag_cap_scaling_included(population))
var/mob/candidate = pick_n_take(candidates)
if (isnull(candidate))
break
@@ -171,8 +170,7 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
/datum/dynamic_ruleset/roundstart/changeling/pre_execute(population)
. = ..()
var/num_changelings = get_antag_cap(population) * (scaled_times + 1)
for (var/i = 1 to num_changelings)
for (var/i in 1 to get_antag_cap_scaling_included(population))
if(candidates.len <= 0)
break
var/mob/M = pick_n_take(candidates)
@@ -717,13 +715,15 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
required_candidates = 3 // lives or dies by there being a few spies
weight = 5
cost = 8
scaling_cost = 101 // see below
minimum_players = 8
antag_cap = list("denominator" = 8, "offset" = 1) // should have quite a few spies to work against each other
scaling_cost = 4
minimum_players = 10
antag_cap = list("denominator" = 20, "offset" = 1)
requirements = list(8, 8, 8, 8, 8, 8, 8, 8, 8, 8)
/// What fraction is added to the antag cap for each additional scale
var/fraction_per_scale = 0.2
/datum/dynamic_ruleset/roundstart/spies/pre_execute(population)
for(var/i in 1 to get_antag_cap(population) * (scaled_times + 1))
for(var/i in 1 to get_antag_cap_scaling_included(population))
if(length(candidates) <= 0)
break
var/mob/picked_player = pick_n_take(candidates)
@@ -733,7 +733,6 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
GLOB.pre_setup_antags += picked_player.mind
return TRUE
/datum/dynamic_ruleset/roundstart/spies/scale_up(population, max_scale)
// Disabled (at least until dynamic can handle scaling this better)
// Because spies have a very low demoninator, this can easily spawn like 30 of them
return 0
// Scaling adds a fraction of the amount of additional spies rather than the full amount.
/datum/dynamic_ruleset/roundstart/spies/get_scaling_antag_cap(population)
return ceil(..() * fraction_per_scale)