mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-09 16:14:13 +00:00
[Role Datums] Dynamic Mode (#19548)
* Dynamic Mode * no rules * latejoin * midround * Traitor * secs * process * better * aight
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
// Dynamic Mode
|
||||
#define CURRENT_LIVING_PLAYERS "living"
|
||||
#define CURRENT_LIVING_ANTAGS "antags"
|
||||
#define CURRENT_DEAD_PLAYERS "dead"
|
||||
#define CURRENT_OBSERVERS "observers"
|
||||
|
||||
// Faction IDs
|
||||
#define BLOODCULT "cult of Nar-Sie"
|
||||
#define REVOLUTION "revolution"
|
||||
@@ -43,9 +49,12 @@
|
||||
|
||||
#define GREET_DEFAULT "default"
|
||||
#define GREET_ROUNDSTART "roundstart"
|
||||
#define GREET_LATEJOIN "latejoin"
|
||||
#define GREET_ADMINTOGGLE "admintoggle"
|
||||
#define GREET_CUSTOM "custom"
|
||||
|
||||
#define GREET_AUTOTATOR "autotator"
|
||||
|
||||
#define GREET_CONVERTED "converted"
|
||||
#define GREET_PAMPHLET "pamphlet"
|
||||
|
||||
|
||||
279
code/datums/gamemode/dynamic/dynamic.dm
Normal file
279
code/datums/gamemode/dynamic/dynamic.dm
Normal file
@@ -0,0 +1,279 @@
|
||||
var/list/forced_roundstart_ruleset = list()
|
||||
|
||||
/datum/gamemode/dynamic
|
||||
name = "Dynamic Mode"
|
||||
var/threat_level = 0//rolled at the beginning of the round.
|
||||
var/threat = 0//set at the beginning of the round. Spent by the mode to "purchase" rules.
|
||||
var/list/roundstart_rules = list()
|
||||
var/list/latejoin_rules = list()
|
||||
var/list/midround_rules = list()
|
||||
var/list/second_rule_req = list(0,0,0,80,60,40,20,0,0,0)//requirements for extra round start rules
|
||||
//var/list/second_rule_req = list(100,100,100,80,60,40,20,0,0,0)//requirements for extra round start rules
|
||||
var/list/third_rule_req = list(100,100,100,100,100,70,50,30,10,0)
|
||||
var/roundstart_pop_ready = 0
|
||||
var/list/candidates = list()
|
||||
var/list/current_rules = list()
|
||||
var/list/executed_rules = list()
|
||||
|
||||
var/list/living_players = list()
|
||||
var/list/living_antags = list()
|
||||
var/list/dead_players = list()
|
||||
var/list/list_observers = list()
|
||||
|
||||
var/latejoin_injection_cooldown = 0
|
||||
var/midround_injection_cooldown = 0
|
||||
|
||||
var/datum/dynamic_ruleset/latejoin/forced_latejoin_rule = null
|
||||
|
||||
/datum/gamemode/dynamic/can_start()
|
||||
threat_level = rand(1,100)*0.6 + rand(1,100)*0.4//https://docs.google.com/spreadsheets/d/1QLN_OBHqeL4cm9zTLEtxlnaJHHUu0IUPzPbsI-DFFmc/edit#gid=499381388
|
||||
threat = threat_level
|
||||
message_admins("Dynamic Mode initialized with a Threat Level of... <font size='8'>[threat_level]</font>!")
|
||||
return 1
|
||||
|
||||
/datum/gamemode/dynamic/Setup()
|
||||
for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart))
|
||||
roundstart_rules += new rule()
|
||||
for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin))
|
||||
latejoin_rules += new rule()
|
||||
for (var/rule in subtypesof(/datum/dynamic_ruleset/midround))
|
||||
midround_rules += new rule()
|
||||
for(var/mob/new_player/player in player_list)
|
||||
if(player.ready && player.mind)
|
||||
roundstart_pop_ready++
|
||||
candidates.Add(player)
|
||||
message_admins("Listing [roundstart_rules.len] round start rulesets, and [candidates.len] players ready.")
|
||||
if (candidates.len <= 0)
|
||||
message_admins("Not a single player readied-up. The round will begin without any roles assigned.")
|
||||
return 1
|
||||
if (roundstart_rules.len <= 0)
|
||||
message_admins("There are no roundstart rules within the code, what the fuck? The round will begin without any roles assigned.")
|
||||
return 1
|
||||
if (forced_roundstart_ruleset.len > 0)
|
||||
rigged_roundstart()
|
||||
else
|
||||
roundstart()
|
||||
return 1
|
||||
|
||||
/datum/gamemode/dynamic/proc/rigged_roundstart()
|
||||
message_admins("[forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.")
|
||||
for (var/datum/dynamic_ruleset/roundstart/rule in forced_roundstart_ruleset)
|
||||
rule.mode = src
|
||||
rule.candidates = candidates.Copy()
|
||||
rule.trim_candidates()
|
||||
if (rule.ready())
|
||||
picking_roundstart_rule(list(rule))
|
||||
|
||||
/datum/gamemode/dynamic/proc/roundstart()
|
||||
var/list/drafted_rules = list()
|
||||
var/i = 0
|
||||
for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules)
|
||||
if (rule.acceptable(roundstart_pop_ready,threat_level) && threat >= rule.cost) //if we got the population and threat required
|
||||
i++ //we check whether we've got elligible players
|
||||
rule.candidates = candidates.Copy()
|
||||
rule.trim_candidates()
|
||||
if (rule.ready())
|
||||
drafted_rules[rule] = rule.weight
|
||||
|
||||
var/indice_pop = min(10,round(roundstart_pop_ready/5)+1)
|
||||
message_admins("[i] rulesets qualify for the current pop and threat level, including [drafted_rules.len] with elligible candidates.")
|
||||
if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules))
|
||||
if (threat >= second_rule_req[indice_pop])//we've got enough population and threat for a second rulestart rule
|
||||
message_admins("The current pop and threat level allow for a second round start ruleset, there remains [candidates.len] elligible candidates and [drafted_rules.len] elligible rulesets")
|
||||
if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules))
|
||||
if (threat >= third_rule_req[indice_pop])//we've got enough population and threat for a third rulestart rule
|
||||
message_admins("The current pop and threat level allow for a third round start ruleset, there remains [candidates.len] elligible candidates and [drafted_rules.len] elligible rulesets")
|
||||
if (!drafted_rules.len > 0 || !picking_roundstart_rule(drafted_rules))
|
||||
message_admins("The mode failed to pick a third ruleset.")
|
||||
else
|
||||
message_admins("The mode failed to pick a second ruleset.")
|
||||
else
|
||||
message_admins("The mode failed to pick a first ruleset. The round will begin without any roles assigned.")
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/datum/gamemode/dynamic/proc/picking_roundstart_rule(var/list/drafted_rules = list())
|
||||
var/datum/dynamic_ruleset/roundstart/starting_rule = pickweight(drafted_rules)
|
||||
|
||||
if (starting_rule)
|
||||
message_admins("Picking a ruleset...<font size='3'>[starting_rule.name]</font>!")
|
||||
log_admin("Picking a ruleset...[starting_rule.name]!")
|
||||
|
||||
roundstart_rules -= starting_rule
|
||||
drafted_rules -= starting_rule
|
||||
|
||||
threat = max(0,threat-starting_rule.cost)
|
||||
if (starting_rule.execute())//this should never fail since ready() returned 1
|
||||
executed_rules += starting_rule
|
||||
if (starting_rule.persistent)
|
||||
current_rules += starting_rule
|
||||
for(var/mob/M in starting_rule.assigned)
|
||||
candidates -= M
|
||||
for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules)
|
||||
rule.candidates -= M//removing the assigned players from the candidates for the other rules
|
||||
if (!rule.ready())
|
||||
drafted_rules -= rule//and removing rules from those that are no longer elligible
|
||||
return 1
|
||||
else
|
||||
message_admins("....except not because whoever coded that ruleset forgot some cases in ready() apparently! execute() returned 0.")
|
||||
return 0
|
||||
|
||||
/datum/gamemode/dynamic/proc/picking_latejoin_rule(var/list/drafted_rules = list())
|
||||
var/datum/dynamic_ruleset/latejoin/latejoin_rule = pickweight(drafted_rules)
|
||||
if (latejoin_rule)
|
||||
if (!latejoin_rule.repeatable)
|
||||
latejoin_rules -= latejoin_rule
|
||||
threat = max(0,threat-latejoin_rule.cost)
|
||||
if (latejoin_rule.execute())//this should never fail since ready() returned 1
|
||||
var/mob/M = pick(latejoin_rule.assigned)
|
||||
message_admins("[key_name(M)] joined the station, and was selected by the <font size='3'>[latejoin_rule.name]</font> ruleset.")
|
||||
log_admin("[key_name(M)] joined the station, and was selected by the [latejoin_rule.name] ruleset.")
|
||||
executed_rules += latejoin_rule
|
||||
if (latejoin_rule.persistent)
|
||||
current_rules += latejoin_rule
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/datum/gamemode/dynamic/proc/picking_midround_rule(var/list/drafted_rules = list())
|
||||
var/datum/dynamic_ruleset/midround/midround_rule = pickweight(drafted_rules)
|
||||
if (midround_rule)
|
||||
if (!midround_rule.repeatable)
|
||||
midround_rules -= midround_rule
|
||||
threat = max(0,threat-midround_rule.cost)
|
||||
if (midround_rule.execute())//this should never fail since ready() returned 1
|
||||
message_admins("Injecting some threats...<font size='3'>[midround_rule.name]</font>!")
|
||||
log_admin("Injecting some threats...[midround_rule.name]!")
|
||||
executed_rules += midround_rule
|
||||
if (midround_rule.persistent)
|
||||
current_rules += midround_rule
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/datum/gamemode/dynamic/proc/picking_specific_rule(var/ruletype,var/forced=0)//an experimental proc to allow admins to call rules on the fly or have rules call other rules
|
||||
var/datum/dynamic_ruleset/midround/new_rule = new ruletype()//you should only use it to call midround rules though.
|
||||
update_playercounts()
|
||||
var/list/current_players = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS)
|
||||
current_players[CURRENT_LIVING_PLAYERS] = living_players.Copy()
|
||||
current_players[CURRENT_LIVING_ANTAGS] = living_antags.Copy()
|
||||
current_players[CURRENT_DEAD_PLAYERS] = dead_players.Copy()
|
||||
current_players[CURRENT_OBSERVERS] = list_observers.Copy()
|
||||
if (new_rule && (forced || (new_rule.acceptable(living_players.len,threat_level) && new_rule.cost <= threat)))
|
||||
new_rule.candidates = current_players.Copy()
|
||||
new_rule.trim_candidates()
|
||||
if (new_rule.ready())
|
||||
threat -= new_rule.cost
|
||||
if (new_rule.execute())//this should never fail since ready() returned 1
|
||||
message_admins("Making a call to a specific ruleset...<font size='3'>[new_rule.name]</font>!")
|
||||
log_admin("Making a call to a specific ruleset...[new_rule.name]!")
|
||||
executed_rules += new_rule
|
||||
if (new_rule.persistent)
|
||||
current_rules += new_rule
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/datum/gamemode/dynamic/process()
|
||||
if (latejoin_injection_cooldown)
|
||||
latejoin_injection_cooldown--
|
||||
|
||||
for (var/datum/dynamic_ruleset/rule in current_rules)
|
||||
rule.process()
|
||||
|
||||
if (midround_injection_cooldown)
|
||||
midround_injection_cooldown--
|
||||
else
|
||||
//time to inject some threat into the round
|
||||
if(emergency_shuttle.departed)//unless the shuttle is gone
|
||||
return
|
||||
|
||||
update_playercounts()
|
||||
|
||||
if (injection_attempt())
|
||||
midround_injection_cooldown = rand(12000,21000)//20 to 35 minutes inbetween midround threat injections attempts
|
||||
var/list/drafted_rules = list()
|
||||
var/list/current_players = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS)
|
||||
current_players[CURRENT_LIVING_PLAYERS] = living_players.Copy()
|
||||
current_players[CURRENT_LIVING_ANTAGS] = living_antags.Copy()
|
||||
current_players[CURRENT_DEAD_PLAYERS] = dead_players.Copy()
|
||||
current_players[CURRENT_OBSERVERS] = list_observers.Copy()
|
||||
for (var/datum/dynamic_ruleset/latejoin/rule in midround_rules)
|
||||
if (rule.acceptable(living_players.len,threat_level) && threat >= rule.cost)
|
||||
rule.candidates = current_players.Copy()
|
||||
rule.trim_candidates()
|
||||
if (rule.ready())
|
||||
drafted_rules[rule] = rule.weight
|
||||
|
||||
if (drafted_rules.len > 0)
|
||||
picking_latejoin_rule(drafted_rules)
|
||||
|
||||
|
||||
/datum/gamemode/dynamic/proc/update_playercounts()
|
||||
living_players = list()
|
||||
living_antags = list()
|
||||
dead_players = list()
|
||||
list_observers = list()
|
||||
for (var/mob/M in player_list)
|
||||
if (!M.client)
|
||||
continue
|
||||
if (istype(M,/mob/new_player))
|
||||
continue
|
||||
if (M.stat != DEAD)
|
||||
living_players.Add(M)
|
||||
if (M.mind && (M.mind.antag_roles.len > 0))
|
||||
living_antags.Add(M)
|
||||
else
|
||||
if (istype(M,/mob/dead/observer))
|
||||
var/mob/dead/observer/O = M
|
||||
if (O.started_as_observer)//Observers
|
||||
list_observers.Add(M)
|
||||
continue
|
||||
if (O.mind && O.mind.current && O.mind.current.ajourn)//Cultists
|
||||
living_players.Add(M)//yes we're adding a ghost to "living_players", so make sure to properly check for type when testing midround rules
|
||||
continue
|
||||
dead_players.Add(M)//Players who actually died (and admins who ghosted, would be nice to avoid counting them somehow)
|
||||
|
||||
/datum/gamemode/dynamic/proc/injection_attempt()//will need to gather stats to refine those values later
|
||||
if (latejoin_injection_cooldown)
|
||||
return
|
||||
var/chance = 0
|
||||
var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(living_players.len/5))//https://docs.google.com/spreadsheets/d/1QLN_OBHqeL4cm9zTLEtxlnaJHHUu0IUPzPbsI-DFFmc/edit#gid=2053826290
|
||||
if (!living_antags.len)
|
||||
chance += 50//no antags at all? let's boost those odds!
|
||||
else
|
||||
var/current_pop_per_antag = living_players.len / living_antags.len
|
||||
if (current_pop_per_antag > max_pop_per_antag)
|
||||
chance += min(50, 25+10*(current_pop_per_antag-max_pop_per_antag))
|
||||
else
|
||||
chance += 25-10*(max_pop_per_antag-current_pop_per_antag)
|
||||
if (dead_players.len > living_players.len)
|
||||
chance -= 30//more than half the crew died? ew, let's calm down on antags
|
||||
if (threat > 70)
|
||||
chance += 20
|
||||
if (threat < 30)
|
||||
chance -= 20
|
||||
chance = round(max(0,chance))
|
||||
return (prob(chance))
|
||||
|
||||
/datum/gamemode/dynamic/latespawn(var/mob/living/newPlayer)
|
||||
if(emergency_shuttle.departed)//no more rules after the shuttle has left
|
||||
return
|
||||
|
||||
update_playercounts()
|
||||
|
||||
if (forced_latejoin_rule)
|
||||
forced_latejoin_rule.candidates = list(newPlayer)
|
||||
forced_latejoin_rule.trim_candidates()
|
||||
if (forced_latejoin_rule.ready())
|
||||
picking_latejoin_rule(list(forced_latejoin_rule))
|
||||
forced_latejoin_rule = null
|
||||
|
||||
else if (injection_attempt())
|
||||
var/list/drafted_rules = list()
|
||||
for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules)
|
||||
if (rule.acceptable(living_players.len,threat_level) && threat >= rule.cost)
|
||||
rule.candidates = list(newPlayer)
|
||||
rule.trim_candidates()
|
||||
if (rule.ready())
|
||||
drafted_rules[rule] = rule.weight
|
||||
|
||||
if (drafted_rules.len > 0 && picking_latejoin_rule(drafted_rules))
|
||||
latejoin_injection_cooldown = rand(6600,10200)//11 to 17 minutes inbetween antag latejoiner rolls
|
||||
147
code/datums/gamemode/dynamic/dynamic_rulesets.dm
Normal file
147
code/datums/gamemode/dynamic/dynamic_rulesets.dm
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
|
||||
/datum/dynamic_ruleset
|
||||
var/name = ""//For admin logging, and round end scoreboard
|
||||
var/persistent = 0//if set to 1, the rule won't be discarded after being executed, and /gamemode/dynamic will call process() every MC tick
|
||||
var/repeatable = 0//if set to 1, dynamic mode will be able to draft this ruleset again later on. (doesn't apply for roundstart rules)
|
||||
var/list/candidates = list()//list of players that are being drafted for this rule
|
||||
var/list/assigned = list()//list of players that were selected for this rule
|
||||
var/role_category = ROLE_TRAITOR//rule will only accept candidates with "Yes" or "Always" in the preferences for this role
|
||||
var/list/restricted_from_jobs = list()//if set, rule will deny candidates from those jobs
|
||||
var/list/exclusive_to_jobs = list()//if set, rule will only accept candidates from those jobs
|
||||
var/list/jobs_must_exist = list()//if set, there needs to be a certain amount of players doing those jobs (among the players who won't be drafted) for the rule to be drafted
|
||||
var/required_candidates = 0//the rule needs this many candidates (post-trimming) to be executed (example: Cult need 4 players at round start)
|
||||
var/weight = 5//1 -> 9, probability for this rule to be picked against other rules
|
||||
var/cost = 0//threat cost for this rule.
|
||||
|
||||
var/list/requirements = list(40,30,20,10,10,10,10,10,10,10)
|
||||
//requirements are the threat level requirements per pop range. The ranges are as follow:
|
||||
//0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+
|
||||
//so with the above default values, The rule will never get drafted below 10 threat level (aka: "peaceful extended"), and it requires a higher threat level at lower pops.
|
||||
//for reminder: the threat level is rolled at roundstart and tends to hover around 50 https://docs.google.com/spreadsheets/d/1QLN_OBHqeL4cm9zTLEtxlnaJHHUu0IUPzPbsI-DFFmc/edit#gid=499381388
|
||||
|
||||
var/datum/gamemode/dynamic/mode = null
|
||||
|
||||
/datum/dynamic_ruleset/New()
|
||||
..()
|
||||
if (istype(ticker.mode, /datum/gamemode/dynamic))
|
||||
mode = ticker.mode
|
||||
else
|
||||
message_admins("A dynamic ruleset was created but server isn't on Dynamic Mode!")
|
||||
qdel(src)
|
||||
|
||||
/datum/dynamic_ruleset/roundstart//One or more of those drafted at roundstart
|
||||
|
||||
/datum/dynamic_ruleset/latejoin//Can be drafted when a player joins the server
|
||||
|
||||
/datum/dynamic_ruleset/midround//Can be drafted once in a while during a round
|
||||
var/list/living_players = list()
|
||||
var/list/living_antags = list()
|
||||
var/list/dead_players = list()
|
||||
var/list/list_observers = list()
|
||||
|
||||
/datum/dynamic_ruleset/proc/acceptable(var/population=0,var/threat=0)
|
||||
//by default, a rule is acceptable if it satisfies the threat level/population requirements.
|
||||
//If your rule has extra checks, such as counting security officers, do that in ready() instead
|
||||
var/indice_pop = min(10,round(population/5)+1)
|
||||
return (threat >= requirements[indice_pop])
|
||||
|
||||
/datum/dynamic_ruleset/proc/process()
|
||||
//write here your rule execution code, everything about faction/role spawning/populating.
|
||||
return
|
||||
|
||||
/datum/dynamic_ruleset/proc/execute()
|
||||
//write here your rule execution code, everything about faction/role spawning/populating.
|
||||
return 1
|
||||
|
||||
/datum/dynamic_ruleset/proc/ready() //Here you can perform any additional checks you want. (such as checking the map, the amount of certain jobs, etc)
|
||||
if (required_candidates > candidates.len) //IMPORTANT: If ready() returns 1, that means execute() should never fail!
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/datum/dynamic_ruleset/proc/trim_candidates()
|
||||
return
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// //
|
||||
// ROUNDSTART RULESETS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/trim_candidates()
|
||||
for(var/mob/new_player/P in candidates)
|
||||
if (!P.client || !P.mind || !P.mind.assigned_role)//are they connected?
|
||||
candidates.Remove(P)
|
||||
continue
|
||||
if (!P.client.desires_role(role_category) || jobban_isbanned(P, role_category))//are they willing and not antag-banned?
|
||||
candidates.Remove(P)
|
||||
continue
|
||||
if (P.mind.assigned_role in restricted_from_jobs)//does their job allow for it?
|
||||
candidates.Remove(P)
|
||||
continue
|
||||
if ((exclusive_to_jobs.len > 0) && !(P.mind.assigned_role in exclusive_to_jobs))//is the rule exclusive to their job?
|
||||
candidates.Remove(P)
|
||||
continue
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// //
|
||||
// LATEJOIN RULESETS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/datum/dynamic_ruleset/latejoin/trim_candidates()
|
||||
for(var/mob/new_player/P in candidates)
|
||||
if (!P.client || !P.mind || !P.mind.assigned_role)//are they connected?
|
||||
candidates.Remove(P)
|
||||
continue
|
||||
if (!P.client.desires_role(role_category) || jobban_isbanned(P, role_category))//are they willing and not antag-banned?
|
||||
candidates.Remove(P)
|
||||
continue
|
||||
if (P.mind.assigned_role in restricted_from_jobs)//does their job allow for it?
|
||||
candidates.Remove(P)
|
||||
continue
|
||||
if ((exclusive_to_jobs.len > 0) && !(P.mind.assigned_role in exclusive_to_jobs))//is the rule exclusive to their job?
|
||||
candidates.Remove(P)
|
||||
continue
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// //
|
||||
// MIDROUND RULESETS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
|
||||
/datum/dynamic_ruleset/midround/trim_candidates()
|
||||
//unlike the previous two types, these rulesets are not meant for /mob/new_player
|
||||
//and since I want those rulesets to be as flexible as possible, I'm not gonna put much here,
|
||||
//but be sure to check dynamic_rulesets_debug.dm for an example.
|
||||
//
|
||||
//all you need to know is that here, the candidates list contains 4 lists itself, indexed with the following defines:
|
||||
//candidates = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS)
|
||||
//so for example you can get the list of all current dead players with var/list/dead_players = candidates[CURRENT_DEAD_PLAYERS]
|
||||
//make sure to properly typecheck the mobs in those lists, as the dead_players list could contain ghosts, or dead players still in their bodies.
|
||||
//we're still gonna trim the obvious (mobs without clients, jobbanned players, etc)
|
||||
living_players = trim_list(candidates[CURRENT_LIVING_PLAYERS])
|
||||
living_antags = trim_list(candidates[CURRENT_LIVING_ANTAGS])
|
||||
dead_players = trim_list(candidates[CURRENT_DEAD_PLAYERS])
|
||||
observers = trim_list(candidates[CURRENT_OBSERVERS])
|
||||
|
||||
/datum/dynamic_ruleset/midround/proc/trim_list(var/list/L = list())
|
||||
var/list/trimmed_list = L.Copy()
|
||||
for(var/mob/M in trimmed_list)
|
||||
if (!M.client)//are they connected?
|
||||
trimmed_list.Remove(M)
|
||||
continue
|
||||
if (!M.client.desires_role(role_category) || jobban_isbanned(M, role_category))//are they willing and not antag-banned?
|
||||
trimmed_list.Remove(M)
|
||||
continue
|
||||
if (M.mind.assigned_role in restricted_from_jobs)//does their job allow for it?
|
||||
trimmed_list.Remove(M)
|
||||
continue
|
||||
if ((exclusive_to_jobs.len > 0) && !(M.mind.assigned_role in exclusive_to_jobs))//is the rule exclusive to their job?
|
||||
trimmed_list.Remove(M)
|
||||
continue
|
||||
return trimmed_list
|
||||
|
||||
//You can then for example prompt dead players in execute() to join as strike teams or whatever
|
||||
//Or autotator someone
|
||||
88
code/datums/gamemode/dynamic/dynamic_rulesets_debug.dm
Normal file
88
code/datums/gamemode/dynamic/dynamic_rulesets_debug.dm
Normal file
@@ -0,0 +1,88 @@
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/test_traitor
|
||||
name = "Test Lone Traitor"
|
||||
persistent = 0//if set to 1, the rule won't be discarded after being executed, and the game mode will call update() once in a while
|
||||
repeatable = 0//if set to 1, dynamic mode will be able to draft this ruleset again later on
|
||||
role_category = ROLE_TRAITOR//rule will only accept candidates with "Yes" or "Always" in the preferences for this role
|
||||
required_candidates = 1//the rule needs this many candidates (post-trimming) to be executed (example: Cult need 4 players at round start)
|
||||
weight = 5//1 -> 9, probability for this rule to be picked against other rules
|
||||
cost = 0//threat cost for this rule.
|
||||
requirements = list(0,0,0,0,0,0,0,0,0,0)
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/test_traitor/execute()
|
||||
var/mob/M = pick(candidates)
|
||||
assigned += M
|
||||
var/datum/role/traitor/newTraitor = new
|
||||
newTraitor.AssignToRole(M.mind,1)
|
||||
newTraitor.OnPostSetup(FALSE)
|
||||
newTraitor.Greet(GREET_ROUNDSTART)
|
||||
return 1
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/test_cultist
|
||||
name = "Test Lone Cultist"
|
||||
persistent = 0//if set to 1, the rule won't be discarded after being executed, and the game mode will call update() once in a while
|
||||
repeatable = 0//if set to 1, dynamic mode will be able to draft this ruleset again later on
|
||||
role_category = ROLE_CULTIST//rule will only accept candidates with "Yes" or "Always" in the preferences for this role
|
||||
required_candidates = 1//the rule needs this many candidates (post-trimming) to be executed (example: Cult need 4 players at round start)
|
||||
weight = 8//1 -> 9, probability for this rule to be picked against other rules
|
||||
cost = 0//threat cost for this rule.
|
||||
requirements = list(0,0,0,0,0,0,0,0,0,0)
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/test_cultist/execute()
|
||||
var/mob/M = pick(candidates)
|
||||
assigned += M
|
||||
var/datum/role/cultist/newCultist = new
|
||||
newCultist.AssignToRole(M.mind,1)
|
||||
var/datum/faction/bloodcult/cult = find_active_faction_by_type(/datum/faction/bloodcult)
|
||||
if (!cult)
|
||||
cult = ticker.mode.CreateFaction(/datum/faction/bloodcult, null, 1)
|
||||
cult.HandleRecruitedRole(newCultist)
|
||||
newCultist.OnPostSetup(FALSE)
|
||||
newCultist.Greet(GREET_ROUNDSTART)
|
||||
return 1
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/test_vampire
|
||||
name = "Test Lone Vampire"
|
||||
persistent = 0//if set to 1, the rule won't be discarded after being executed, and the game mode will call update() once in a while
|
||||
repeatable = 0//if set to 1, dynamic mode will be able to draft this ruleset again later on
|
||||
role_category = ROLE_VAMPIRE//rule will only accept candidates with "Yes" or "Always" in the preferences for this role
|
||||
required_candidates = 1//the rule needs this many candidates (post-trimming) to be executed (example: Cult need 4 players at round start)
|
||||
weight = 2//1 -> 9, probability for this rule to be picked against other rules
|
||||
cost = 0//threat cost for this rule.
|
||||
requirements = list(0,0,0,0,0,0,0,0,0,0)
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/test_vampire/execute()
|
||||
var/mob/M = pick(candidates)
|
||||
assigned += M
|
||||
var/datum/role/traitor/newVampire = new
|
||||
newVampire.AssignToRole(M.mind,1)
|
||||
newVampire.OnPostSetup(FALSE)
|
||||
newVampire.Greet(GREET_ROUNDSTART)
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
/datum/dynamic_ruleset/latejoin/test_traitor
|
||||
name = "Test Latejoin Traitor"
|
||||
persistent = 0//if set to 1, the rule won't be discarded after being executed, and the game mode will call update() once in a while
|
||||
repeatable = 0//if set to 1, dynamic mode will be able to draft this ruleset again later on
|
||||
role_category = ROLE_TRAITOR//rule will only accept candidates with "Yes" or "Always" in the preferences for this role
|
||||
required_candidates = 1//the rule needs this many candidates (post-trimming) to be executed (example: Cult need 4 players at round start)
|
||||
weight = 5//1 -> 9, probability for this rule to be picked against other rules
|
||||
cost = 0//threat cost for this rule.
|
||||
requirements = list(0,0,0,0,0,0,0,0,0,0)
|
||||
|
||||
/datum/dynamic_ruleset/latejoin/test_traitor/execute()
|
||||
var/mob/M = pick(candidates)
|
||||
assigned += M
|
||||
var/datum/role/traitor/newTraitor = new
|
||||
newTraitor.AssignToRole(M.mind,1)
|
||||
newTraitor.OnPostSetup(FALSE)
|
||||
newTraitor.Greet(GREET_ROUNDSTART)
|
||||
return 1
|
||||
|
||||
34
code/datums/gamemode/dynamic/dynamic_rulesets_latejoin.dm
Normal file
34
code/datums/gamemode/dynamic/dynamic_rulesets_latejoin.dm
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// //
|
||||
// SYNDICATE TRAITORS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/datum/dynamic_ruleset/latejoin/infiltrator
|
||||
name = "Syndicate Infiltrator"
|
||||
role_category = ROLE_TRAITOR
|
||||
restricted_from_jobs = list("Cyborg","Mobile MMI","Security Officer", "Warden", "Detective", "Head of Security", "Captain")
|
||||
required_candidates = 1
|
||||
weight = 5
|
||||
cost = 5
|
||||
requirements = list(40,30,20,10,10,10,10,10,10,10)
|
||||
|
||||
/datum/dynamic_ruleset/latejoin/infiltrator/acceptable(var/population=0,var/threat=0)
|
||||
var/player_count = mode.living_players.len
|
||||
var/antag_count = mode.living_antags.len
|
||||
var/max_traitors = round(player_count / 10) + 1
|
||||
if ((antag_count < max_traitors) && prob(mode.threat_level))//adding traitors if the antag population is getting low
|
||||
return ..()
|
||||
else
|
||||
return 0
|
||||
|
||||
/datum/dynamic_ruleset/latejoin/infiltrator/execute()
|
||||
var/mob/M = pick(candidates)
|
||||
assigned += M
|
||||
candidates -= M
|
||||
var/datum/role/traitor/newTraitor = new
|
||||
newTraitor.AssignToRole(M.mind,1)
|
||||
newTraitor.OnPostSetup(FALSE)
|
||||
newTraitor.Greet(GREET_LATEJOIN)
|
||||
return 1
|
||||
43
code/datums/gamemode/dynamic/dynamic_rulesets_midround.dm
Normal file
43
code/datums/gamemode/dynamic/dynamic_rulesets_midround.dm
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// //
|
||||
// SYNDICATE TRAITORS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/datum/dynamic_ruleset/midround/autotraitor
|
||||
name = "Syndicate Sleeper Agent"
|
||||
role_category = ROLE_TRAITOR
|
||||
restricted_from_jobs = list("Cyborg","Mobile MMI","Security Officer", "Warden", "Detective", "Head of Security", "Captain")
|
||||
required_candidates = 1
|
||||
weight = 5
|
||||
cost = 5
|
||||
requirements = list(40,30,20,10,10,10,10,10,10,10)
|
||||
|
||||
/datum/dynamic_ruleset/midround/autotraitor/acceptable(var/population=0,var/threat=0)
|
||||
var/player_count = mode.living_players.len
|
||||
var/antag_count = mode.living_antags.len
|
||||
var/max_traitors = round(player_count / 10) + 1
|
||||
if ((antag_count < max_traitors) && prob(mode.threat_level))//adding traitors if the antag population is getting low
|
||||
return ..()
|
||||
else
|
||||
return 0
|
||||
|
||||
/datum/dynamic_ruleset/midround/autotraitor/trim_candidates()
|
||||
..()
|
||||
for(var/mob/living/player in living_players)
|
||||
if(player.z == map.zCentcomm)
|
||||
living_players -= player//we don't autotator people on Z=2
|
||||
continue
|
||||
if(player.mind && (player.mind.antag_roles.len > 0))
|
||||
living_players -= player//we don't autotator people with roles already
|
||||
|
||||
/datum/dynamic_ruleset/midround/autotraitor/execute()
|
||||
var/mob/M = pick(living_players)
|
||||
assigned += M
|
||||
candidates -= M
|
||||
var/datum/role/traitor/newTraitor = new
|
||||
newTraitor.AssignToRole(M.mind,1)
|
||||
newTraitor.OnPostSetup(FALSE)
|
||||
newTraitor.Greet(GREET_AUTOTATOR)
|
||||
return 1
|
||||
37
code/datums/gamemode/dynamic/dynamic_rulesets_roundstart.dm
Normal file
37
code/datums/gamemode/dynamic/dynamic_rulesets_roundstart.dm
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// //
|
||||
// SYNDICATE TRAITORS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/traitor
|
||||
name = "Syndicate Traitors"
|
||||
persistent = 1
|
||||
role_category = ROLE_TRAITOR
|
||||
restricted_from_jobs = list("Cyborg","Mobile MMI","Security Officer", "Warden", "Detective", "Head of Security", "Captain")
|
||||
required_candidates = 1
|
||||
weight = 5
|
||||
cost = 10
|
||||
requirements = list(40,30,20,10,10,10,10,10,10,10)
|
||||
var/autotraitor_cooldown = 900//15 minutes
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/traitor/execute()
|
||||
var/num_traitors = min(round(mode.candidates.len / 10) + 1, candidates.len)
|
||||
for (var/i = 1 to num_traitors)
|
||||
var/mob/M = pick(candidates)
|
||||
assigned += M
|
||||
candidates -= M
|
||||
var/datum/role/traitor/newTraitor = new
|
||||
newTraitor.AssignToRole(M.mind,1)
|
||||
newTraitor.OnPostSetup(FALSE)
|
||||
newTraitor.Greet(GREET_ROUNDSTART)
|
||||
return 1
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/traitor/process()
|
||||
if (autotraitor_cooldown)
|
||||
autotraitor_cooldown--
|
||||
else
|
||||
autotraitor_cooldown = 900//15 minutes
|
||||
message_admins("Dynamic Mode: Checking if we can turn someone into a traitor...")
|
||||
mode.picking_specific_rule(/datum/dynamic_ruleset/midround/autotraitor)
|
||||
@@ -34,7 +34,8 @@ var/veil_thickness = CULT_PROLOGUE
|
||||
M.special_role = "Cultist"
|
||||
|
||||
/datum/faction/bloodcult/OnPostSetup()
|
||||
..()
|
||||
for(var/datum/role/R in members)
|
||||
R.OnPostSetup(FALSE)
|
||||
initialize_cultwords()
|
||||
|
||||
//to recode later on
|
||||
|
||||
@@ -68,6 +68,23 @@
|
||||
M.take_uplink()
|
||||
to_chat(M.current, "<span class='warning'>You have been stripped of your uplink.</span>")
|
||||
|
||||
/datum/role/traitor/Greet(var/greeting,var/custom)
|
||||
if(!greeting)
|
||||
return
|
||||
|
||||
var/icon/logo = icon('icons/logos.dmi', logo_state)
|
||||
switch(greeting)
|
||||
if (GREET_ROUNDSTART)
|
||||
to_chat(antag.current, "<img src='data:image/png;base64,[icon2base64(logo)]' style='position: relative; top: 10;'/> <span class='danger'>You are a Syndicate agent, a Traitor.</span>")
|
||||
if (GREET_AUTOTATOR)
|
||||
to_chat(antag.current, "<img src='data:image/png;base64,[icon2base64(logo)]' style='position: relative; top: 10;'/> <span class='danger'>Your memory clears up as you remember your identity as a sleeper agent of the Syndicate. It's time to pay your debt to them. You are now a Traitor.</span>")
|
||||
if (GREET_LATEJOIN)
|
||||
to_chat(antag.current, "<img src='data:image/png;base64,[icon2base64(logo)]' style='position: relative; top: 10;'/> <span class='danger'>As a Syndicate agent, you are to infiltrate the crew and accomplish your objectives at all cost. You are a Traitor.</span>")
|
||||
else
|
||||
to_chat(antag.current, "<img src='data:image/png;base64,[icon2base64(logo)]' style='position: relative; top: 10;'/> <span class='danger'>You are a Traitor.</span>")
|
||||
|
||||
to_chat(antag.current, "<span class='info'><a HREF='?src=\ref[antag.current];getwiki=[wikiroute]'>(Wiki Guide)</a></span>")
|
||||
|
||||
|
||||
//________________________________________________
|
||||
|
||||
|
||||
@@ -679,6 +679,21 @@ var/global/floorIsLava = 0
|
||||
"}
|
||||
if(master_mode == "secret")
|
||||
dat += "<A href='?src=\ref[src];f_secret=1'>(Force Secret Mode)</A><br>"
|
||||
if(master_mode == "Dynamic Mode")
|
||||
if(ticker.current_state == GAME_STATE_PREGAME)
|
||||
dat += "<A href='?src=\ref[src];f_dynamic_roundstart=1'>(Force Roundstart Rulesets)</A><br>"
|
||||
if (forced_roundstart_ruleset.len > 0)
|
||||
for(var/datum/dynamic_ruleset/roundstart/rule in forced_roundstart_ruleset)
|
||||
dat += {"<A href='?src=\ref[src];f_dynamic_roundstart_remove=\ref[rule]'>-> [rule.name] <-</A><br>"}
|
||||
dat += "<A href='?src=\ref[src];f_dynamic_roundstart_clear=1'>(Clear Rulesets)</A><br>"
|
||||
else
|
||||
dat += "<A href='?src=\ref[src];f_dynamic_latejoin=1'>(Force Next Latejoin Ruleset)</A><br>"
|
||||
if (ticker && ticker.mode && istype(ticker.mode,/datum/gamemode/dynamic))
|
||||
var/datum/gamemode/dynamic/mode = ticker.mode
|
||||
if (mode.forced_latejoin_rule)
|
||||
dat += {"<A href='?src=\ref[src];f_dynamic_latejoin_clear=1'>-> [mode.forced_latejoin_rule.name] <-</A><br>"}
|
||||
dat += "<A href='?src=\ref[src];f_dynamic_midround=1'>(Execute Midround Ruleset!)</A><br>"
|
||||
|
||||
|
||||
dat += {"
|
||||
<hr />
|
||||
|
||||
@@ -1447,6 +1447,98 @@
|
||||
dat += {"Now: [secret_force_mode]"}
|
||||
usr << browse(dat, "window=f_secret")
|
||||
|
||||
else if(href_list["f_dynamic_roundstart"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
if(ticker && ticker.mode)
|
||||
return alert(usr, "The game has already started.", null, null, null, null)
|
||||
if(master_mode != "Dynamic Mode")
|
||||
return alert(usr, "The game mode has to be Dynamic Mode!", null, null, null, null)
|
||||
var/roundstart_rules = list()
|
||||
for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart))
|
||||
var/datum/dynamic_ruleset/roundstart/newrule = new rule()
|
||||
roundstart_rules[newrule.name] = newrule
|
||||
var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in roundstart_rules
|
||||
if (added_rule)
|
||||
forced_roundstart_ruleset += roundstart_rules[added_rule]
|
||||
log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.")
|
||||
message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1)
|
||||
Game()
|
||||
|
||||
else if(href_list["f_dynamic_roundstart_clear"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
forced_roundstart_ruleset = list()
|
||||
Game()
|
||||
log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.")
|
||||
message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1)
|
||||
|
||||
|
||||
else if(href_list["f_dynamic_roundstart_remove"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"])
|
||||
forced_roundstart_ruleset -= rule
|
||||
Game()
|
||||
log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.")
|
||||
message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1)
|
||||
|
||||
|
||||
else if(href_list["f_dynamic_latejoin"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
if(!ticker || !ticker.mode)
|
||||
return alert(usr, "The game must start first.", null, null, null, null)
|
||||
if(master_mode != "Dynamic Mode")
|
||||
return alert(usr, "The game mode has to be Dynamic Mode!", null, null, null, null)
|
||||
var/latejoin_rules = list()
|
||||
for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin))
|
||||
var/datum/dynamic_ruleset/latejoin/newrule = new rule()
|
||||
latejoin_rules[newrule.name] = newrule
|
||||
var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in latejoin_rules
|
||||
if (added_rule)
|
||||
var/datum/gamemode/dynamic/mode = ticker.mode
|
||||
mode.forced_latejoin_rule = latejoin_rules[added_rule]
|
||||
log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.")
|
||||
message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1)
|
||||
Game()
|
||||
|
||||
else if(href_list["f_dynamic_latejoin_clear"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
if (ticker && ticker.mode && istype(ticker.mode,/datum/gamemode/dynamic))
|
||||
var/datum/gamemode/dynamic/mode = ticker.mode
|
||||
mode.forced_latejoin_rule = null
|
||||
Game()
|
||||
log_admin("[key_name(usr)] cleared the forced latejoin ruleset.")
|
||||
message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1)
|
||||
|
||||
else if(href_list["f_dynamic_midround"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
if(!ticker || !ticker.mode)
|
||||
return alert(usr, "The game must start first.", null, null, null, null)
|
||||
if(master_mode != "Dynamic Mode")
|
||||
return alert(usr, "The game mode has to be Dynamic Mode!", null, null, null, null)
|
||||
var/midround_rules = list()
|
||||
for (var/rule in subtypesof(/datum/dynamic_ruleset/midround))
|
||||
var/datum/dynamic_ruleset/midround/newrule = new rule()
|
||||
midround_rules[newrule.name] = rule
|
||||
var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in midround_rules
|
||||
if (added_rule)
|
||||
var/datum/gamemode/dynamic/mode = ticker.mode
|
||||
log_admin("[key_name(usr)] executed the [added_rule] ruleset.")
|
||||
message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1)
|
||||
mode.picking_specific_rule(midround_rules[added_rule],1)
|
||||
|
||||
|
||||
|
||||
else if(href_list["c_mode2"])
|
||||
if(!check_rights(R_ADMIN|R_SERVER))
|
||||
return
|
||||
|
||||
@@ -1 +1 @@
|
||||
sandbox
|
||||
Dynamic Mode
|
||||
@@ -282,6 +282,11 @@
|
||||
#include "code\datums\gamemode\traitor.dm"
|
||||
#include "code\datums\gamemode\vampire_gamemode.dm"
|
||||
#include "code\datums\gamemode\wizard.dm"
|
||||
#include "code\datums\gamemode\dynamic\dynamic.dm"
|
||||
#include "code\datums\gamemode\dynamic\dynamic_rulesets.dm"
|
||||
#include "code\datums\gamemode\dynamic\dynamic_rulesets_latejoin.dm"
|
||||
#include "code\datums\gamemode\dynamic\dynamic_rulesets_midround.dm"
|
||||
#include "code\datums\gamemode\dynamic\dynamic_rulesets_roundstart.dm"
|
||||
#include "code\datums\gamemode\factions\faction.dm"
|
||||
#include "code\datums\gamemode\factions\malf.dm"
|
||||
#include "code\datums\gamemode\factions\vampire_faction.dm"
|
||||
|
||||
Reference in New Issue
Block a user