Merge pull request #10189 from Putnam3145/storytellers

n word
This commit is contained in:
nik707
2020-01-02 22:01:19 -06:00
committed by GitHub
17 changed files with 750 additions and 261 deletions

14
code/__DEFINES/dynamic.dm Normal file
View File

@@ -0,0 +1,14 @@
#define CURRENT_LIVING_PLAYERS 1
#define CURRENT_LIVING_ANTAGS 2
#define CURRENT_DEAD_PLAYERS 3
#define CURRENT_OBSERVERS 4
#define NO_ASSASSIN (1<<0)
#define WAROPS_ALWAYS_ALLOWED (1<<1)
#define ONLY_RULESET (1<<0)
#define HIGHLANDER_RULESET (1<<1)
#define TRAITOR_RULESET (1<<2)
#define MINOR_RULESET (1<<3)
#define RULESET_STOP_PROCESSING 1

6
code/__DEFINES/vote.dm Normal file
View File

@@ -0,0 +1,6 @@
#define PLURALITY_VOTING 0
#define APPROVAL_VOTING 1
#define RANKED_CHOICE_VOTING 2
#define SCORE_VOTING 3
GLOBAL_LIST_INIT(vote_score_options,list("Bad","Poor","Acceptable","Good","Great"))

View File

@@ -14,6 +14,7 @@
var/list/modes // allowed modes var/list/modes // allowed modes
var/list/gamemode_cache var/list/gamemode_cache
var/list/votable_modes // votable modes var/list/votable_modes // votable modes
var/list/storyteller_cache
var/list/mode_names var/list/mode_names
var/list/mode_reports var/list/mode_reports
var/list/mode_false_report_weight var/list/mode_false_report_weight
@@ -37,6 +38,7 @@
CRASH("/datum/controller/configuration/Load() called more than once!") CRASH("/datum/controller/configuration/Load() called more than once!")
InitEntries() InitEntries()
LoadModes() LoadModes()
storyteller_cache = typecacheof(/datum/dynamic_storyteller, TRUE)
if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1) if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1)
var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt") var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt")
for(var/I in legacy_configs) for(var/I in legacy_configs)
@@ -227,6 +229,7 @@
for(var/T in gamemode_cache) for(var/T in gamemode_cache)
// I wish I didn't have to instance the game modes in order to look up // I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of). // their information, but it is the only way (at least that I know of).
// for future reference: just use initial() lol
var/datum/game_mode/M = new T() var/datum/game_mode/M = new T()
if(M.config_tag) if(M.config_tag)
@@ -317,6 +320,14 @@
return new T return new T
return new /datum/game_mode/extended() return new /datum/game_mode/extended()
/datum/controller/configuration/proc/pick_storyteller(storyteller_name)
for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T
var/name = initial(S.name)
if(name && name == storyteller_name)
return T
return /datum/dynamic_storyteller/classic
/datum/controller/configuration/proc/get_runnable_modes() /datum/controller/configuration/proc/get_runnable_modes()
var/list/datum/game_mode/runnable_modes = new var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)

View File

@@ -13,13 +13,14 @@ SUBSYSTEM_DEF(persistence)
var/list/saved_messages = list() var/list/saved_messages = list()
var/list/saved_modes = list(1,2,3) var/list/saved_modes = list(1,2,3)
var/list/saved_dynamic_rules = list(list(),list(),list()) var/list/saved_dynamic_rules = list(list(),list(),list())
var/list/saved_threat_levels = list(1,1,1) var/list/saved_storytellers = list("foo","bar","baz","foo again","bar again")
var/list/saved_maps var/list/saved_maps
var/list/saved_trophies = list() var/list/saved_trophies = list()
var/list/spawned_objects = list() var/list/spawned_objects = list()
var/list/antag_rep = list() var/list/antag_rep = list()
var/list/antag_rep_change = list() var/list/antag_rep_change = list()
var/list/picture_logging_information = list() var/list/picture_logging_information = list()
var/list/saved_votes = list()
var/list/obj/structure/sign/picture_frame/photo_frames var/list/obj/structure/sign/picture_frame/photo_frames
var/list/obj/item/storage/photo_album/photo_albums var/list/obj/item/storage/photo_album/photo_albums
@@ -29,9 +30,12 @@ SUBSYSTEM_DEF(persistence)
LoadChiselMessages() LoadChiselMessages()
LoadTrophies() LoadTrophies()
LoadRecentModes() LoadRecentModes()
LoadRecentThreats() LoadRecentStorytellers()
LoadRecentRulesets()
LoadRecentMaps() LoadRecentMaps()
LoadPhotoPersistence() LoadPhotoPersistence()
for(var/client/C in GLOB.clients)
LoadSavedVote(C.ckey)
if(CONFIG_GET(flag/use_antag_rep)) if(CONFIG_GET(flag/use_antag_rep))
LoadAntagReputation() LoadAntagReputation()
LoadRandomizedRecipes() LoadRandomizedRecipes()
@@ -169,14 +173,23 @@ SUBSYSTEM_DEF(persistence)
return return
saved_modes = json["data"] saved_modes = json["data"]
/datum/controller/subsystem/persistence/proc/LoadRecentThreats() /datum/controller/subsystem/persistence/proc/LoadRecentRulesets()
var/json_file = file("data/RecentThreatLevels.json") var/json_file = file("data/RecentRulesets.json")
if(!fexists(json_file)) if(!fexists(json_file))
return return
var/list/json = json_decode(file2text(json_file)) var/list/json = json_decode(file2text(json_file))
if(!json) if(!json)
return return
saved_threat_levels = json["data"] saved_dynamic_rules = json["data"]
/datum/controller/subsystem/persistence/proc/LoadRecentStorytellers()
var/json_file = file("data/RecentStorytellers.json")
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
saved_storytellers = json["data"]
/datum/controller/subsystem/persistence/proc/LoadRecentMaps() /datum/controller/subsystem/persistence/proc/LoadRecentMaps()
var/json_file = file("data/RecentMaps.json") var/json_file = file("data/RecentMaps.json")
@@ -197,6 +210,15 @@ SUBSYSTEM_DEF(persistence)
return return
antag_rep = json_decode(json) antag_rep = json_decode(json)
/datum/controller/subsystem/persistence/proc/LoadSavedVote(var/ckey)
var/json_file = file("data/player_saves/[copytext(ckey,1,2)]/[ckey]/SavedVotes.json")
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
saved_votes[ckey] = json["data"]
/datum/controller/subsystem/persistence/proc/SetUpTrophies(list/trophy_items) /datum/controller/subsystem/persistence/proc/SetUpTrophies(list/trophy_items)
for(var/A in GLOB.trophy_cases) for(var/A in GLOB.trophy_cases)
var/obj/structure/displaycase/trophy/T = A var/obj/structure/displaycase/trophy/T = A
@@ -230,7 +252,7 @@ SUBSYSTEM_DEF(persistence)
CollectRoundtype() CollectRoundtype()
if(istype(SSticker.mode, /datum/game_mode/dynamic)) if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode var/datum/game_mode/dynamic/mode = SSticker.mode
CollectThreatLevel(mode) CollectStoryteller(mode)
CollectRulesets(mode) CollectRulesets(mode)
RecordMaps() RecordMaps()
SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION.
@@ -388,13 +410,16 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file) fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data)) WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/persistence/proc/CollectThreatLevel(var/datum/game_mode/dynamic/mode) /datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode)
saved_threat_levels[3] = saved_threat_levels[2] saved_storytellers.len = 5
saved_threat_levels[2] = saved_threat_levels [1] saved_storytellers[5] = saved_storytellers[4]
saved_threat_levels[1] = mode.threat_level saved_storytellers[4] = saved_storytellers[3]
var/json_file = file("data/RecentThreatLevels.json") saved_storytellers[3] = saved_storytellers[2]
saved_storytellers[2] = saved_storytellers[1]
saved_storytellers[1] = mode.storyteller.name
var/json_file = file("data/RecentStorytellers.json")
var/list/file_data = list() var/list/file_data = list()
file_data["data"] = saved_threat_levels file_data["data"] = saved_storytellers
fdel(json_file) fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data)) WRITE_FILE(json_file, json_encode(file_data))
@@ -402,8 +427,9 @@ SUBSYSTEM_DEF(persistence)
saved_dynamic_rules[3] = saved_dynamic_rules[2] saved_dynamic_rules[3] = saved_dynamic_rules[2]
saved_dynamic_rules[2] = saved_dynamic_rules[1] saved_dynamic_rules[2] = saved_dynamic_rules[1]
saved_dynamic_rules[1] = list() saved_dynamic_rules[1] = list()
for(var/datum/dynamic_ruleset/ruleset in mode.executed_rules) for(var/r in mode.executed_rules)
saved_dynamic_rules[1] += ruleset.config_tag var/datum/dynamic_ruleset/rule = r
saved_dynamic_rules[1] += rule.config_tag
var/json_file = file("data/RecentRulesets.json") var/json_file = file("data/RecentRulesets.json")
var/list/file_data = list() var/list/file_data = list()
file_data["data"] = saved_dynamic_rules file_data["data"] = saved_dynamic_rules
@@ -473,3 +499,11 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file) fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data)) WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/persistence/proc/SaveSavedVotes()
for(var/ckey in saved_votes)
var/json_file = file("data/player_saves/[copytext(ckey,1,2)]/[ckey]/SavedVotes.json")
var/list/file_data = list()
file_data["data"] = saved_votes[ckey]
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))

View File

@@ -483,7 +483,10 @@ SUBSYSTEM_DEF(ticker)
SSticker.timeLeft = 900 SSticker.timeLeft = 900
SSticker.modevoted = TRUE SSticker.modevoted = TRUE
var/dynamic = CONFIG_GET(flag/dynamic_voting) var/dynamic = CONFIG_GET(flag/dynamic_voting)
SSvote.initiate_vote(dynamic ? "dynamic" : "roundtype","server",TRUE) if(dynamic)
SSvote.initiate_vote("dynamic","server",hideresults=TRUE,votesystem=SCORE_VOTING,forced=TRUE,vote_time = 2 MINUTES)
else
SSvote.initiate_vote("roundtype","server",hideresults=TRUE,votesystem=PLURALITY_VOTING,forced=TRUE, vote_time = 2 MINUTES)
/datum/controller/subsystem/ticker/Recover() /datum/controller/subsystem/ticker/Recover()
current_state = SSticker.current_state current_state = SSticker.current_state

View File

@@ -1,3 +1,5 @@
#define VOTE_COOLDOWN 10
SUBSYSTEM_DEF(vote) SUBSYSTEM_DEF(vote)
name = "Vote" name = "Vote"
wait = 10 wait = 10
@@ -8,13 +10,17 @@ SUBSYSTEM_DEF(vote)
var/initiator = null var/initiator = null
var/started_time = null var/started_time = null
var/time_remaining = 0 var/end_time = 0
var/mode = null var/mode = null
var/vote_system = PLURALITY_VOTING
var/question = null var/question = null
var/list/choices = list() var/list/choices = list()
var/list/choice_descs = list() // optional descriptions
var/list/voted = list() var/list/voted = list()
var/list/voting = list() var/list/voting = list()
var/list/saved = list()
var/list/generated_actions = list() var/list/generated_actions = list()
var/next_pop = 0
var/obfuscated = FALSE//CIT CHANGE - adds obfuscated/admin-only votes var/obfuscated = FALSE//CIT CHANGE - adds obfuscated/admin-only votes
@@ -22,28 +28,30 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/fire() //called by master_controller /datum/controller/subsystem/vote/fire() //called by master_controller
if(mode) if(mode)
time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10) if(end_time < world.time)
if(time_remaining < 0)
result() result()
SSpersistence.SaveSavedVotes()
for(var/client/C in voting) for(var/client/C in voting)
C << browse(null, "window=vote;can_close=0") C << browse(null, "window=vote;can_close=0")
reset() reset()
else else if(next_pop < world.time)
var/datum/browser/client_popup var/datum/browser/client_popup
for(var/client/C in voting) for(var/client/C in voting)
client_popup = new(C, "vote", "Voting Panel") client_popup = new(C, "vote", "Voting Panel", nwidth=600,nheight=700)
client_popup.set_window_options("can_close=0") client_popup.set_window_options("can_close=0")
client_popup.set_content(interface(C)) client_popup.set_content(interface(C))
client_popup.open(0) client_popup.open(0)
next_pop = world.time+VOTE_COOLDOWN
/datum/controller/subsystem/vote/proc/reset() /datum/controller/subsystem/vote/proc/reset()
initiator = null initiator = null
time_remaining = 0 end_time = 0
mode = null mode = null
question = null question = null
choices.Cut() choices.Cut()
choice_descs.Cut()
voted.Cut() voted.Cut()
voting.Cut() voting.Cut()
obfuscated = FALSE //CIT CHANGE - obfuscated votes obfuscated = FALSE //CIT CHANGE - obfuscated votes
@@ -84,17 +92,114 @@ SUBSYSTEM_DEF(vote)
. += option . += option
return . return .
/datum/controller/subsystem/vote/proc/calculate_condorcet_votes(var/blackbox_text)
// https://en.wikipedia.org/wiki/Schulze_method#Implementation
var/list/d[][] = new/list(choices.len,choices.len) // the basic vote matrix, how many times a beats b
for(var/ckey in voted)
var/list/this_vote = voted[ckey]
for(var/a in 1 to choices.len)
for(var/b in a+1 to choices.len)
var/a_rank = this_vote.Find(a)
var/b_rank = this_vote.Find(b)
a_rank = a_rank ? a_rank : choices.len+1
b_rank = b_rank ? b_rank : choices.len+1
if(a_rank<b_rank)
d[a][b]++
else if(b_rank<a_rank)
d[b][a]++
//if equal, do nothing
var/list/p[][] = new/list(choices.len,choices.len) //matrix of shortest path from a to b
for(var/i in 1 to choices.len)
for(var/j in 1 to choices.len)
if(i != j)
var/pref_number = d[i][j]
var/opposite_pref = d[j][i]
if(pref_number>opposite_pref)
p[i][j] = d[i][j]
p[j][i] = 0
else
p[i][j] = 0
p[j][i] = d[i][j]
for(var/i in 1 to choices.len)
for(var/j in 1 to choices.len)
if(i != j)
for(var/k in 1 to choices.len) // YEAH O(n^3) !!
if(i != k && j != k)
p[j][k] = max(p[j][k],min(p[j][i], p[i][k]))
//one last pass, now that we've done the math
for(var/i in 1 to choices.len)
for(var/j in 1 to choices.len)
if(i != j)
SSblackbox.record_feedback("nested tally","voting",p[i][j],list(blackbox_text,"Shortest Paths",choices[i],choices[j]))
if(p[i][j] >= p[j][i])
choices[choices[i]]++ // higher shortest path = better candidate, so we add to choices here
// choices[choices[i]] is the schulze ranking, here, rather than raw vote numbers
/datum/controller/subsystem/vote/proc/calculate_majority_judgement_vote(var/blackbox_text)
// https://en.wikipedia.org/wiki/Majority_judgment
var/list/scores_by_choice = list()
for(var/choice in choices)
scores_by_choice[choice] = list()
for(var/ckey in voted)
var/list/this_vote = voted[ckey]
var/list/pretty_vote = list()
for(var/choice in this_vote)
sorted_insert(scores_by_choice[choice],this_vote[choice],/proc/cmp_numeric_asc)
// START BALLOT GATHERING
pretty_vote += choice
pretty_vote[choice] = GLOB.vote_score_options[this_vote[choice]]
SSblackbox.record_feedback("associative","voting_ballots",1,pretty_vote)
// END BALLOT GATHERING
for(var/score_name in scores_by_choice)
var/list/score = scores_by_choice[score_name]
for(var/indiv_score in score)
SSblackbox.record_feedback("nested tally","voting",1,list(blackbox_text,"Scores",score_name,GLOB.vote_score_options[indiv_score]))
if(score.len == 0)
scores_by_choice -= score_name
while(scores_by_choice.len > 1)
var/highest_median = 0
for(var/score_name in scores_by_choice) // first get highest median
var/list/score = scores_by_choice[score_name]
if(!score.len)
scores_by_choice -= score_name
continue
var/median = score[max(1,round(score.len/2))]
if(median >= highest_median)
highest_median = median
for(var/score_name in scores_by_choice) // then, remove
var/list/score = scores_by_choice[score_name]
var/median = score[max(1,round(score.len/2))]
if(median < highest_median)
scores_by_choice -= score_name
for(var/score_name in scores_by_choice) // after removals
var/list/score = scores_by_choice[score_name]
if(score.len == 0)
choices[score_name] += 100 // we're in a tie situation--just go with the first one
return
var/median_pos = max(1,round(score.len/2))
score.Cut(median_pos,median_pos+1)
choices[score_name]++
/datum/controller/subsystem/vote/proc/announce_result() /datum/controller/subsystem/vote/proc/announce_result()
var/list/winners = get_result() var/vote_title_text
var/text var/text
if(question)
text += "<b>[question]</b>"
vote_title_text = "[question]"
else
text += "<b>[capitalize(mode)] Vote</b>"
vote_title_text = "[capitalize(mode)] Vote"
if(vote_system == RANKED_CHOICE_VOTING)
calculate_condorcet_votes(vote_title_text)
if(vote_system == SCORE_VOTING)
calculate_majority_judgement_vote(vote_title_text)
var/list/winners = get_result()
var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic" var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic"
if(winners.len > 0) if(winners.len > 0)
if(question)
text += "<b>[question]</b>"
else
text += "<b>[capitalize(mode)] Vote</b>"
if(was_roundtype_vote) if(was_roundtype_vote)
stored_gamemode_votes = list() stored_gamemode_votes = list()
if(!obfuscated && vote_system == RANKED_CHOICE_VOTING)
text += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!"
for(var/i=1,i<=choices.len,i++) for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]] var/votes = choices[choices[i]]
if(!votes) if(!votes)
@@ -116,17 +221,25 @@ SUBSYSTEM_DEF(vote)
log_vote(text) log_vote(text)
remove_action_buttons() remove_action_buttons()
to_chat(world, "\n<font color='purple'>[text]</font>") to_chat(world, "\n<font color='purple'>[text]</font>")
switch(vote_system)
if(APPROVAL_VOTING,PLURALITY_VOTING)
for(var/i=1,i<=choices.len,i++)
SSblackbox.record_feedback("nested tally","voting",choices[choices[i]],list(vote_title_text,choices[i]))
if(RANKED_CHOICE_VOTING)
for(var/i=1,i<=voted.len,i++)
var/list/myvote = voted[voted[i]]
for(var/j=1,j<=myvote.len,j++)
SSblackbox.record_feedback("nested tally","voting",1,list(vote_title_text,"[j]\th",choices[myvote[j]]))
if(obfuscated) //CIT CHANGE - adds obfuscated votes. this messages admins with the vote's true results if(obfuscated) //CIT CHANGE - adds obfuscated votes. this messages admins with the vote's true results
var/admintext = "Obfuscated results" var/admintext = "Obfuscated results"
if(vote_system == RANKED_CHOICE_VOTING)
admintext += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!"
for(var/i=1,i<=choices.len,i++) for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]] var/votes = choices[choices[i]]
admintext += "\n<b>[choices[i]]:</b> [votes]" admintext += "\n<b>[choices[i]]:</b> [votes]"
message_admins(admintext) message_admins(admintext)
return . return .
#define PEACE "calm"
#define CHAOS "chaotic"
/datum/controller/subsystem/vote/proc/result() /datum/controller/subsystem/vote/proc/result()
. = announce_result() . = announce_result()
var/restart = 0 var/restart = 0
@@ -152,33 +265,15 @@ SUBSYSTEM_DEF(vote)
if("dynamic") if("dynamic")
if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started.
return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.")
GLOB.master_mode = "dynamic" if(. == "Secret")
if("extended" in choices) GLOB.master_mode = "secret"
if(. == "extended") SSticker.save_mode(.)
GLOB.dynamic_forced_extended = TRUE // we still do the rest of the stuff message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]")
choices[PEACE] += choices["extended"] log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].")
var/mean = 0 else
var/voters = 0 GLOB.master_mode = "dynamic"
for(var/client/c in GLOB.clients) var/datum/dynamic_storyteller/S = config.pick_storyteller(.)
var/vote = c.prefs.preferred_chaos GLOB.dynamic_storyteller_type = S
if(vote)
voters += 1
switch(vote)
if(CHAOS_NONE)
mean -= 0.1
if(CHAOS_LOW)
mean -= 0.05
if(CHAOS_HIGH)
mean += 0.05
if(CHAOS_MAX)
mean += 0.1
mean/=voters
if(voted.len != 0)
mean += (choices[PEACE]*-1+choices[CHAOS])/voted.len
GLOB.dynamic_curve_centre = mean*20
GLOB.dynamic_curve_width = CLAMP(2-abs(mean*5),0.5,4)
to_chat(world,"<span class='boldannounce'>Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width].</span>")
log_admin("Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width]")
if("map") if("map")
var/datum/map_config/VM = config.maplist[.] var/datum/map_config/VM = config.maplist[.]
message_admins("The map has been voted for and will change to: [VM.map_name]") message_admins("The map has been voted for and will change to: [VM.map_name]")
@@ -196,27 +291,58 @@ SUBSYSTEM_DEF(vote)
else else
to_chat(world, "<span style='boldannounce'>Notice:Restart vote will not restart the server automatically because there are active admins on.</span>") to_chat(world, "<span style='boldannounce'>Notice:Restart vote will not restart the server automatically because there are active admins on.</span>")
message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.") message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.")
return . return .
/datum/controller/subsystem/vote/proc/submit_vote(vote) /datum/controller/subsystem/vote/proc/submit_vote(vote, score = 0)
if(mode) if(mode)
if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder) if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder)
return 0 return 0
if(!(usr.ckey in voted)) if(vote && ISINRANGE(vote, 1, choices.len))
if(vote && 1<=vote && vote<=choices.len) switch(vote_system)
voted += usr.ckey if(PLURALITY_VOTING)
voted[usr.ckey] = vote if(usr.ckey in voted)
choices[choices[vote]]++ //check this choices[choices[voted[usr.ckey]]]--
return vote voted[usr.ckey] = vote
else if(vote && 1<=vote && vote<=choices.len) choices[choices[vote]]++
choices[choices[voted[usr.ckey]]]-- return vote
voted[usr.ckey] = vote else
choices[choices[vote]]++ voted += usr.ckey
return vote voted[usr.ckey] = vote
choices[choices[vote]]++ //check this
return vote
if(APPROVAL_VOTING)
if(usr.ckey in voted)
if(vote in voted[usr.ckey])
voted[usr.ckey] -= vote
choices[choices[vote]]--
else
voted[usr.ckey] += vote
choices[choices[vote]]++
else
voted += usr.ckey
voted[usr.ckey] = list(vote)
choices[choices[vote]]++
return vote
if(RANKED_CHOICE_VOTING)
if(usr.ckey in voted)
if(vote in voted[usr.ckey])
voted[usr.ckey] -= vote
else
voted += usr.ckey
voted[usr.ckey] = list()
voted[usr.ckey] += vote
saved -= usr.ckey
if(SCORE_VOTING)
if(!(usr.ckey in voted))
voted += usr.ckey
voted[usr.ckey] = list()
voted[usr.ckey][choices[vote]] = score
saved -= usr.ckey
return 0 return 0
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes /datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults, votesystem = PLURALITY_VOTING, forced = FALSE,vote_time = -1)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes
vote_system = votesystem
if(!mode) if(!mode)
if(started_time) if(started_time)
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay)) var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
@@ -257,11 +383,17 @@ SUBSYSTEM_DEF(vote)
if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote
choices.Add("secret", "extended") choices.Add("secret", "extended")
if("dynamic") if("dynamic")
var/saved_threats = SSpersistence.saved_threat_levels for(var/T in config.storyteller_cache)
if((saved_threats[1]+saved_threats[2]+saved_threats[3])>150) var/datum/dynamic_storyteller/S = T
choices.Add("extended",PEACE,CHAOS) var/recent_rounds = 0
else for(var/i in 1 to SSpersistence.saved_storytellers.len)
choices.Add(PEACE,CHAOS) if(SSpersistence.saved_storytellers[i] == initial(S.name))
recent_rounds++
if(recent_rounds < initial(S.weight))
choices.Add(initial(S.name))
choice_descs.Add(initial(S.desc))
choices.Add("Secret")
choice_descs.Add("Standard secret. Switches mode if it wins.")
if("custom") if("custom")
question = stripped_input(usr,"What is the vote for?") question = stripped_input(usr,"What is the vote for?")
if(!question) if(!question)
@@ -280,9 +412,11 @@ SUBSYSTEM_DEF(vote)
if(mode == "custom") if(mode == "custom")
text += "\n[question]" text += "\n[question]"
log_vote(text) log_vote(text)
var/vp = CONFIG_GET(number/vote_period) var/vp = vote_time
if(vp == -1)
vp = CONFIG_GET(number/vote_period)
to_chat(world, "\n<font color='purple'><b>[text]</b>\nType <b>vote</b> or click <a href='?src=[REF(src)]'>here</a> to place your votes.\nYou have [DisplayTimeText(vp)] to vote.</font>") to_chat(world, "\n<font color='purple'><b>[text]</b>\nType <b>vote</b> or click <a href='?src=[REF(src)]'>here</a> to place your votes.\nYou have [DisplayTimeText(vp)] to vote.</font>")
time_remaining = round(vp/10) end_time = started_time+vp
for(var/c in GLOB.clients) for(var/c in GLOB.clients)
SEND_SOUND(c, sound('sound/misc/server-ready.ogg')) SEND_SOUND(c, sound('sound/misc/server-ready.ogg'))
var/client/C = c var/client/C = c
@@ -292,6 +426,11 @@ SUBSYSTEM_DEF(vote)
C.player_details.player_actions += V C.player_details.player_actions += V
V.Grant(C.mob) V.Grant(C.mob)
generated_actions += V generated_actions += V
if(forced)
var/datum/browser/popup = new(C, "vote", "Voting Panel",nwidth=600,nheight=700)
popup.set_window_options("can_close=0")
popup.set_content(SSvote.interface(C))
popup.open(0)
return 1 return 1
return 0 return 0
@@ -311,14 +450,71 @@ SUBSYSTEM_DEF(vote)
. += "<h2>Vote: '[question]'</h2>" . += "<h2>Vote: '[question]'</h2>"
else else
. += "<h2>Vote: [capitalize(mode)]</h2>" . += "<h2>Vote: [capitalize(mode)]</h2>"
. += "Time Left: [time_remaining] s<hr><ul>" switch(vote_system)
for(var/i=1,i<=choices.len,i++) if(PLURALITY_VOTING)
var/votes = choices[choices[i]] . += "<h3>Vote one.</h3>"
var/ivotedforthis = ((C.ckey in voted) && (voted[C.ckey] == i) ? TRUE : FALSE) if(APPROVAL_VOTING)
if(!votes) . += "<h3>Vote any number of choices.</h3>"
votes = 0 if(RANKED_CHOICE_VOTING)
. += "<li>[ivotedforthis ? "<b>" : ""]<a href='?src=[REF(src)];vote=[i]'>[choices[i]]</a> ([obfuscated ? (admin ? "??? ([votes])" : "???") : votes] votes)[ivotedforthis ? "</b>" : ""]</li>" // CIT CHANGE - adds obfuscated votes . += "<h3>Vote by order of preference. Revoting will demote to the bottom. 1 is your favorite, and higher numbers are worse.</h3>"
. += "</ul><hr>" if(SCORE_VOTING)
. += "<h3>Grade the candidates by how much you like them.</h3>"
. += "<h3>No-votes have no power--your opinion is only heard if you vote!</h3>"
. += "Time Left: [DisplayTimeText(end_time-world.time)]<hr><ul>"
switch(vote_system)
if(PLURALITY_VOTING, APPROVAL_VOTING)
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
var/ivotedforthis = FALSE
switch(vote_system)
if(PLURALITY_VOTING)
ivotedforthis = ((C.ckey in voted) && (voted[C.ckey] == i))
if(APPROVAL_VOTING)
ivotedforthis = ((C.ckey in voted) && (i in voted[C.ckey]))
if(!votes)
votes = 0
. += "<li>[ivotedforthis ? "<b>" : ""]<a href='?src=[REF(src)];vote=[i]'>[choices[i]]</a> ([obfuscated ? (admin ? "??? ([votes])" : "???") : votes] votes)[ivotedforthis ? "</b>" : ""]</li>" // CIT CHANGE - adds obfuscated votes
if(choice_descs.len >= i)
. += "<li>[choice_descs[i]]</li>"
. += "</ul><hr>"
if(RANKED_CHOICE_VOTING)
var/list/myvote = voted[C.ckey]
for(var/i=1,i<=choices.len,i++)
var/vote = (myvote ? (myvote.Find(i)) : 0)
if(vote)
. += "<li><b><a href='?src=[REF(src)];vote=[i]'>[choices[i]]</a> ([vote])</b></li>"
else
. += "<li><a href='?src=[REF(src)];vote=[i]'>[choices[i]]</a></li>"
if(choice_descs.len >= i)
. += "<li>[choice_descs[i]]</li>"
. += "</ul><hr>"
if(!(C.ckey in saved))
. += "(<a href='?src=[REF(src)];vote=save'>Save vote</a>)"
else
. += "(Saved!)"
. += "(<a href='?src=[REF(src)];vote=load'>Load vote from save</a>)"
. += "(<a href='?src=[REF(src)];vote=reset'>Reset votes</a>)"
if(SCORE_VOTING)
var/list/myvote = voted[C.ckey]
for(var/i=1,i<=choices.len,i++)
. += "<li><b>[choices[i]]</b>"
for(var/r in 1 to GLOB.vote_score_options.len)
. += " <a href='?src=[REF(src)];vote=[i];score=[r]'>"
if((choices[i] in myvote) && myvote[choices[i]] == r)
. +="<b>([GLOB.vote_score_options[r]])</b>"
else
. +="[GLOB.vote_score_options[r]]"
. += "</a>"
. += "</li>"
if(choice_descs.len >= i)
. += "<li>[choice_descs[i]]</li>"
. += "</ul><hr>"
if(!(C.ckey in saved))
. += "(<a href='?src=[REF(src)];vote=save'>Save vote</a>)"
else
. += "(Saved!)"
. += "(<a href='?src=[REF(src)];vote=load'>Load vote from save</a>)"
. += "(<a href='?src=[REF(src)];vote=reset'>Reset votes</a>)"
if(admin) if(admin)
. += "(<a href='?src=[REF(src)];vote=cancel'>Cancel Vote</a>) " . += "(<a href='?src=[REF(src)];vote=cancel'>Cancel Vote</a>) "
else else
@@ -376,8 +572,31 @@ SUBSYSTEM_DEF(vote)
if("custom") if("custom")
if(usr.client.holder) if(usr.client.holder)
initiate_vote("custom",usr.key) initiate_vote("custom",usr.key)
if("reset")
if(usr.ckey in voted)
voted -= usr.ckey
if("save")
if(usr.ckey in voted)
if(!(usr.ckey in SSpersistence.saved_votes))
SSpersistence.saved_votes[usr.ckey] = list()
SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey]
saved += usr.ckey
if("load")
if(!(usr.ckey in SSpersistence.saved_votes))
SSpersistence.LoadSavedVote(usr.ckey)
if(!(usr.ckey in SSpersistence.saved_votes))
SSpersistence.saved_votes[usr.ckey] = list()
if(usr.ckey in voted)
SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey]
else
SSpersistence.saved_votes[usr.ckey][mode] = list()
voted[usr.ckey] = SSpersistence.saved_votes[usr.ckey][mode]
saved += usr.ckey
else else
submit_vote(round(text2num(href_list["vote"]))) if(vote_system == SCORE_VOTING)
submit_vote(round(text2num(href_list["vote"])),round(text2num(href_list["score"])))
else
submit_vote(round(text2num(href_list["vote"])))
usr.vote() usr.vote()
/datum/controller/subsystem/vote/proc/remove_action_buttons() /datum/controller/subsystem/vote/proc/remove_action_buttons()
@@ -392,7 +611,7 @@ SUBSYSTEM_DEF(vote)
set category = "OOC" set category = "OOC"
set name = "Vote" set name = "Vote"
var/datum/browser/popup = new(src, "vote", "Voting Panel") var/datum/browser/popup = new(src, "vote", "Voting Panel",nwidth=600,nheight=700)
popup.set_window_options("can_close=0") popup.set_window_options("can_close=0")
popup.set_content(SSvote.interface(client)) popup.set_content(SSvote.interface(client))
popup.open(0) popup.open(0)
@@ -419,6 +638,3 @@ SUBSYSTEM_DEF(vote)
var/datum/player_details/P = GLOB.player_details[owner.ckey] var/datum/player_details/P = GLOB.player_details[owner.ckey]
if(P) if(P)
P.player_actions -= src P.player_actions -= src
#undef PEACE
#undef CHAOS

View File

@@ -1,14 +1,3 @@
#define CURRENT_LIVING_PLAYERS 1
#define CURRENT_LIVING_ANTAGS 2
#define CURRENT_DEAD_PLAYERS 3
#define CURRENT_OBSERVERS 4
#define ONLY_RULESET 1
#define HIGHLANDER_RULESET 2
#define TRAITOR_RULESET 4
#define MINOR_RULESET 8
#define RULESET_STOP_PROCESSING 1
// -- Injection delays // -- Injection delays
GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (10 MINUTES)) GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (10 MINUTES))
@@ -52,6 +41,8 @@ GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset)
// Forced threat level, setting this to zero or higher forces the roundstart threat to the value. // Forced threat level, setting this to zero or higher forces the roundstart threat to the value.
GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
GLOBAL_VAR_INIT(dynamic_storyteller_type, null)
/datum/game_mode/dynamic /datum/game_mode/dynamic
name = "dynamic mode" name = "dynamic mode"
config_tag = "dynamic" config_tag = "dynamic"
@@ -60,7 +51,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
announce_text = "Dynamic mode!" // This needs to be changed maybe announce_text = "Dynamic mode!" // This needs to be changed maybe
reroll_friendly = FALSE; reroll_friendly = FALSE;
// Current storyteller
var/datum/dynamic_storyteller/storyteller = null
// Threat logging vars // Threat logging vars
/// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations /// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations
var/threat_level = 0 var/threat_level = 0
@@ -164,6 +156,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
dat += "Threat to Spend: <b>[threat]</b> <a href='?src=\ref[src];[HrefToken()];adjustthreat=1'>\[Adjust\]</A> <a href='?src=\ref[src];[HrefToken()];threatlog=1'>\[View Log\]</a><br/>" dat += "Threat to Spend: <b>[threat]</b> <a href='?src=\ref[src];[HrefToken()];adjustthreat=1'>\[Adjust\]</A> <a href='?src=\ref[src];[HrefToken()];threatlog=1'>\[View Log\]</a><br/>"
dat += "<br/>" dat += "<br/>"
dat += "Storyteller: <b>[storyteller.name]</b><br/>"
dat += "Parameters: centre = [GLOB.dynamic_curve_centre] ; width = [GLOB.dynamic_curve_width].<br/>" dat += "Parameters: centre = [GLOB.dynamic_curve_centre] ; width = [GLOB.dynamic_curve_width].<br/>"
dat += "<i>On average, <b>[peaceful_percentage]</b>% of the rounds are more peaceful.</i><br/>" dat += "<i>On average, <b>[peaceful_percentage]</b>% of the rounds are more peaceful.</i><br/>"
dat += "Forced extended: <a href='?src=\ref[src];[HrefToken()];forced_extended=1'><b>[GLOB.dynamic_forced_extended ? "On" : "Off"]</b></a><br/>" dat += "Forced extended: <a href='?src=\ref[src];[HrefToken()];forced_extended=1'><b>[GLOB.dynamic_forced_extended ? "On" : "Off"]</b></a><br/>"
@@ -178,7 +171,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
dat += "[DR.ruletype] - <b>[DR.name]</b><br>" dat += "[DR.ruletype] - <b>[DR.name]</b><br>"
else else
dat += "none.<br>" dat += "none.<br>"
dat += "<br>Injection Timers: (<b>[get_injection_chance(TRUE)]%</b> chance)<BR>" dat += "<br>Injection Timers: (<b>[storyteller.get_injection_chance(TRUE)]%</b> chance)<BR>"
dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] <a href='?src=\ref[src];[HrefToken()];injectlate=1'>\[Now!\]</a><BR>" dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] <a href='?src=\ref[src];[HrefToken()];injectlate=1'>\[Now!\]</a><BR>"
dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] <a href='?src=\ref[src];[HrefToken()];injectmid=1'>\[Now!\]</a><BR>" dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] <a href='?src=\ref[src];[HrefToken()];injectmid=1'>\[Now!\]</a><BR>"
dat += "Event: [(event_injection_cooldown-world.time)>60*10 ? "[round((event_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(event_injection_cooldown-world.time)] seconds"] <a href='?src=\ref[src];[HrefToken()];forceevent=1'>\[Now!\]</a><BR>" dat += "Event: [(event_injection_cooldown-world.time)>60*10 ? "[round((event_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(event_injection_cooldown-world.time)] seconds"] <a href='?src=\ref[src];[HrefToken()];forceevent=1'>\[Now!\]</a><BR>"
@@ -336,6 +329,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
SSblackbox.record_feedback("tally","dynamic_threat",peaceful_percentage,"Percent of same-vote rounds that are more peaceful") SSblackbox.record_feedback("tally","dynamic_threat",peaceful_percentage,"Percent of same-vote rounds that are more peaceful")
/datum/game_mode/dynamic/can_start() /datum/game_mode/dynamic/can_start()
storyteller = new GLOB.dynamic_storyteller_type // this is where all the initialization happens
storyteller.on_start()
SSblackbox.record_feedback("text","dynamic_storyteller",1,storyteller.name)
message_admins("Dynamic mode parameters for the round:") message_admins("Dynamic mode parameters for the round:")
message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].") message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].") message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].")
@@ -345,19 +341,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(GLOB.dynamic_forced_threat_level >= 0) if(GLOB.dynamic_forced_threat_level >= 0)
threat_level = round(GLOB.dynamic_forced_threat_level, 0.1) threat_level = round(GLOB.dynamic_forced_threat_level, 0.1)
threat = threat_level threat = threat_level
SSblackbox.record_feedback("tally","dynamic_threat",threat_level,"Threat level (forced by admins)") SSblackbox.record_feedback("tally","dynamic_threat",threat_level,"Threat level (forced)")
else else
generate_threat() generate_threat()
var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_latejoin_delay_max + GLOB.dynamic_first_latejoin_delay_min) storyteller.start_injection_cooldowns()
latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_first_latejoin_delay_min, GLOB.dynamic_first_latejoin_delay_max)) + world.time
var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_midround_delay_min + GLOB.dynamic_first_midround_delay_max)
midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_first_midround_delay_min, GLOB.dynamic_first_midround_delay_max)) + world.time
var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!") log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!")
initial_threat_level = threat_level initial_threat_level = threat_level
return TRUE return TRUE
@@ -391,7 +380,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
log_game("DYNAMIC: [roundstart_rules.len] rules.") log_game("DYNAMIC: [roundstart_rules.len] rules.")
return TRUE return TRUE
SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Roundstart rules considered") SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Roundstart rules considered")
SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Players readied up") SSblackbox.record_feedback("tally","dynamic",roundstart_pop_ready,"Players readied up")
if(GLOB.dynamic_forced_roundstart_ruleset.len > 0) if(GLOB.dynamic_forced_roundstart_ruleset.len > 0)
rigged_roundstart() rigged_roundstart()
else else
@@ -429,13 +418,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (GLOB.dynamic_forced_extended) if (GLOB.dynamic_forced_extended)
log_game("DYNAMIC: Starting a round of forced extended.") log_game("DYNAMIC: Starting a round of forced extended.")
return TRUE return TRUE
var/list/drafted_rules = list() var/list/drafted_rules = storyteller.roundstart_draft()
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
rule.candidates = candidates.Copy()
rule.trim_candidates()
if (rule.ready() && rule.candidates.len > 0)
drafted_rules[rule] = rule.weight
if(!drafted_rules.len) if(!drafted_rules.len)
message_admins("Not enough threat level for roundstart antags!") message_admins("Not enough threat level for roundstart antags!")
log_game("DYNAMIC: Not enough threat level for roundstart antags!") log_game("DYNAMIC: Not enough threat level for roundstart antags!")
@@ -653,7 +636,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (rule.persistent) if (rule.persistent)
current_rules += rule current_rules += rule
return TRUE return TRUE
rule.clean_up()
stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.") stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
return FALSE return FALSE
@@ -667,62 +649,41 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
current_rules -= rule current_rules -= rule
SSblackbox.record_feedback("tally","dynamic",1,"Rulesets finished") SSblackbox.record_feedback("tally","dynamic",1,"Rulesets finished")
SSblackbox.record_feedback("associative","dynamic_rulesets_finished",1,rule.get_blackbox_info()) SSblackbox.record_feedback("associative","dynamic_rulesets_finished",1,rule.get_blackbox_info())
storyteller.do_process()
if (midround_injection_cooldown < world.time) if (midround_injection_cooldown < world.time)
if (GLOB.dynamic_forced_extended) if (GLOB.dynamic_forced_extended)
return return
// Somehow it managed to trigger midround multiple times so this was moved here. // Somehow it managed to trigger midround multiple times so this was moved here.
// There is no way this should be able to trigger an injection twice now. // There is no way this should be able to trigger an injection twice now.
var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min) midround_injection_cooldown = storyteller.get_midround_cooldown() + world.time
midround_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time)
// Time to inject some threat into the round // Time to inject some threat into the round
if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone
return return
if((world.realtime - SSshuttle.realtimeofstart) > SSshuttle.auto_call) // no rules after shuttle is auto-called
return
message_admins("DYNAMIC: Checking for midround injection.") message_admins("DYNAMIC: Checking for midround injection.")
log_game("DYNAMIC: Checking for midround injection.") log_game("DYNAMIC: Checking for midround injection.")
update_playercounts() update_playercounts()
if (get_injection_chance()) if (prob(storyteller.get_injection_chance()))
SSblackbox.record_feedback("tally","dynamic",1,"Attempted midround injections") SSblackbox.record_feedback("tally","dynamic",1,"Attempted midround injections")
var/cur_threat_frac = threat/threat_level var/list/drafted_rules = storyteller.midround_draft()
var/list/drafted_rules = list()
var/antag_num = current_players[CURRENT_LIVING_ANTAGS].len
for (var/datum/dynamic_ruleset/midround/rule in midround_rules)
// if there are antags OR the rule is an antag rule, antag_acceptable will be true.
if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
continue
rule.trim_candidates()
if (rule.ready())
if(!antag_num)
drafted_rules[rule] = round(rule.get_weight() + (rule.cost * cur_threat_frac))
else
drafted_rules[rule] = rule.get_weight()
else if(threat < rule.cost)
SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
if (drafted_rules.len > 0) if (drafted_rules.len > 0)
SSblackbox.record_feedback("tally","dynamic",1,"Successful midround injections") SSblackbox.record_feedback("tally","dynamic",1,"Successful midround injections")
picking_midround_latejoin_rule(drafted_rules) picking_midround_latejoin_rule(drafted_rules)
else // get_injection_chance can do things on fail
midround_injection_cooldown = (midround_injection_cooldown + world.time)/2
if(event_injection_cooldown < world.time) if(event_injection_cooldown < world.time)
SSblackbox.record_feedback("tally","dynamic",1,"Attempted event injections") SSblackbox.record_feedback("tally","dynamic",1,"Attempted event injections")
var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min) event_injection_cooldown = storyteller.get_event_cooldown() + world.time
event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
message_admins("DYNAMIC: Doing event injection.") message_admins("DYNAMIC: Doing event injection.")
log_game("DYNAMIC: Doing event injection.") log_game("DYNAMIC: Doing event injection.")
update_playercounts() update_playercounts()
var/list/drafted_rules = list() var/list/drafted_rules = storyteller.event_draft()
for(var/datum/dynamic_ruleset/event/rule in events)
if(rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
if(rule.ready())
drafted_rules[rule] = rule.get_weight()
else if(threat < rule.cost)
SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
if(drafted_rules.len > 0) if(drafted_rules.len > 0)
SSblackbox.record_feedback("tally","dynamic",1,"Successful event injections") SSblackbox.record_feedback("tally","dynamic",1,"Successful event injections")
picking_midround_latejoin_rule(drafted_rules) picking_midround_latejoin_rule(drafted_rules)
@@ -748,31 +709,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
continue continue
current_players[CURRENT_DEAD_PLAYERS].Add(M) // Players who actually died (and admins who ghosted, would be nice to avoid counting them somehow) current_players[CURRENT_DEAD_PLAYERS].Add(M) // Players who actually died (and admins who ghosted, would be nice to avoid counting them somehow)
/// Gets the chance for latejoin and midround injection, the dry_run argument is only used for forced injection.
/datum/game_mode/dynamic/proc/get_injection_chance(dry_run = FALSE)
if(forced_injection)
forced_injection = !dry_run
return 100
var/chance = 0
// If the high pop override is in effect, we reduce the impact of population on the antag injection chance
var/high_pop_factor = (current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit)
var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5)))
if (!current_players[CURRENT_LIVING_ANTAGS].len)
chance += 80 // No antags at all? let's boost those odds!
else
var/current_pop_per_antag = current_players[CURRENT_LIVING_PLAYERS].len / current_players[CURRENT_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 (current_players[CURRENT_DEAD_PLAYERS].len > current_players[CURRENT_LIVING_PLAYERS].len)
chance -= 30 // More than half the crew died? ew, let's calm down on antags
if (threat > 70)
chance += 15
if (threat < 30)
chance -= 15
return round(max(0,chance))
/// Removes type from the list /// Removes type from the list
/datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type) /datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type)
for(var/I in type_list) for(var/I in type_list)
@@ -803,7 +739,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return return
if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left
return return
if((world.realtime - SSshuttle.realtimeofstart) > SSshuttle.auto_call) // no rules after shuttle is auto-called
return
update_playercounts() update_playercounts()
if (forced_latejoin_rule) if (forced_latejoin_rule)
@@ -814,28 +751,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
picking_midround_latejoin_rule(list(forced_latejoin_rule), forced = TRUE) picking_midround_latejoin_rule(list(forced_latejoin_rule), forced = TRUE)
forced_latejoin_rule = null forced_latejoin_rule = null
else if (latejoin_injection_cooldown < world.time && prob(get_injection_chance())) else if (latejoin_injection_cooldown < world.time && prob(storyteller.get_injection_chance()))
SSblackbox.record_feedback("tally","dynamic",1,"Attempted latejoin injections") SSblackbox.record_feedback("tally","dynamic",1,"Attempted latejoin injections")
var/list/drafted_rules = list() var/list/drafted_rules = storyteller.latejoin_draft(newPlayer)
for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules)
if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
continue
// No stacking : only one round-ender, unless threat level > stacking_limit.
if (threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
if(rule.flags & HIGHLANDER_RULESET && highlander_executed)
continue
rule.candidates = list(newPlayer)
rule.trim_candidates()
if (rule.ready())
drafted_rules[rule] = rule.get_weight()
if (drafted_rules.len > 0 && picking_midround_latejoin_rule(drafted_rules)) if (drafted_rules.len > 0 && picking_midround_latejoin_rule(drafted_rules))
SSblackbox.record_feedback("tally","dynamic",1,"Successful latejoin injections") SSblackbox.record_feedback("tally","dynamic",1,"Successful latejoin injections")
var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min) latejoin_injection_cooldown = storyteller.get_latejoin_cooldown() + world.time
latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time
/// Refund threat, but no more than threat_level. /// Refund threat, but no more than threat_level.
/datum/game_mode/dynamic/proc/refund_threat(regain) /datum/game_mode/dynamic/proc/refund_threat(regain)

View File

@@ -80,9 +80,13 @@
/// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin). /// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin).
/// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure. /// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure.
var/delay = 0 var/delay = 0
/// List of tags for use in storytellers.
var/list/property_weights = list()
/// Whether or not recent-round weight values are taken into account for this ruleset. /// Whether or not recent-round weight values are taken into account for this ruleset.
/// Weight reduction uses the same values as secret's recent-round mode weight reduction. /// Weight reduction uses the same values as secret's recent-round mode weight reduction.
var/always_max_weight = FALSE var/always_max_weight = FALSE
/// Weight reduction by recent-rounds. Saved on new.
var/weight_mult = 1
/datum/dynamic_ruleset/New() /datum/dynamic_ruleset/New()
..() ..()
@@ -96,13 +100,11 @@
var/high_population_requirements = CONFIG_GET(keyed_list/dynamic_high_population_requirement) var/high_population_requirements = CONFIG_GET(keyed_list/dynamic_high_population_requirement)
var/list/repeated_mode_adjust = CONFIG_GET(number_list/repeated_mode_adjust) var/list/repeated_mode_adjust = CONFIG_GET(number_list/repeated_mode_adjust)
if(config_tag in weights) if(config_tag in weights)
var/weight_mult = 1
if(!always_max_weight && SSpersistence.saved_dynamic_rules.len == 3 && repeated_mode_adjust.len == 3) if(!always_max_weight && SSpersistence.saved_dynamic_rules.len == 3 && repeated_mode_adjust.len == 3)
var/saved_dynamic_rules = SSpersistence.saved_dynamic_rules var/saved_dynamic_rules = SSpersistence.saved_dynamic_rules
for(var/i in 1 to 3) for(var/i in 1 to 3)
if(config_tag in saved_dynamic_rules[i]) if(config_tag in saved_dynamic_rules[i])
weight_mult -= (repeated_mode_adjust[i]/100) weight_mult -= (repeated_mode_adjust[i]/100)
weight = weights[config_tag] * weight_mult
if(config_tag in costs) if(config_tag in costs)
cost = costs[config_tag] cost = costs[config_tag]
if(config_tag in requirementses) if(config_tag in requirementses)

View File

@@ -58,6 +58,7 @@
cost = 10 cost = 10
blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear) blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear)
requirements = list(70,60,50,50,40,40,40,30,20,15) requirements = list(70,60,50,50,40,40,40,30,20,15)
property_weights = list("story_potential" = 1, "trust" = 1, "chaos" = 1)
high_population_requirement = 15 high_population_requirement = 15
/datum/dynamic_ruleset/event/pirates/ready(forced = FALSE) /datum/dynamic_ruleset/event/pirates/ready(forced = FALSE)
@@ -81,6 +82,7 @@
cost = 10 cost = 10
requirements = list(70,60,50,50,40,40,40,30,20,15) requirements = list(70,60,50,50,40,40,40,30,20,15)
high_population_requirement = 15 high_population_requirement = 15
property_weights = list("chaos" = 1, "valid" = 1)
////////////////////////////////////////////// //////////////////////////////////////////////
// // // //
@@ -100,6 +102,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) // yes, can happen on fake-extended requirements = list(5,5,5,5,5,5,5,5,5,5) // yes, can happen on fake-extended
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("chaos" = 1, "extended" = 2)
/datum/dynamic_ruleset/event/ventclog/ready() /datum/dynamic_ruleset/event/ventclog/ready()
if(mode.threat_level > 30 && mode.threat >= 5 && prob(20)) if(mode.threat_level > 30 && mode.threat >= 5 && prob(20))
@@ -133,10 +136,11 @@
required_enemies = list(1,1,0,0,0,0,0,0,0,0) required_enemies = list(1,1,0,0,0,0,0,0,0,0)
weight = 4 weight = 4
// no repeatable weight decrease. too variable to be unfun multiple times in one round // no repeatable weight decrease. too variable to be unfun multiple times in one round
cost = 3 cost = 1
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("story_potential" = 1, "extended" = 1)
always_max_weight = TRUE always_max_weight = TRUE
////////////////////////////////////////////// //////////////////////////////////////////////
@@ -156,6 +160,7 @@
repeatable_weight_decrease = 2 repeatable_weight_decrease = 2
requirements = list(60,50,40,30,30,30,30,30,30,30) requirements = list(60,50,40,30,30,30,30,30,30,30)
high_population_requirement = 30 high_population_requirement = 30
property_weights = list("extended" = -2)
/datum/dynamic_ruleset/event/meteor_wave/ready() /datum/dynamic_ruleset/event/meteor_wave/ready()
if(mode.threat_level > 40 && mode.threat >= 25 && prob(20)) if(mode.threat_level > 40 && mode.threat >= 25 && prob(20))
@@ -190,6 +195,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/anomaly_flux /datum/dynamic_ruleset/event/anomaly_flux
name = "Anomaly: Hyper-Energetic Flux" name = "Anomaly: Hyper-Energetic Flux"
@@ -203,6 +209,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 10 high_population_requirement = 10
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/anomaly_gravitational /datum/dynamic_ruleset/event/anomaly_gravitational
name = "Anomaly: Gravitational" name = "Anomaly: Gravitational"
@@ -214,6 +221,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/anomaly_pyroclastic /datum/dynamic_ruleset/event/anomaly_pyroclastic
name = "Anomaly: Pyroclastic" name = "Anomaly: Pyroclastic"
@@ -227,6 +235,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10) requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10 high_population_requirement = 10
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/anomaly_vortex /datum/dynamic_ruleset/event/anomaly_vortex
name = "Anomaly: Vortex" name = "Anomaly: Vortex"
@@ -240,6 +249,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10) requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10 high_population_requirement = 10
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
////////////////////////////////////////////// //////////////////////////////////////////////
// // // //
@@ -259,6 +269,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10) requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10 high_population_requirement = 10
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = -1, "chaos" = 1)
/datum/dynamic_ruleset/event/carp_migration /datum/dynamic_ruleset/event/carp_migration
name = "Carp Migration" name = "Carp Migration"
@@ -270,6 +281,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10) requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10 high_population_requirement = 10
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/communications_blackout /datum/dynamic_ruleset/event/communications_blackout
name = "Communications Blackout" name = "Communications Blackout"
@@ -283,6 +295,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1, "chaos" = 1)
/datum/dynamic_ruleset/event/processor_overload /datum/dynamic_ruleset/event/processor_overload
name = "Processor Overload" name = "Processor Overload"
@@ -296,6 +309,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1, "chaos" = 1)
always_max_weight = TRUE always_max_weight = TRUE
/datum/dynamic_ruleset/event/space_dust /datum/dynamic_ruleset/event/space_dust
@@ -310,6 +324,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
always_max_weight = TRUE always_max_weight = TRUE
/datum/dynamic_ruleset/event/major_dust /datum/dynamic_ruleset/event/major_dust
@@ -324,6 +339,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10) requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10 high_population_requirement = 10
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/electrical_storm /datum/dynamic_ruleset/event/electrical_storm
name = "Electrical Storm" name = "Electrical Storm"
@@ -337,6 +353,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/heart_attack /datum/dynamic_ruleset/event/heart_attack
name = "Random Heart Attack" name = "Random Heart Attack"
@@ -350,6 +367,7 @@
requirements = list(101,101,101,5,5,5,5,5,5,5) requirements = list(101,101,101,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
repeatable = TRUE repeatable = TRUE
property_weights = list("extended" = 1)
always_max_weight = TRUE always_max_weight = TRUE
/datum/dynamic_ruleset/event/radiation_storm /datum/dynamic_ruleset/event/radiation_storm
@@ -362,3 +380,4 @@
required_enemies = list(1,1,1,1,1,1,1,1,1,1) required_enemies = list(1,1,1,1,1,1,1,1,1,1)
requirements = list(5,5,5,5,5,5,5,5,5,5) requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5 high_population_requirement = 5
property_weights = list("extended" = 1,"chaos" = 1)

View File

@@ -69,8 +69,15 @@
high_population_requirement = 15 high_population_requirement = 15
repeatable = TRUE repeatable = TRUE
flags = TRAITOR_RULESET flags = TRAITOR_RULESET
property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1)
always_max_weight = TRUE always_max_weight = TRUE
/datum/dynamic_ruleset/latejoin/infiltrator/execute()
. = ..()
for(var/datum/mind/M in assigned)
log_admin("[M.name] was made into a traitor by dynamic.")
message_admins("[M.name] was made into a traitor by dynamic.")
////////////////////////////////////////////// //////////////////////////////////////////////
// // // //
// REVOLUTIONARY PROVOCATEUR // // REVOLUTIONARY PROVOCATEUR //
@@ -94,6 +101,7 @@
requirements = list(101,101,70,40,40,40,40,40,40,40) requirements = list(101,101,70,40,40,40,40,40,40,40)
high_population_requirement = 40 high_population_requirement = 40
flags = HIGHLANDER_RULESET flags = HIGHLANDER_RULESET
property_weights = list("trust" = -2, "chaos" = 2, "extended" = -2, "valid" = 2, "conversion" = 1)
var/required_heads_of_staff = 3 var/required_heads_of_staff = 3
var/finished = FALSE var/finished = FALSE
var/datum/team/revolution/revolution var/datum/team/revolution/revolution
@@ -123,6 +131,8 @@
revolution.update_objectives() revolution.update_objectives()
revolution.update_heads() revolution.update_heads()
SSshuttle.registerHostileEnvironment(src) SSshuttle.registerHostileEnvironment(src)
log_admin("[M.name] was made into a revolutionary by dynamic.")
message_admins("[M.name] was made into a revolutionary by dynamic.")
return TRUE return TRUE
else else
log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.") log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.")
@@ -187,30 +197,31 @@
////////////////////////////////////////////// //////////////////////////////////////////////
// // // //
// VAMPIRE // // BLOODSUCKERS //
// // // //
////////////////////////////////////////////// //////////////////////////////////////////////
/* /datum/dynamic_ruleset/latejoin/bloodsucker
/datum/dynamic_ruleset/latejoin/vampire name = "Bloodsucker Infiltrator"
name = "vampire" config_tag = "latejoin_bloodsucker"
config_tag = "vampire_latejoin" antag_datum = ANTAG_DATUM_BLOODSUCKER
antag_flag = ROLE_VAMPIRE antag_flag = ROLE_TRAITOR
antag_datum = ANTAG_DATUM_VAMPIRE
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
restricted_roles = list("AI", "Cyborg") restricted_roles = list("AI", "Cyborg")
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_candidates = 1 required_candidates = 1
weight = 5 weight = 3
cost = 15 cost = 10
requirements = list(80,70,60,50,40,20,20,15,15,15) property_weights = list("story_potential" = 2, "extended" = 2, "trust" = -2, "valid" = 1)
requirements = list(70,65,60,55,50,45,40,35,30,30)
high_population_requirement = 30
repeatable = TRUE repeatable = TRUE
high_population_requirement = 15
/datum/dynamic_ruleset/latejoin/vampire/pre_execute() /datum/dynamic_ruleset/latejoin/bloodsucker/execute()
var/mob/M = pick(candidates) var/mob/M = pick(candidates)
candidates -= M
assigned += M.mind assigned += M.mind
M.mind.restricted_roles = restricted_roles M.mind.special_role = antag_flag
M.mind.special_role = ROLE_VAMPIRE if(mode.make_bloodsucker(M.mind))
mode.bloodsuckers += M
log_admin("[M.name] was made into a bloodsucker by dynamic.")
message_admins("[M.name] was made into a bloodsucker by dynamic.")
return TRUE return TRUE
*/

View File

@@ -108,8 +108,12 @@
candidates = pollGhostCandidates("The mode is looking for volunteers to become a [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300) candidates = pollGhostCandidates("The mode is looking for volunteers to become a [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300)
if(!candidates || candidates.len <= required_candidates) if(!candidates || candidates.len < required_candidates)
message_admins("The ruleset [name] did not receive enough applications.") message_admins("The ruleset [name] did not receive enough applications.")
if(candidates)
message_admins("Only received [candidates.len], needed [required_candidates].")
else
message_admins("There were no candidates.")
log_game("DYNAMIC: The ruleset [name] did not receive enough applications.") log_game("DYNAMIC: The ruleset [name] did not receive enough applications.")
return FALSE return FALSE
@@ -180,6 +184,7 @@
repeatable = TRUE repeatable = TRUE
high_population_requirement = 15 high_population_requirement = 15
flags = TRAITOR_RULESET flags = TRAITOR_RULESET
property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1)
always_max_weight = TRUE always_max_weight = TRUE
/datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0) /datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0)
@@ -214,6 +219,8 @@
living_players -= M living_players -= M
var/datum/antagonist/traitor/newTraitor = new var/datum/antagonist/traitor/newTraitor = new
M.mind.add_antag_datum(newTraitor) M.mind.add_antag_datum(newTraitor)
log_admin("[M] was made into a traitor by dynamic.")
message_admins("[M] was made into a traitor by dynamic.")
return TRUE return TRUE
@@ -237,6 +244,7 @@
requirements = list(101,101,70,50,50,50,40,30,30,30) requirements = list(101,101,70,50,50,50,40,30,30,30)
high_population_requirement = 30 high_population_requirement = 30
required_type = /mob/living/silicon/ai required_type = /mob/living/silicon/ai
property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2)
var/ion_announce = 33 var/ion_announce = 33
var/removeDontImproveChance = 10 var/removeDontImproveChance = 10
@@ -261,6 +269,8 @@
var/datum/antagonist/traitor/AI = new var/datum/antagonist/traitor/AI = new
M.mind.special_role = antag_flag M.mind.special_role = antag_flag
M.mind.add_antag_datum(AI) M.mind.add_antag_datum(AI)
log_admin("[M] was made into a malf AI by dynamic.")
message_admins("[M] was made into a malf AI by dynamic.")
if(prob(ion_announce)) if(prob(ion_announce))
priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", "ionstorm") priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", "ionstorm")
if(prob(removeDontImproveChance)) if(prob(removeDontImproveChance))
@@ -289,6 +299,7 @@
requirements = list(90,90,70,50,50,50,50,40,30,30) requirements = list(90,90,70,50,50,50,50,40,30,30)
high_population_requirement = 30 high_population_requirement = 30
repeatable = TRUE repeatable = TRUE
property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2, "extended" = -2)
var/datum/mind/wizard var/datum/mind/wizard
/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE) /datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE)
@@ -337,6 +348,7 @@
cost = 35 cost = 35
requirements = list(90,90,90,80,70,60,50,40,40,40) requirements = list(90,90,90,80,70,60,50,40,40,40)
high_population_requirement = 40 high_population_requirement = 40
property_weights = list("story_potential" = 2, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2)
var/operative_cap = list(2,2,3,3,4,5,5,5,5,5) var/operative_cap = list(2,2,3,3,4,5,5,5,5,5)
var/datum/team/nuclear/nuke_team var/datum/team/nuclear/nuke_team
flags = HIGHLANDER_RULESET flags = HIGHLANDER_RULESET
@@ -390,6 +402,7 @@
requirements = list(101,101,101,80,60,50,50,50,50,50) requirements = list(101,101,101,80,60,50,50,50,50,50)
high_population_requirement = 50 high_population_requirement = 50
repeatable = TRUE repeatable = TRUE
property_weights = list("story_potential" = -1, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2)
/datum/dynamic_ruleset/midround/from_ghosts/blob/ready(forced = FALSE) /datum/dynamic_ruleset/midround/from_ghosts/blob/ready(forced = FALSE)
if (required_candidates > (dead_players.len + list_observers.len)) if (required_candidates > (dead_players.len + list_observers.len))
@@ -421,6 +434,7 @@
high_population_requirement = 50 high_population_requirement = 50
repeatable_weight_decrease = 2 repeatable_weight_decrease = 2
repeatable = TRUE repeatable = TRUE
property_weights = list("story_potential" = -1, "trust" = 1, "chaos" = 2, "extended" = -2, "valid" = 2)
var/list/vents = list() var/list/vents = list()
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/ready(forced = FALSE) /datum/dynamic_ruleset/midround/from_ghosts/xenomorph/ready(forced = FALSE)
@@ -476,6 +490,7 @@
high_population_requirement = 50 high_population_requirement = 50
repeatable_weight_decrease = 2 repeatable_weight_decrease = 2
repeatable = TRUE repeatable = TRUE
property_weights = list("story_potential" = 1, "trust" = 1, "extended" = 1, "valid" = 2, "integrity" = 2)
var/list/spawn_locs = list() var/list/spawn_locs = list()
/datum/dynamic_ruleset/midround/from_ghosts/nightmare/execute() /datum/dynamic_ruleset/midround/from_ghosts/nightmare/execute()
@@ -521,6 +536,7 @@
weight = 4 weight = 4
cost = 5 cost = 5
requirements = list(30,30,20,20,15,10,10,10,10,5) // yes, it can even happen in "extended"! requirements = list(30,30,20,20,15,10,10,10,10,5) // yes, it can even happen in "extended"!
property_weights = list("story_potential" = 1, "extended" = 1, "valid" = -2)
high_population_requirement = 5 high_population_requirement = 5
/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/ready(forced = FALSE) /datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/ready(forced = FALSE)
@@ -555,14 +571,18 @@
cost = 5 cost = 5
requirements = list(30,30,30,30,20,15,15,15,15,15) requirements = list(30,30,30,30,20,15,15,15,15,15)
high_population_requirement = 15 high_population_requirement = 15
property_weights = list("story_potential" = -2, "extended" = -1)
var/list/spawn_locs = list() var/list/spawn_locs = list()
/datum/dynamic_ruleset/midround/from_ghosts/revenant/ready(forced = FALSE) /datum/dynamic_ruleset/midround/from_ghosts/revenant/acceptable(population = 0,threat = 0)
var/deadMobs = 0 var/deadMobs = 0
for(var/mob/M in GLOB.dead_mob_list) for(var/mob/M in GLOB.dead_mob_list)
deadMobs++ deadMobs++
if(deadMobs < REVENANT_SPAWN_THRESHOLD) if(deadMobs < REVENANT_SPAWN_THRESHOLD)
return FALSE return FALSE
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/revenant/ready(forced = FALSE)
if(required_candidates > (dead_players.len + list_observers.len)) if(required_candidates > (dead_players.len + list_observers.len))
SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts") SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE return FALSE
@@ -607,6 +627,7 @@
weight = 4 weight = 4
cost = 15 cost = 15
requirements = list(101,101,101,90,80,70,60,50,40,30) requirements = list(101,101,101,90,80,70,60,50,40,30)
property_weights = list("story_potential" = -2, "extended" = -2, "integrity" = 2, "valid" = 2, "trust" = 2)
high_population_requirement = 30 high_population_requirement = 30
var/list/spawn_locs = list() var/list/spawn_locs = list()
@@ -659,6 +680,7 @@
blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear) blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear)
high_population_requirement = 15 high_population_requirement = 15
var/datum/team/abductor_team/team var/datum/team/abductor_team/team
property_weights = list("story_potential" = 1, "extended" = -2, "valid" = 1, "trust" = -1, "chaos" = 2)
repeatable_weight_decrease = 4 repeatable_weight_decrease = 4
repeatable = TRUE repeatable = TRUE
@@ -699,6 +721,7 @@
cost = 15 cost = 15
requirements = list(101,101,101,90,80,70,60,50,40,30) requirements = list(101,101,101,90,80,70,60,50,40,30)
high_population_requirement = 30 high_population_requirement = 30
property_weights = list("story_potential" = 1, "extended" = -2, "valid" = 2)
var/list/spawn_locs = list() var/list/spawn_locs = list()
var/spawn_loc var/spawn_loc
@@ -745,31 +768,3 @@
#undef ABDUCTOR_MAX_TEAMS #undef ABDUCTOR_MAX_TEAMS
#undef REVENANT_SPAWN_THRESHOLD #undef REVENANT_SPAWN_THRESHOLD
//////////////////////////////////////////////
// //
// BLOODSUCKERS //
// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/latejoin/bloodsucker
name = "Bloodsucker Infiltrator"
config_tag = "latejoin_bloodsucker"
antag_datum = ANTAG_DATUM_BLOODSUCKER
antag_flag = ROLE_TRAITOR
restricted_roles = list("AI", "Cyborg")
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_candidates = 1
weight = 3
cost = 10
requirements = list(90,80,70,60,55,50,45,40,35,30)
high_population_requirement = 30
repeatable = TRUE
/datum/dynamic_ruleset/latejoin/bloodsucker/execute()
var/mob/M = pick(candidates)
assigned += M.mind
M.mind.special_role = antag_flag
if(mode.make_bloodsucker(M.mind))
mode.bloodsuckers += M
return TRUE

View File

@@ -21,6 +21,7 @@
requirements = list(50,50,50,50,50,50,50,50,50,50) requirements = list(50,50,50,50,50,50,50,50,50,50)
high_population_requirement = 40 high_population_requirement = 40
antag_cap = list(1,1,1,1,2,2,2,2,3,3) antag_cap = list(1,1,1,1,2,2,2,2,3,3)
property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1, "valid" = 1)
always_max_weight = TRUE always_max_weight = TRUE
var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec) var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec)
@@ -61,6 +62,7 @@
requirements = list(101,101,101,101,101,101,101,101,101,101) requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101 high_population_requirement = 101
antag_cap = list(2,2,2,2,2,2,2,2,2,2) // Can pick 3 per team, but rare enough it doesn't matter. antag_cap = list(2,2,2,2,2,2,2,2,2,2) // Can pick 3 per team, but rare enough it doesn't matter.
property_weights = list("story_potential" = 1, "trust" = -1, "extended" = 1, "valid" = 1)
var/list/datum/team/brother_team/pre_brother_teams = list() var/list/datum/team/brother_team/pre_brother_teams = list()
var/const/min_team_size = 2 var/const/min_team_size = 2
@@ -108,6 +110,7 @@
cost = 15 cost = 15
scaling_cost = 15 scaling_cost = 15
requirements = list(101,101,101,101,101,101,101,101,101,101) requirements = list(101,101,101,101,101,101,101,101,101,101)
property_weights = list("trust" = -2, "valid" = 2)
high_population_requirement = 10 high_population_requirement = 10
antag_cap = list(1,1,1,1,1,2,2,2,2,3) antag_cap = list(1,1,1,1,1,2,2,2,2,3)
var/team_mode_probability = 30 var/team_mode_probability = 30
@@ -160,6 +163,7 @@
cost = 30 cost = 30
requirements = list(101,101,101,60,50,50,50,50,50,50) requirements = list(101,101,101,60,50,50,50,50,50,50)
high_population_requirement = 50 high_population_requirement = 50
property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2, "extended" = -2, "valid" = 2)
var/list/roundstart_wizards = list() var/list/roundstart_wizards = list()
/datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0) /datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0)
@@ -222,6 +226,7 @@
weight = 3 weight = 3
cost = 30 cost = 30
requirements = list(101,101,101,80,70,60,50,50,50,50) requirements = list(101,101,101,80,70,60,50,50,50,50)
property_weights = list("story_potential" = -1, "trust" = -1, "chaos" = 1, "conversion" = 1, "extended" = -2, "valid" = 2)
high_population_requirement = 50 high_population_requirement = 50
flags = HIGHLANDER_RULESET flags = HIGHLANDER_RULESET
antag_cap = list(2,2,2,3,3,4,4,4,4,4) antag_cap = list(2,2,2,3,3,4,4,4,4,4)
@@ -283,6 +288,7 @@
high_population_requirement = 50 high_population_requirement = 50
flags = HIGHLANDER_RULESET flags = HIGHLANDER_RULESET
antag_cap = list(1,1,2,3,4,5,5,5,5,5) antag_cap = list(1,1,2,3,4,5,5,5,5,5)
property_weights = list("story_potential" = 2, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2)
var/datum/team/nuclear/nuke_team var/datum/team/nuclear/nuke_team
/datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE) /datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE)
@@ -373,6 +379,7 @@
flags = HIGHLANDER_RULESET flags = HIGHLANDER_RULESET
// I give up, just there should be enough heads with 35 players... // I give up, just there should be enough heads with 35 players...
minimum_players = 35 minimum_players = 35
property_weights = list("trust" = -2, "chaos" = 2, "extended" = -2, "valid" = 2, "conversion" = 1)
var/datum/team/revolution/revolution var/datum/team/revolution/revolution
var/finished = FALSE var/finished = FALSE
@@ -490,6 +497,7 @@
weight = 3 weight = 3
cost = 0 cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101) requirements = list(101,101,101,101,101,101,101,101,101,101)
property_weights = list("extended" = 2)
high_population_requirement = 101 high_population_requirement = 101
/datum/dynamic_ruleset/roundstart/extended/pre_execute() /datum/dynamic_ruleset/roundstart/extended/pre_execute()
@@ -517,6 +525,7 @@
high_population_requirement = 50 high_population_requirement = 50
flags = HIGHLANDER_RULESET flags = HIGHLANDER_RULESET
antag_cap = list(2,3,3,4,4,4,4,4,4,4) antag_cap = list(2,3,3,4,4,4,4,4,4,4)
property_weights = list("trust" = 2, "chaos" = 2, "extended" = -2, "conversion" = 1, "valid" = 2)
var/ark_time var/ark_time
/datum/dynamic_ruleset/roundstart/clockcult/pre_execute() /datum/dynamic_ruleset/roundstart/clockcult/pre_execute()
@@ -616,6 +625,8 @@
antag_leader_datum = /datum/antagonist/nukeop/leader/clownop antag_leader_datum = /datum/antagonist/nukeop/leader/clownop
requirements = list(101,101,101,101,101,101,101,101,101,101) requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101 high_population_requirement = 101
property_weights = list("trust" = 2, "chaos" = 2, "extended" = -2, "story_potential" = 2, "valid" = 2)
/datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute() /datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute()
. = ..() . = ..()
@@ -647,6 +658,7 @@
requirements = list(101,101,101,101,101,101,101,101,101,101) requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101 high_population_requirement = 101
antag_cap = list(1,1,1,2,2,2,3,3,3,4) antag_cap = list(1,1,1,2,2,2,3,3,3,4)
property_weights = list("extended" = 1)
/datum/dynamic_ruleset/roundstart/devil/pre_execute() /datum/dynamic_ruleset/roundstart/devil/pre_execute()
var/num_devils = antag_cap[indice_pop] var/num_devils = antag_cap[indice_pop]
@@ -698,6 +710,7 @@
cost = 0 cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101) requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101 high_population_requirement = 101
property_weights = list("extended" = -2, "chaos" = 2, "conversion" = 1, "valid" = 2)
var/players_per_carrier = 30 var/players_per_carrier = 30
var/monkeys_to_win = 1 var/monkeys_to_win = 1
var/escaped_monkeys = 0 var/escaped_monkeys = 0
@@ -759,6 +772,7 @@
cost = 0 cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101) requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101 high_population_requirement = 101
property_weights = list("extended" = -2, "chaos" = 2, "trust" = 2)
var/meteordelay = 2000 var/meteordelay = 2000
var/nometeors = 0 var/nometeors = 0
var/rampupdelta = 5 var/rampupdelta = 5
@@ -799,7 +813,8 @@
weight = 2 weight = 2
cost = 15 cost = 15
scaling_cost = 10 scaling_cost = 10
requirements = list(90,80,70,60,50,50,50,50,50,50) property_weights = list("story_potential" = 1, "extended" = 1, "trust" = -2, "valid" = 1)
requirements = list(70,65,60,55,50,50,50,50,50,50)
high_population_requirement = 50 high_population_requirement = 50
antag_cap = list(1,1,1,1,1,2,2,2,2,2) antag_cap = list(1,1,1,1,1,2,2,2,2,2)

View File

@@ -0,0 +1,235 @@
/datum/dynamic_storyteller
var/name = "none"
var/desc = "A coder's idiocy."
var/list/property_weights = list()
var/curve_centre = 0
var/curve_width = 1.8
var/forced_threat_level = -1
var/flags = 0
var/weight = 3 // how many rounds need to have been recently played for this storyteller to be left out of the vote
var/datum/game_mode/dynamic/mode = null
/**
Property weights are:
"story_potential" -- essentially how many different ways the antag can be played.
"trust" -- How much it makes the crew trust each other. Negative values means they're suspicious. Team antags are like this.
"chaos" -- How chaotic it makes the round. Has some overlap with "valid" and somewhat contradicts "extended".
"valid" -- How likely the non-antag-enemy crew are to get involved, e.g. nukies encouraging the warden to
let everyone into the armory, wizard moving around and being a nuisance, nightmare busting lights.
"extended" -- How much the antag is conducive to a long round. Nukies and cults are bad for this; Wizard is less bad; and so on.
"conversion" -- Basically a bool. Conversion antags, well, convert. It's its own class for a good reason.
*/
/datum/dynamic_storyteller/New()
..()
if (istype(SSticker.mode, /datum/game_mode/dynamic))
mode = SSticker.mode
GLOB.dynamic_curve_centre = curve_centre
GLOB.dynamic_curve_width = curve_width
GLOB.dynamic_forced_threat_level = forced_threat_level
/datum/dynamic_storyteller/proc/start_injection_cooldowns()
var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_latejoin_delay_max + GLOB.dynamic_first_latejoin_delay_min)
mode.latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_first_latejoin_delay_min, GLOB.dynamic_first_latejoin_delay_max)) + world.time
var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_midround_delay_min + GLOB.dynamic_first_midround_delay_max)
mode.midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_first_midround_delay_min, GLOB.dynamic_first_midround_delay_max)) + world.time
var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
mode.event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
/datum/dynamic_storyteller/proc/do_process()
return
/datum/dynamic_storyteller/proc/on_start()
return
/datum/dynamic_storyteller/proc/get_midround_cooldown()
var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
return round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max))
/datum/dynamic_storyteller/proc/get_event_cooldown()
var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
return round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max))
/datum/dynamic_storyteller/proc/get_latejoin_cooldown()
var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min)
return round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max))
/datum/dynamic_storyteller/proc/get_injection_chance(dry_run = FALSE)
if(mode.forced_injection)
mode.forced_injection = !dry_run
return 100
var/chance = 0
// If the high pop override is in effect, we reduce the impact of population on the antag injection chance
var/high_pop_factor = (mode.current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit)
var/max_pop_per_antag = max(5,15 - round(mode.threat_level/10) - round(mode.current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5)))
if (!mode.current_players[CURRENT_LIVING_ANTAGS].len)
chance += 80 // No antags at all? let's boost those odds!
else
var/current_pop_per_antag = mode.current_players[CURRENT_LIVING_PLAYERS].len / mode.current_players[CURRENT_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 (mode.current_players[CURRENT_DEAD_PLAYERS].len > mode.current_players[CURRENT_LIVING_PLAYERS].len)
chance -= 30 // More than half the crew died? ew, let's calm down on antags
if (mode.threat > 70)
chance += 15
if (mode.threat < 30)
chance -= 15
return round(max(0,chance))
/datum/dynamic_storyteller/proc/roundstart_draft()
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/roundstart/rule in mode.roundstart_rules)
if (rule.acceptable(mode.roundstart_pop_ready, mode.threat_level) && mode.threat >= rule.cost) // If we got the population and threat required
rule.candidates = mode.candidates.Copy()
rule.trim_candidates()
if (rule.ready() && rule.candidates.len > 0)
var/property_weight = 0
for(var/property in property_weights)
if(property in rule.property_weights) // just treat it as 0 if it's not in there
property_weight += rule.property_weights[property] * property_weights[property]
drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
return drafted_rules
/datum/dynamic_storyteller/proc/midround_draft()
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/midround/rule in mode.midround_rules)
// if there are antags OR the rule is an antag rule, antag_acceptable will be true.
if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost)
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
continue
rule.trim_candidates()
if (rule.ready())
var/property_weight = 0
for(var/property in property_weights)
if(property in rule.property_weights)
property_weight += rule.property_weights[property] * property_weights[property]
drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
else if(mode.threat < rule.cost)
SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
return drafted_rules
/datum/dynamic_storyteller/proc/latejoin_draft(mob/living/carbon/human/newPlayer)
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/latejoin/rule in mode.latejoin_rules)
if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost)
// Classic secret : only autotraitor/minor roles
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
continue
// No stacking : only one round-ender, unless threat level > stacking_limit.
if (mode.threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
if(rule.flags & HIGHLANDER_RULESET && mode.highlander_executed)
continue
rule.candidates = list(newPlayer)
rule.trim_candidates()
if (rule.ready())
var/property_weight = 0
for(var/property in property_weights)
if(property in rule.property_weights)
property_weight += rule.property_weights[property] * property_weights[property]
drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
else if(mode.threat < rule.cost)
SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
return drafted_rules
/datum/dynamic_storyteller/proc/event_draft()
var/list/drafted_rules = list()
for(var/datum/dynamic_ruleset/event/rule in mode.events)
if(rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost)
if(rule.ready())
var/property_weight = 0
for(var/property in property_weights)
if(property in rule.property_weights)
property_weight += rule.property_weights[property] * property_weights[property]
drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
else if(mode.threat < rule.cost)
SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
return drafted_rules
/datum/dynamic_storyteller/cowabunga
name = "Chaotic"
curve_centre = 10
desc = "Chaos: high. Variation: high. Likely antags: clock cult, revs, wizard."
property_weights = list("extended" = -1, "chaos" = 10)
weight = 2
flags = WAROPS_ALWAYS_ALLOWED
var/refund_cooldown
/datum/dynamic_storyteller/cowabunga/get_midround_cooldown()
return ..() / 4
/datum/dynamic_storyteller/cowabunga/get_latejoin_cooldown()
return ..() / 4
/datum/dynamic_storyteller/cowabunga/do_process()
if(refund_cooldown < world.time)
mode.refund_threat(10)
mode.log_threat("Cowabunga it is. Refunded 10 threat. Threat is now [mode.threat].")
refund_cooldown = world.time + 300 SECONDS
/datum/dynamic_storyteller/team
name = "Teamwork"
desc = "Chaos: high. Variation: low. Likely antags: nukies, clockwork cult, wizard, blob, xenomorph."
curve_centre = 2
curve_width = 1.5
weight = 2
flags = WAROPS_ALWAYS_ALLOWED
property_weights = list("valid" = 3, "trust" = 5)
/datum/dynamic_storyteller/team/get_injection_chance(dry_run = FALSE)
return (mode.current_players[CURRENT_LIVING_ANTAGS].len ? 0 : ..())
/datum/dynamic_storyteller/conversion
name = "Conversion"
desc = "Chaos: high. Variation: medium. Likely antags: cults, bloodsuckers, revs."
curve_centre = 3
curve_width = 1
weight = 2
flags = WAROPS_ALWAYS_ALLOWED
property_weights = list("valid" = 1, "conversion" = 20)
/datum/dynamic_storyteller/classic
name = "Random"
desc = "Chaos: varies. Variation: highest. No special weights attached."
weight = 6
curve_width = 4
/datum/dynamic_storyteller/memes
name = "Story"
desc = "Chaos: varies. Variation: high. Likely antags: abductors, nukies, wizard, traitor."
curve_width = 4
property_weights = list("story_potential" = 10)
/datum/dynamic_storyteller/suspicion
name = "Intrigue"
desc = "Chaos: low. Variation: high. Likely antags: traitor, bloodsucker. Rare: revs, blood cult."
curve_width = 4
property_weights = list("trust" = -5)
/datum/dynamic_storyteller/liteextended
name = "Calm"
desc = "Chaos: low. Variation: medium. Likely antags: bloodsuckers, traitors, sentient disease, revenant."
curve_centre = -5
curve_width = 0.5
flags = NO_ASSASSIN
weight = 2
property_weights = list("extended" = 1, "chaos" = -1, "valid" = -1, "story_potential" = 1, "conversion" = -10)
/datum/dynamic_storyteller/liteextended/get_injection_chance(dry_run = FALSE)
return ..()/2
/datum/dynamic_storyteller/extended
name = "Extended"
desc = "Chaos: none. Variation: none. Likely antags: none."
curve_centre = -20
weight = 2
curve_width = 0.5
/datum/dynamic_storyteller/extended/on_start()
GLOB.dynamic_forced_extended = TRUE

View File

@@ -693,7 +693,7 @@
var/prev_dynamic_voting = CONFIG_GET(flag/dynamic_voting) var/prev_dynamic_voting = CONFIG_GET(flag/dynamic_voting)
CONFIG_SET(flag/dynamic_voting,!prev_dynamic_voting) CONFIG_SET(flag/dynamic_voting,!prev_dynamic_voting)
if (!prev_dynamic_voting) if (!prev_dynamic_voting)
to_chat(world, "<B>Vote is now between extended and dynamic chaos.</B>") to_chat(world, "<B>Vote is now a ranked choice of dynamic storytellers.</B>")
else else
to_chat(world, "<B>Vote is now between extended and secret.</B>") to_chat(world, "<B>Vote is now between extended and secret.</B>")
log_admin("[key_name(usr)] [prev_dynamic_voting ? "disabled" : "enabled"] dynamic voting.") log_admin("[key_name(usr)] [prev_dynamic_voting ? "disabled" : "enabled"] dynamic voting.")

View File

@@ -76,9 +76,10 @@ GLOBAL_VAR_INIT(war_declared, FALSE)
CONFIG_SET(number/shuttle_refuel_delay, max(CONFIG_GET(number/shuttle_refuel_delay), CHALLENGE_SHUTTLE_DELAY)) CONFIG_SET(number/shuttle_refuel_delay, max(CONFIG_GET(number/shuttle_refuel_delay), CHALLENGE_SHUTTLE_DELAY))
if(istype(SSticker.mode, /datum/game_mode/dynamic)) if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode var/datum/game_mode/dynamic/mode = SSticker.mode
var/threat_spent = CONFIG_GET(number/dynamic_warops_cost) if(!(mode.storyteller.flags & WAROPS_ALWAYS_ALLOWED))
mode.spend_threat(threat_spent) var/threat_spent = CONFIG_GET(number/dynamic_warops_cost)
mode.log_threat("Nuke ops spent [threat_spent] on war ops.") mode.spend_threat(threat_spent)
mode.log_threat("Nuke ops spent [threat_spent] on war ops.")
SSblackbox.record_feedback("amount", "nuclear_challenge_mode", 1) SSblackbox.record_feedback("amount", "nuclear_challenge_mode", 1)
qdel(src) qdel(src)
@@ -101,12 +102,13 @@ GLOBAL_VAR_INIT(war_declared, FALSE)
return FALSE return FALSE
if(istype(SSticker.mode, /datum/game_mode/dynamic)) if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode var/datum/game_mode/dynamic/mode = SSticker.mode
if(mode.threat_level < CONFIG_GET(number/dynamic_warops_requirement)) if(!(mode.storyteller.flags & WAROPS_ALWAYS_ALLOWED))
to_chat(user, "Due to the dynamic space in which the station resides, you are too deep into Nanotrasen territory to reasonably go loud.") if(mode.threat_level < CONFIG_GET(number/dynamic_warops_requirement))
return FALSE to_chat(user, "Due to the dynamic space in which the station resides, you are too deep into Nanotrasen territory to reasonably go loud.")
else if(mode.threat < CONFIG_GET(number/dynamic_warops_cost)) return FALSE
to_chat(user, "Due to recent threats on the station, Nanotrasen is looking too closely for a war declaration to be wise.") else if(mode.threat < CONFIG_GET(number/dynamic_warops_cost))
return FALSE to_chat(user, "Due to recent threats on the station, Nanotrasen is looking too closely for a war declaration to be wise.")
return FALSE
return TRUE return TRUE
/obj/item/nuclear_challenge/clownops /obj/item/nuclear_challenge/clownops

View File

@@ -80,6 +80,8 @@
if(istype(SSticker.mode,/datum/game_mode/dynamic)) if(istype(SSticker.mode,/datum/game_mode/dynamic))
mode = SSticker.mode mode = SSticker.mode
is_dynamic = TRUE is_dynamic = TRUE
if(mode.storyteller.flags & NO_ASSASSIN)
is_hijacker = FALSE
if(GLOB.joined_player_list.len>=GLOB.dynamic_high_pop_limit) if(GLOB.joined_player_list.len>=GLOB.dynamic_high_pop_limit)
is_hijacker = (prob(10) && mode.threat_level > CONFIG_GET(number/dynamic_hijack_high_population_requirement)) is_hijacker = (prob(10) && mode.threat_level > CONFIG_GET(number/dynamic_hijack_high_population_requirement))
else else
@@ -180,7 +182,7 @@
destroy_objective.owner = owner destroy_objective.owner = owner
destroy_objective.find_target() destroy_objective.find_target()
add_objective(destroy_objective) add_objective(destroy_objective)
else if(prob(30)) else if(prob(30) || (mode.storyteller.flags & NO_ASSASSIN))
var/datum/objective/maroon/maroon_objective = new var/datum/objective/maroon/maroon_objective = new
maroon_objective.owner = owner maroon_objective.owner = owner
maroon_objective.find_target() maroon_objective.find_target()

View File

@@ -43,6 +43,7 @@
#include "code\__DEFINES\diseases.dm" #include "code\__DEFINES\diseases.dm"
#include "code\__DEFINES\DNA.dm" #include "code\__DEFINES\DNA.dm"
#include "code\__DEFINES\donator_groupings.dm" #include "code\__DEFINES\donator_groupings.dm"
#include "code\__DEFINES\dynamic.dm"
#include "code\__DEFINES\events.dm" #include "code\__DEFINES\events.dm"
#include "code\__DEFINES\exports.dm" #include "code\__DEFINES\exports.dm"
#include "code\__DEFINES\fantasy_affixes.dm" #include "code\__DEFINES\fantasy_affixes.dm"
@@ -108,6 +109,7 @@
#include "code\__DEFINES\typeids.dm" #include "code\__DEFINES\typeids.dm"
#include "code\__DEFINES\vehicles.dm" #include "code\__DEFINES\vehicles.dm"
#include "code\__DEFINES\voreconstants.dm" #include "code\__DEFINES\voreconstants.dm"
#include "code\__DEFINES\vote.dm"
#include "code\__DEFINES\vv.dm" #include "code\__DEFINES\vv.dm"
#include "code\__DEFINES\wall_dents.dm" #include "code\__DEFINES\wall_dents.dm"
#include "code\__DEFINES\wires.dm" #include "code\__DEFINES\wires.dm"
@@ -579,6 +581,7 @@
#include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm"
#include "code\game\gamemodes\dynamic\dynamic_storytellers.dm"
#include "code\game\gamemodes\extended\extended.dm" #include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\gangs\dominator.dm" #include "code\game\gamemodes\gangs\dominator.dm"
#include "code\game\gamemodes\gangs\dominator_countdown.dm" #include "code\game\gamemodes\gangs\dominator_countdown.dm"