diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm index 45c1ba9cb2..0d57961f48 100644 --- a/code/__DEFINES/dynamic.dm +++ b/code/__DEFINES/dynamic.dm @@ -5,6 +5,7 @@ #define NO_ASSASSIN (1<<0) #define WAROPS_ALWAYS_ALLOWED (1<<1) +#define USE_PREF_WEIGHTS (1<<2) #define ONLY_RULESET (1<<0) #define HIGHLANDER_RULESET (1<<1) diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 28a04c2a51..7fabb74157 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -38,6 +38,7 @@ #define ROLE_BLOODSUCKER "bloodsucker" //#define ROLE_MONSTERHUNTER "monster hunter" Disabled for now #define ROLE_GHOSTCAFE "ghostcafe" +#define ROLE_MINOR_ANTAG "minorantag" //Missing assignment means it's not a gamemode specific role, IT'S NOT A BUG OR ERROR. //The gamemode specific ones are just so the gamemodes can query whether a player is old enough //(in game days played) to play that role diff --git a/code/__DEFINES/vote.dm b/code/__DEFINES/vote.dm index 8fb2e6deab..b83ea7b8f6 100644 --- a/code/__DEFINES/vote.dm +++ b/code/__DEFINES/vote.dm @@ -1,6 +1,8 @@ #define PLURALITY_VOTING 0 #define APPROVAL_VOTING 1 -#define RANKED_CHOICE_VOTING 2 +#define SCHULZE_VOTING 2 #define SCORE_VOTING 3 +#define MAJORITY_JUDGEMENT_VOTING 4 +#define INSTANT_RUNOFF_VOTING 5 GLOBAL_LIST_INIT(vote_score_options,list("Bad","Poor","Acceptable","Good","Great")) diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 19ee2b2a5a..04e7ffd721 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -3,6 +3,9 @@ #define POPCOUNT_SHUTTLE_ESCAPEES "shuttle_escapees" //Emergency shuttle only. /datum/controller/subsystem/ticker/proc/gather_roundend_feedback() + var/datum/station_state/end_state = new /datum/station_state() + end_state.count() + station_integrity = min(PERCENT(GLOB.start_state.score(end_state)), 100) gather_antag_data() record_nuke_disk_location() var/json_file = file("[GLOB.log_directory]/round_end_data.json") @@ -71,9 +74,6 @@ mob_data += list("name" = m.name, "typepath" = m.type) var/pos = length(file_data["[escaped]"]["[category]"]) + 1 file_data["[escaped]"]["[category]"]["[pos]"] = mob_data - var/datum/station_state/end_state = new /datum/station_state() - end_state.count() - var/station_integrity = min(PERCENT(GLOB.start_state.score(end_state)), 100) file_data["additional data"]["station integrity"] = station_integrity WRITE_FILE(json_file, json_encode(file_data)) SSblackbox.record_feedback("nested tally", "round_end_stats", num_survivors, list("survivors", "total")) @@ -111,7 +111,14 @@ if(A.objectives.len) for(var/datum/objective/O in A.objectives) - var/result = O.check_completion() ? "SUCCESS" : "FAIL" + var/result = "UNKNOWN" + var/actual_result = O.check_completion() + if(actual_result >= 1) + result = "SUCCESS" + else if(actual_result <= 0) + result = "FAIL" + else + result = "[actual_result*100]%" antag_info["objectives"] += list(list("objective_type"=O.type,"text"=O.explanation_text,"result"=result)) SSblackbox.record_feedback("associative", "antagonists", 1, antag_info) @@ -550,10 +557,16 @@ var/list/objective_parts = list() var/count = 1 for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - objective_parts += "Objective #[count]: [objective.explanation_text] Success!" + if(objective.completable) + var/completion = objective.check_completion() + if(completion >= 1) + objective_parts += "Objective #[count]: [objective.explanation_text] Success!" + else if(completion <= 0) + objective_parts += "Objective #[count]: [objective.explanation_text] Fail." + else + objective_parts += "Objective #[count]: [objective.explanation_text] [completion*100]%" else - objective_parts += "Objective #[count]: [objective.explanation_text] Fail." + objective_parts += "Objective #[count]: [objective.explanation_text]" count++ return objective_parts.Join("
") diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index 79d125f608..974a0c0e88 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -364,6 +364,29 @@ runnable_modes[M] = final_weight return runnable_modes +/datum/controller/configuration/proc/get_runnable_storytellers() + var/list/datum/dynamic_storyteller/runnable_storytellers = new + var/list/probabilities = Get(/datum/config_entry/keyed_list/storyteller_weight) + var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust) + for(var/T in storyteller_cache) + var/datum/dynamic_storyteller/S = T + var/config_tag = initial(S.config_tag) + var/final_weight = initial(S.weight) + if(probabilities[config_tag]<=0) + continue + final_weight = probabilities[config_tag] + if(SSpersistence.saved_storytellers.len == 3 && repeated_mode_adjust.len == 3) + var/name = initial(S.name) + var/recent_round = min(SSpersistence.saved_storytellers.Find(name),3) + var/adjustment = 0 + while(recent_round) + adjustment += repeated_mode_adjust[recent_round] + recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0) + final_weight *= ((100-adjustment)/100) + runnable_storytellers[S] = final_weight + return runnable_storytellers + + /datum/controller/configuration/proc/get_runnable_midround_modes(crew) var/list/datum/game_mode/runnable_modes = new var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) diff --git a/code/controllers/configuration/entries/dynamic.dm b/code/controllers/configuration/entries/dynamic.dm index 7f3e16d57e..4c03de4678 100644 --- a/code/controllers/configuration/entries/dynamic.dm +++ b/code/controllers/configuration/entries/dynamic.dm @@ -88,3 +88,7 @@ /datum/config_entry/number/dynamic_warops_cost config_entry_value = 10 min_val = 0 + +/datum/config_entry/keyed_list/storyteller_weight + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm index 4cd75c9af3..de2ff17a97 100644 --- a/code/controllers/subsystem/persistence.dm +++ b/code/controllers/subsystem/persistence.dm @@ -13,7 +13,7 @@ SUBSYSTEM_DEF(persistence) var/list/saved_messages = list() var/list/saved_modes = list(1,2,3) var/list/saved_dynamic_rules = list(list(),list(),list()) - var/list/saved_storytellers = list("foo","bar","baz","foo again","bar again") + var/list/saved_storytellers = list("foo","bar","baz") var/list/saved_maps var/list/saved_trophies = list() var/list/spawned_objects = list() @@ -191,6 +191,7 @@ SUBSYSTEM_DEF(persistence) if(!json) return saved_storytellers = json["data"] + saved_storytellers.len = 3 /datum/controller/subsystem/persistence/proc/LoadRecentMaps() var/json_file = file("data/RecentMaps.json") @@ -429,9 +430,7 @@ SUBSYSTEM_DEF(persistence) WRITE_FILE(json_file, json_encode(file_data)) /datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode) - saved_storytellers.len = 5 - saved_storytellers[5] = saved_storytellers[4] - saved_storytellers[4] = saved_storytellers[3] + saved_storytellers.len = 3 saved_storytellers[3] = saved_storytellers[2] saved_storytellers[2] = saved_storytellers[1] saved_storytellers[1] = mode.storyteller.name diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm index a52bcf4607..10655981e5 100644 --- a/code/controllers/subsystem/processing/quirks.dm +++ b/code/controllers/subsystem/processing/quirks.dm @@ -35,7 +35,7 @@ PROCESSING_SUBSYSTEM_DEF(quirks) var/list/my_quirks = cli.prefs.all_quirks.Copy() var/list/cut if(job?.blacklisted_quirks) - cut = filter_quirks(my_quirks, job) + cut = filter_quirks(my_quirks, job.blacklisted_quirks) for(var/V in my_quirks) var/datum/quirk/Q = quirks[V] if(Q) @@ -63,11 +63,11 @@ PROCESSING_SUBSYSTEM_DEF(quirks) for(var/i in quirk_names) . += quirk_points_by_name(i) -/datum/controller/subsystem/processing/quirks/proc/filter_quirks(list/our_quirks, datum/job/job) +/datum/controller/subsystem/processing/quirks/proc/filter_quirks(list/our_quirks, list/blacklisted_quirks) var/list/cut = list() var/list/banned_names = list() var/pointscut = 0 - for(var/i in job.blacklisted_quirks) + for(var/i in blacklisted_quirks) var/name = quirk_name_by_path(i) if(name) banned_names += name diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index c9562bcfd1..ceb003210e 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -68,6 +68,8 @@ SUBSYSTEM_DEF(ticker) var/modevoted = FALSE //Have we sent a vote for the gamemode? + var/station_integrity = 100 // stored at roundend for use in some antag goals + /datum/controller/subsystem/ticker/Initialize(timeofday) load_mode() diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index c67a1620f1..2a7c4401f3 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -188,22 +188,44 @@ SUBSYSTEM_DEF(vote) choices[score_name]++ /datum/controller/subsystem/vote/proc/calculate_scores(var/blackbox_text) - var/list/scores_by_choice = list() for(var/choice in choices) - scores_by_choice += "[choice]" - scores_by_choice["[choice]"] = list() + scores += "[choice]" + scores["[choice]"] = 0 for(var/ckey in voted) var/list/this_vote = voted[ckey] - for(var/choice in choices) - if("[choice]" in this_vote && "[choice]" in scores_by_choice) - sorted_insert(scores_by_choice["[choice]"],this_vote["[choice]"],/proc/cmp_numeric_asc) - var/middle_score = round(GLOB.vote_score_options.len/2,1) - for(var/score_name in scores_by_choice) - var/list/score = scores_by_choice[score_name] - for(var/S in score) - scores[score_name] += S-middle_score + for(var/choice in this_vote) + scores["[choice]"] += this_vote["[choice]"] + var/min_score = 100 + var/max_score = -100 + for(var/score_name in scores) // normalize the scores from 0-1 + max_score=max(max_score,scores[score_name]) + min_score=min(min_score,scores[score_name]) + for(var/score_name in scores) + scores[score_name] = (scores[score_name]-min_score)/(max_score-min_score) SSblackbox.record_feedback("nested tally","voting",scores[score_name],list(blackbox_text,"Total scores",score_name)) +/datum/controller/subsystem/vote/proc/get_runoff_results(var/blackbox_text) + var/already_lost_runoff = list() + var/list/cur_choices = choices.Copy() + for(var/ckey in voted) + choices[choices[voted[ckey][1]]]++ // jesus christ how horrifying + for(var/_this_var_unused_ignore_it in 1 to choices.len) // if it takes more than this something REALLY wrong happened + for(var/ckey in voted) + cur_choices[cur_choices[voted[ckey][1]]]++ // jesus christ how horrifying + var/least_vote = 100000 + var/least_voted + for(var/i in 1 to cur_choices.len) + var/option = cur_choices[i] + if(cur_choices[option] > voted.len/2) + return list(option) + else if(cur_choices[option] < least_vote && !(option in already_lost_runoff)) + least_vote = cur_choices[option] + least_voted = i + already_lost_runoff += cur_choices[least_voted] + for(var/ckey in voted) + voted[ckey] -= least_voted + for(var/option in cur_choices) + cur_choices[option] = 0 /datum/controller/subsystem/vote/proc/announce_result() var/vote_title_text @@ -214,24 +236,22 @@ SUBSYSTEM_DEF(vote) else text += "[capitalize(mode)] Vote" vote_title_text = "[capitalize(mode)] Vote" - if(vote_system == RANKED_CHOICE_VOTING) + if(vote_system == SCHULZE_VOTING) calculate_condorcet_votes(vote_title_text) if(vote_system == SCORE_VOTING) - calculate_majority_judgement_vote(vote_title_text) calculate_scores(vote_title_text) - var/list/winners = get_result() + if(vote_system == MAJORITY_JUDGEMENT_VOTING) + calculate_majority_judgement_vote(vote_title_text) // nothing uses this at the moment + var/list/winners = vote_system == INSTANT_RUNOFF_VOTING ? get_runoff_results() : get_result() var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic" if(winners.len > 0) if(was_roundtype_vote) 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!" - if(mode == "mode tiers") - for(var/score_name in scores) - var/score = scores[score_name] - if(!score) - score = 0 - text = "\n[score_name]: [obfuscated ? "???" : score]" + if(!obfuscated) + if(vote_system == SCHULZE_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!" + if(vote_system == MAJORITY_JUDGEMENT_VOTING) + text += "\nIt should be noted that this is not a raw tally of votes but the number of runoffs done by majority judgement!" else for(var/i=1,i<=choices.len,i++) var/votes = choices[choices[i]] @@ -249,6 +269,15 @@ SUBSYSTEM_DEF(vote) text += "\nVote Result: [obfuscated ? "???" : .]" //CIT CHANGE - adds obfuscated votes else text += "\nDid not vote: [GLOB.clients.len-voted.len]" + else if(vote_system == SCORE_VOTING) + for(var/score_name in scores) + var/score = scores[score_name] + if(!score) + score = 0 + if(was_roundtype_vote) + stored_gamemode_votes[score_name] = score + text = "\n[score_name]: [obfuscated ? "???" : score]" + . = 1 else text += "Vote Result: Inconclusive - No Votes!" log_vote(text) @@ -258,7 +287,7 @@ SUBSYSTEM_DEF(vote) 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) + if(SCHULZE_VOTING,INSTANT_RUNOFF_VOTING) for(var/i=1,i<=voted.len,i++) var/list/myvote = voted[voted[i]] if(islist(myvote)) @@ -266,13 +295,18 @@ SUBSYSTEM_DEF(vote) 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 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!" - else if(vote_system == SCORE_VOTING) - admintext += "\nIt should be noted that this is not a raw tally of votes but the number of runoffs done by majority judgement!" - for(var/i=1,i<=choices.len,i++) - var/votes = choices[choices[i]] - admintext += "\n[choices[i]]: [votes]" + if(vote_system != SCORE_VOTING) + if(vote_system == SCHULZE_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!" + else if(vote_system == MAJORITY_JUDGEMENT_VOTING) + admintext += "\nIt should be noted that this is not a raw tally of votes but the number of runoffs done by majority judgement!" + for(var/i=1,i<=choices.len,i++) + var/votes = choices[choices[i]] + admintext += "\n[choices[i]]: [votes]" + else + for(var/i=1,i<=scores.len,i++) + var/score = scores[scores[i]] + admintext += "\n[scores[i]]: [score]" message_admins(admintext) return . @@ -316,15 +350,13 @@ SUBSYSTEM_DEF(vote) if("dynamic") 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.") - if(. == "Secret") - GLOB.master_mode = "secret" - SSticker.save_mode(.) - message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]") - log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].") - else - GLOB.master_mode = "dynamic" - var/datum/dynamic_storyteller/S = config.pick_storyteller(.) - GLOB.dynamic_storyteller_type = S + GLOB.master_mode = "dynamic" + var/list/runnable_storytellers = config.get_runnable_storytellers() + for(var/T in runnable_storytellers) + var/datum/dynamic_storyteller/S = T + runnable_storytellers[S] *= scores[initial(S.name)] + var/datum/dynamic_storyteller/S = pickweightAllowZero(runnable_storytellers) + GLOB.dynamic_storyteller_type = S if("map") var/datum/map_config/VM = config.maplist[.] message_admins("The map has been voted for and will change to: [VM.map_name]") @@ -375,7 +407,7 @@ SUBSYSTEM_DEF(vote) voted[usr.ckey] = list(vote) choices[choices[vote]]++ return vote - if(RANKED_CHOICE_VOTING) + if(SCHULZE_VOTING,INSTANT_RUNOFF_VOTING) if(usr.ckey in voted) if(vote in voted[usr.ckey]) voted[usr.ckey] -= vote @@ -384,7 +416,7 @@ SUBSYSTEM_DEF(vote) voted[usr.ckey] = list() voted[usr.ckey] += vote saved -= usr.ckey - if(SCORE_VOTING) + if(SCORE_VOTING,MAJORITY_JUDGEMENT_VOTING) if(!(usr.ckey in voted)) voted += usr.ckey voted[usr.ckey] = list() @@ -444,15 +476,10 @@ SUBSYSTEM_DEF(vote) if("dynamic") for(var/T in config.storyteller_cache) var/datum/dynamic_storyteller/S = T - var/recent_rounds = 0 - for(var/i in 1 to SSpersistence.saved_storytellers.len) - if(SSpersistence.saved_storytellers[i] == initial(S.name)) - recent_rounds++ - if(recent_rounds < initial(S.weight)) + var/list/probabilities = CONFIG_GET(keyed_list/storyteller_weight) + if(probabilities[initial(S.config_tag)] > 0) 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") question = stripped_input(usr,"What is the vote for?") if(!question) @@ -514,9 +541,9 @@ SUBSYSTEM_DEF(vote) . += "

Vote one.

" if(APPROVAL_VOTING) . += "

Vote any number of choices.

" - if(RANKED_CHOICE_VOTING) + if(SCHULZE_VOTING,INSTANT_RUNOFF_VOTING) . += "

Vote by order of preference. Revoting will demote to the bottom. 1 is your favorite, and higher numbers are worse.

" - if(SCORE_VOTING) + if(SCORE_VOTING,MAJORITY_JUDGEMENT_VOTING) . += "

Grade the candidates by how much you like them.

" . += "

No-votes have no power--your opinion is only heard if you vote!

" . += "Time Left: [DisplayTimeText(end_time-world.time)]

" - if(RANKED_CHOICE_VOTING) + if(SCHULZE_VOTING,INSTANT_RUNOFF_VOTING) var/list/myvote = voted[C.ckey] for(var/i=1,i<=choices.len,i++) var/vote = (islist(myvote) ? (myvote.Find(i)) : 0) @@ -553,7 +580,7 @@ SUBSYSTEM_DEF(vote) . += "(Saved!)" . += "(Load vote from save)" . += "(Reset votes)" - if(SCORE_VOTING) + if(SCORE_VOTING,MAJORITY_JUDGEMENT_VOTING) var/list/myvote = voted[C.ckey] for(var/i=1,i<=choices.len,i++) . += "
  • [choices[i]]" @@ -656,7 +683,7 @@ SUBSYSTEM_DEF(vote) voted[usr.ckey] = SSpersistence.saved_votes[usr.ckey][mode] if(islist(voted[usr.ckey])) var/malformed = FALSE - if(vote_system == SCORE_VOTING) + if(vote_system == SCORE_VOTING || vote_system == MAJORITY_JUDGEMENT_VOTING) for(var/thing in voted[usr.ckey]) if(!(thing in choices)) malformed = TRUE @@ -670,7 +697,7 @@ SUBSYSTEM_DEF(vote) to_chat(usr,"Your saved vote was malformed! Start over!") voted -= usr.ckey else - if(vote_system == SCORE_VOTING) + if(vote_system == SCORE_VOTING || vote_system == MAJORITY_JUDGEMENT_VOTING) submit_vote(round(text2num(href_list["vote"])),round(text2num(href_list["score"]))) else submit_vote(round(text2num(href_list["vote"]))) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index a3c5979b6e..61d467a20d 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -458,6 +458,7 @@ var/list/allowed_types = list( /datum/objective/assassinate, + /datum/objective/assassinate/once, /datum/objective/maroon, /datum/objective/debrain, /datum/objective/protect, diff --git a/code/datums/traits/_quirk.dm b/code/datums/traits/_quirk.dm index d68b11135f..c6466fdd96 100644 --- a/code/datums/traits/_quirk.dm +++ b/code/datums/traits/_quirk.dm @@ -8,6 +8,7 @@ var/gain_text var/lose_text var/medical_record_text //This text will appear on medical records for the trait. Not yet implemented + var/antag_removal_text // Text will be given to the quirk holder if they get an antag that has it blacklisted. var/mood_quirk = FALSE //if true, this quirk affects mood and is unavailable if moodlets are disabled var/mob_trait //if applicable, apply and remove this mob trait var/mob/living/quirk_holder diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm index b19c6a71f7..faa7eec392 100644 --- a/code/datums/traits/negative.dm +++ b/code/datums/traits/negative.dm @@ -6,6 +6,7 @@ value = -2 gain_text = "You feel your vigor slowly fading away." lose_text = "You feel vigorous again." + antag_removal_text = "Your antagonistic nature has removed your blood deficiency." medical_record_text = "Patient requires regular treatment for blood loss due to low production of blood." /datum/quirk/blooddeficiency/on_process() @@ -38,6 +39,8 @@ var/obj/item/heirloom var/where +GLOBAL_LIST_EMPTY(family_heirlooms) + /datum/quirk/family_heirloom/on_spawn() var/mob/living/carbon/human/H = quirk_holder var/obj/item/heirloom_type @@ -76,6 +79,7 @@ /obj/item/lighter, /obj/item/dice/d20) heirloom = new heirloom_type(get_turf(quirk_holder)) + GLOB.family_heirlooms += heirloom var/list/slots = list( "in your left pocket" = SLOT_L_STORE, "in your right pocket" = SLOT_R_STORE, @@ -188,11 +192,7 @@ gain_text = "You feel repulsed by the thought of violence!" lose_text = "You think you can defend yourself again." medical_record_text = "Patient is unusually pacifistic and cannot bring themselves to cause physical harm." - -/datum/quirk/nonviolent/on_process() - if(quirk_holder.mind && LAZYLEN(quirk_holder.mind.antag_datums)) - to_chat(quirk_holder, "Your antagonistic nature has caused you to renounce your pacifism.") - qdel(src) + antag_removal_text = "Your antagonistic nature has caused you to renounce your pacifism." /datum/quirk/paraplegic name = "Paraplegic" @@ -355,6 +355,7 @@ gain_text = "You find yourself unable to speak!" lose_text = "You feel a growing strength in your vocal chords." medical_record_text = "Functionally mute, patient is unable to use their voice in any capacity." + antag_removal_text = "Your antagonistic nature has caused your voice to be heard." var/datum/brain_trauma/severe/mute/mute /datum/quirk/mute/add() @@ -366,11 +367,6 @@ var/mob/living/carbon/human/H = quirk_holder H?.cure_trauma_type(mute, TRAUMA_RESILIENCE_ABSOLUTE) -/datum/quirk/mute/on_process() - if(quirk_holder.mind && LAZYLEN(quirk_holder.mind.antag_datums)) - to_chat(quirk_holder, "Your antagonistic nature has caused your voice to be heard.") - qdel(src) - /datum/quirk/unstable name = "Unstable" desc = "Due to past troubles, you are unable to recover your sanity if you lose it. Be very careful managing your mood!" diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 821c819664..6fe52b0f8d 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -239,12 +239,20 @@ GLOBAL_VAR_INIT(dynamic_storyteller_type, null) . += "Peaceful Waypoint
    " . += "Your station orbits deep within controlled, core-sector systems and serves as a waypoint for routine traffic through Nanotrasen's trade empire. Due to the combination of high security, interstellar traffic, and low strategic value, it makes any direct threat of violence unlikely. Your primary enemies will be incompetence and bored crewmen: try to organize team-building events to keep staffers interested and productive. However, even deep in our territory there may be subversive elements, especially for such a high-value target as your station. Keep an eye out, but don't expect much trouble." set_security_level(SEC_LEVEL_GREEN) + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = new T + if(!(G in station_goals)) + station_goals += G if(21 to 79) var/perc_green = 100-round(100*((threat_level-21)/(79-21))) if(prob(perc_green)) . += "Core Territory
    " . += "Your station orbits within reliably mundane, secure space. Although Nanotrasen has a firm grip on security in your region, the valuable resources and strategic position aboard your station make it a potential target for infiltrations. Monitor crew for non-loyal behavior, but expect a relatively tame shift free of large-scale destruction. We expect great things from your station." set_security_level(SEC_LEVEL_GREEN) + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = new T + if(!(G in station_goals)) + station_goals += G else if(prob(perc_green)) . += "Contested System
    " . += "Your station's orbit passes along the edge of Nanotrasen's sphere of influence. While subversive elements remain the most likely threat against your station, hostile organizations are bolder here, where our grip is weaker. Exercise increased caution against elite Syndicate strike forces, or Executives forbid, some kind of ill-conceived unionizing attempt." @@ -273,7 +281,7 @@ GLOBAL_VAR_INIT(dynamic_storyteller_type, null) if(GLOB.security_level >= SEC_LEVEL_BLUE) priority_announce("A summary has been copied and printed to all communications consoles.", "Security level elevated.", "intercept") else - priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no likely threats to [station_name()]. Have a secure shift!", "Security Report", "commandreport") + priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no likely threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", "commandreport") // Yes, this is copy pasted from game_mode /datum/game_mode/dynamic/check_finished(force_ending) @@ -346,7 +354,8 @@ GLOBAL_VAR_INIT(dynamic_storyteller_type, null) generate_threat() storyteller.start_injection_cooldowns() - + SSevents.frequency_lower = storyteller.event_frequency_lower // 6 minutes by default + SSevents.frequency_upper = storyteller.event_frequency_upper // 20 minutes by default log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!") initial_threat_level = threat_level return TRUE @@ -395,7 +404,7 @@ GLOBAL_VAR_INIT(dynamic_storyteller_type, null) /datum/game_mode/dynamic/post_setup(report) update_playercounts() - + for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules) addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_roundstart_rule, rule), rule.delay) ..() diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index f4e9971a05..f6755057e2 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -47,6 +47,8 @@ assigned += M.mind M.mind.special_role = antag_flag M.mind.add_antag_datum(antag_datum) + log_admin("[M.name] was made into a [name] by dynamic.") + message_admins("[M.name] was made into a [name] by dynamic.") return TRUE ////////////////////////////////////////////// @@ -72,12 +74,6 @@ property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1) 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 // @@ -225,3 +221,25 @@ log_admin("[M.name] was made into a bloodsucker by dynamic.") message_admins("[M.name] was made into a bloodsucker by dynamic.") return TRUE + +////////////////////////////////////////////// +// // +// COLLECTOR // +// // +////////////////////////////////////////////// + +/datum/dynamic_ruleset/latejoin/collector + name = "Contraband Collector" + config_tag = "latejoin_collector" + antag_datum = /datum/antagonist/collector + antag_flag = ROLE_MINOR_ANTAG + 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 = 5 + cost = 1 + requirements = list(10,10,10,10,10,10,10,10,10,10) + high_population_requirement = 10 + repeatable = TRUE + flags = TRAITOR_RULESET + property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 2) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 92a02f5920..abd560171e 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -494,7 +494,7 @@ high_population_requirement = 50 repeatable_weight_decrease = 2 repeatable = TRUE - property_weights = list("story_potential" = 1, "trust" = 1, "extended" = 1, "valid" = 2, "integrity" = 2) + property_weights = list("story_potential" = 1, "trust" = 1, "extended" = 1, "valid" = 2, "integrity" = 1) var/list/spawn_locs = list() /datum/dynamic_ruleset/midround/from_ghosts/nightmare/execute() diff --git a/code/game/gamemodes/dynamic/dynamic_storytellers.dm b/code/game/gamemodes/dynamic/dynamic_storytellers.dm index afa6ed8ba7..303c436f99 100644 --- a/code/game/gamemodes/dynamic/dynamic_storytellers.dm +++ b/code/game/gamemodes/dynamic/dynamic_storytellers.dm @@ -1,5 +1,6 @@ /datum/dynamic_storyteller var/name = "none" + var/config_tag = null var/desc = "A coder's idiocy." var/list/property_weights = list() var/curve_centre = 0 @@ -7,6 +8,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/event_frequency_lower = 6 MINUTES + var/event_frequency_upper = 20 MINUTES var/datum/game_mode/dynamic/mode = null /** @@ -20,14 +23,6 @@ Property weights are: "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 @@ -42,7 +37,28 @@ Property weights are: return /datum/dynamic_storyteller/proc/on_start() - return + if (istype(SSticker.mode, /datum/game_mode/dynamic)) + mode = SSticker.mode + GLOB.dynamic_curve_centre = curve_centre + GLOB.dynamic_curve_width = curve_width + if(flags & USE_PREF_WEIGHTS) + var/voters = 0 + var/mean = 0 + for(var/client/c in GLOB.clients) + var/vote = c.prefs.preferred_chaos + if(vote) + voters += 1 + switch(vote) + if(CHAOS_NONE) + mean -= 5 + if(CHAOS_LOW) + mean -= 2.5 + if(CHAOS_HIGH) + mean += 2.5 + if(CHAOS_MAX) + mean += 5 + GLOB.dynamic_curve_centre += (mean/voters) + GLOB.dynamic_forced_threat_level = forced_threat_level /datum/dynamic_storyteller/proc/get_midround_cooldown() var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min) @@ -154,12 +170,15 @@ Property weights are: /datum/dynamic_storyteller/cowabunga name = "Chaotic" + config_tag = "chaotic" curve_centre = 10 desc = "Chaos: high. Variation: high. Likely antags: clock cult, revs, wizard." property_weights = list("extended" = -1, "chaos" = 10) - weight = 2 + weight = 1 + event_frequency_lower = 2 MINUTES + event_frequency_upper = 10 MINUTES flags = WAROPS_ALWAYS_ALLOWED - var/refund_cooldown + var/refund_cooldown = 0 /datum/dynamic_storyteller/cowabunga/get_midround_cooldown() return ..() / 4 @@ -169,12 +188,13 @@ Property weights are: /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 + mode.refund_threat(20) + mode.log_threat("Cowabunga it is. Refunded 20 threat. Threat is now [mode.threat].") + refund_cooldown = world.time + 600 SECONDS /datum/dynamic_storyteller/team name = "Teamwork" + config_tag = "teamwork" desc = "Chaos: high. Variation: low. Likely antags: nukies, clockwork cult, wizard, blob, xenomorph." curve_centre = 2 curve_width = 1.5 @@ -187,6 +207,7 @@ Property weights are: /datum/dynamic_storyteller/conversion name = "Conversion" + config_tag = "conversion" desc = "Chaos: high. Variation: medium. Likely antags: cults, bloodsuckers, revs." curve_centre = 3 curve_width = 1 @@ -196,24 +217,33 @@ Property weights are: /datum/dynamic_storyteller/classic name = "Random" + config_tag = "random" desc = "Chaos: varies. Variation: highest. No special weights attached." weight = 6 + flags = USE_PREF_WEIGHTS curve_width = 4 /datum/dynamic_storyteller/memes name = "Story" + config_tag = "story" desc = "Chaos: varies. Variation: high. Likely antags: abductors, nukies, wizard, traitor." + weight = 4 + flags = USE_PREF_WEIGHTS curve_width = 4 property_weights = list("story_potential" = 10) /datum/dynamic_storyteller/suspicion name = "Intrigue" + config_tag = "intrigue" desc = "Chaos: low. Variation: high. Likely antags: traitor, bloodsucker. Rare: revs, blood cult." + weight = 4 + flags = USE_PREF_WEIGHTS curve_width = 4 property_weights = list("trust" = -5) /datum/dynamic_storyteller/liteextended name = "Calm" + config_tag = "calm" desc = "Chaos: low. Variation: medium. Likely antags: bloodsuckers, traitors, sentient disease, revenant." curve_centre = -5 curve_width = 0.5 @@ -226,10 +256,12 @@ Property weights are: /datum/dynamic_storyteller/extended name = "Extended" + config_tag = "extended" desc = "Chaos: none. Variation: none. Likely antags: none." curve_centre = -20 - weight = 2 + weight = 0 curve_width = 0.5 /datum/dynamic_storyteller/extended/on_start() + ..() GLOB.dynamic_forced_extended = TRUE diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index b7003e4f1e..459ff2f3d6 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -9,9 +9,10 @@ GLOBAL_LIST_EMPTY(objectives) var/explanation_text = "Nothing" //What that person is supposed to do. var/team_explanation_text //For when there are multiple owners. var/datum/mind/target = null //If they are focused on a particular person. - var/target_amount = 0 //If they are focused on a particular number. Steal objectives have their own counter. - var/completed = 0 //currently only used for custom objectives. - var/martyr_compatible = 0 //If the objective is compatible with martyr objective, i.e. if you can still do it while dead. + var/target_amount = FALSE //If they are focused on a particular number. Steal objectives have their own counter. + var/completed = FALSE //currently only used for custom objectives. + var/completable = TRUE //Whether this objective shows greentext when completed + var/martyr_compatible = FALSE //If the objective is compatible with martyr objective, i.e. if you can still do it while dead. /datum/objective/New(var/text) GLOB.objectives += src // CITADEL EDIT FOR CRYOPODS @@ -172,6 +173,26 @@ GLOBAL_LIST_EMPTY(objectives) /datum/objective/assassinate/admin_edit(mob/admin) admin_simple_target_pick(admin) +/datum/objective/assassinate/once + name = "kill once" + var/won = FALSE + +/datum/objective/assassinate/once/update_explanation_text() + ..() + if(target && target.current) + explanation_text = "Kill [target.name], the [!target_role_type ? target.assigned_role : target.special_role]. You only need to kill them once; if they come back, you've still succeeded." + START_PROCESSING(SSprocessing,src) + else + explanation_text = "Free Objective" + +/datum/objective/assassinate/once/check_completion() + return won || ..() + +/datum/objective/assassinate/once/process() + won = check_completion() + if(won) + STOP_PROCESSING(SSprocessing,src) + /datum/objective/assassinate/internal var/stolen = 0 //Have we already eliminated this target? @@ -365,6 +386,28 @@ GLOBAL_LIST_EMPTY(objectives) return FALSE return TRUE +/datum/objective/breakout + name = "breakout" + martyr_compatible = 1 + var/target_role_type = 0 + var/human_check = TRUE + +/datum/objective/breakout/check_completion() + return !target || considered_escaped(target) + +/datum/objective/breakout/find_target_by_role(role, role_type=0, invert=0) + if(!invert) + target_role_type = role_type + ..() + return target + +/datum/objective/breakout/update_explanation_text() + ..() + if(target && target.current) + explanation_text = "Make sure [target.name], the [!target_role_type ? target.assigned_role : target.special_role] escapes on the shuttle or an escape pod alive and without being in custody." + else + explanation_text = "Free Objective" + /datum/objective/escape/escape_with_identity name = "escape with identity" var/target_real_name // Has to be stored because the target's real_name can change over the course of the round @@ -582,7 +625,6 @@ GLOBAL_LIST_EMPTY(possible_items_special) explanation_text = "Do not give up or lose [targetinfo.name]." steal_target = targetinfo.targetitem - /datum/objective/download name = "download" @@ -786,40 +828,10 @@ GLOBAL_LIST_EMPTY(possible_items_special) /datum/objective/destroy/internal var/stolen = FALSE //Have we already eliminated this target? -/datum/objective/steal_five_of_type - name = "steal five of" - explanation_text = "Steal at least five items!" - var/list/wanted_items = list(/obj/item) - -/datum/objective/steal_five_of_type/New() - ..() - wanted_items = typecacheof(wanted_items) - -/datum/objective/steal_five_of_type/summon_guns - name = "steal guns" - explanation_text = "Steal at least five guns!" - wanted_items = list(/obj/item/gun) - -/datum/objective/steal_five_of_type/summon_magic - name = "steal magic" - explanation_text = "Steal at least five magical artefacts!" - wanted_items = list(/obj/item/spellbook, /obj/item/gun/magic, /obj/item/clothing/suit/space/hardsuit/wizard, /obj/item/scrying, /obj/item/antag_spawner/contract, /obj/item/necromantic_stone) - -/datum/objective/steal_five_of_type/check_completion() - var/list/datum/mind/owners = get_owners() - var/stolen_count = 0 - for(var/datum/mind/M in owners) - if(!isliving(M.current)) - continue - var/list/all_items = M.current.GetAllContents() //this should get things in cheesewheels, books, etc. - for(var/obj/I in all_items) //Check for wanted items - if(is_type_in_typecache(I, wanted_items)) - stolen_count++ - return stolen_count >= 5 - //Created by admin tools /datum/objective/custom name = "custom" + completable = FALSE /datum/objective/custom/admin_edit(mob/admin) var/expl = stripped_input(admin, "Custom objective:", "Objective", explanation_text) @@ -997,4 +1009,147 @@ GLOBAL_LIST_EMPTY(possible_items_special) command_staff_only = TRUE +/datum/objective/hoard + name = "hoard" + var/obj/item/hoarded_item = null +/datum/objective/hoard/get_target() + return hoarded_item + +/datum/objective/hoard/proc/set_target(obj/item/I) + if(I) + hoarded_item = I + explanation_text = "Keep [I] on your person at all times." + return hoarded_item + else + explanation_text = "Free objective" + return + +/datum/objective/hoard/check_completion() + var/list/datum/mind/owners = get_owners() + if(!hoarded_item) + return TRUE + for(var/datum/mind/M in owners) + if(!isliving(M.current)) + continue + + var/list/all_items = M.current.GetAllContents() //this should get things in cheesewheels, books, etc. + + for(var/obj/I in all_items) //Check for items + if(I == hoarded_item) + return TRUE + return FALSE + +/datum/objective/hoard/heirloom + name = "steal heirloom" + +/datum/objective/hoard/heirloom/find_target() + set_target(pick(GLOB.family_heirlooms)) + +GLOBAL_LIST_EMPTY(traitor_contraband) + +GLOBAL_LIST_EMPTY(cult_contraband) + +/datum/objective/hoard/collector + name = "Hoard contraband" + +/datum/objective/collector/New() + ..() + if(!GLOB.traitor_contraband.len)//Only need to fill the list when it's needed. + GLOB.traitor_contraband = list(/obj/item/card/emag/empty,/obj/item/clothing/glasses/phantomthief,/obj/item/clothing/gloves/chameleon/broken) + if(!GLOB.cult_contraband.len) + GLOB.cult_contraband = list(/obj/item/clockwork/slab,/obj/item/clockwork/component/belligerent_eye,/obj/item/clockwork/component/belligerent_eye/lens_gem,/obj/item/shuttle_curse,/obj/item/cult_shift) + +/datum/objective/hoard/collector/find_target() + var/obj/item/I + var/I_type + if(prob(50)) + I_type = pick_n_take(GLOB.traitor_contraband) // always unique unless it's run out, in which case we refill it anyway + else + I_type = pick_n_take(GLOB.cult_contraband) + I = new I_type + I.forceMove(get_turf(owner)) + if(ishuman(owner)) + var/mob/living/carbon/human/H = owner + H.equip_in_one_of_slots(I, list("backpack" = SLOT_IN_BACKPACK)) + hoarded_item = I + + + +GLOBAL_LIST_EMPTY(possible_sabotages) +// For saboteurs. Go in and cause some trouble somewhere. Not necessarily breaking things, just sufficiently troublemaking. +/datum/objective/sabotage + name = "sabotage" + var/datum/sabotage_objective/targetinfo = null //composition > inheritance. + +/datum/objective/sabotage/get_target() + return targetinfo.sabotage_type + +/datum/objective/sabotage/New() + ..() + if(!GLOB.possible_sabotages.len)//Only need to fill the list when it's needed. + for(var/I in subtypesof(/datum/sabotage_objective)) + new I + +/datum/objective/sabotage/find_target() + var/list/datum/mind/owners = get_owners() + var/approved_targets = list() + check_sabotages: + for(var/datum/sabotage_objective/possible_sabotage in GLOB.possible_sabotages) + if(!is_unique_objective(possible_sabotage.sabotage_type) || possible_sabotage.check_conditions()) + continue + for(var/datum/mind/M in owners) + if(M.current.mind.assigned_role in possible_sabotage.excludefromjob) + continue check_sabotages + approved_targets += possible_sabotage + return set_target(safepick(approved_targets)) + +/datum/objective/sabotage/proc/set_target(datum/sabotage_objective/sabo) + if(sabo) + targetinfo = sabo + explanation_text = "[targetinfo.name]" + give_special_equipment(targetinfo.special_equipment) + return sabo + else + explanation_text = "Free objective" + return + +/datum/objective/sabotage/check_completion() + return targetinfo.check_conditions() + +/datum/objective/flavor + name = "flavor" + completable = FALSE + var/flavor_file + +/datum/objective/flavor/proc/get_flavor_list() + return world.file2list(flavor_file) + +/datum/objective/flavor/proc/forge_objective() + var/flavor_list = get_flavor_list() + explanation_text = pick(flavor_list) + +/datum/objective/flavor/traitor + name = "traitor flavor" + flavor_file = "strings/flavor_objectives/traitor.txt" + +/datum/objective/flavor/traitor/get_flavor_list() + . = ..() + switch(owner.assigned_role) + if("Station Engineer", "Atmospheric Technician") + . += world.file2list("strings/flavor_objectives/traitor/engineering.txt") + if("Medical Doctor","Chemist","Virologist","Geneticist") + . += world.file2list("strings/flavor_objectives/traitor/medical.txt") + if("Scientist","Roboticist","Geneticist") + . += world.file2list("strings/flavor_objectives/traitor/science.txt") + if("Assistant") + . += world.file2list("strings/flavor_objectives/traitor/assistant.txt") + +/datum/objective/flavor/ninja_helping + flavor_file = "strings/flavor_objectives/ninja_helping.txt" + +/datum/objective/flavor/ninja_syndie + flavor_file = "strings/flavor_objectives/ninja_syndie.txt" + +/datum/objective/flavor/wizard + flavor_file = "strings/flavor_objectives/wizard.txt" diff --git a/code/game/gamemodes/objective_sabotage.dm b/code/game/gamemodes/objective_sabotage.dm new file mode 100644 index 0000000000..248524f1d2 --- /dev/null +++ b/code/game/gamemodes/objective_sabotage.dm @@ -0,0 +1,107 @@ +/datum/sabotage_objective + var/name = "Free Objective" + var/sabotage_type = "nothing" + var/special_equipment = list() + var/list/excludefromjob = list() + +/datum/sabotage_objective/New() + ..() + if(sabotage_type!="nothing") + GLOB.possible_sabotages += src + +/datum/sabotage_objective/proc/check_conditions() + return TRUE + +/datum/sabotage_objective/processing + var/won = FALSE + +/datum/sabotage_objective/processing/New() + ..() + START_PROCESSING(SSprocessing, src) + +/datum/sabotage_objective/processing/proc/check_condition_processing() + return 100 + +/datum/sabotage_objective/processing/process() + check_condition_processing() + if(won >= 100) + STOP_PROCESSING(SSprocessing,src) + +/datum/sabotage_objective/processing/check_conditions() + return won + +/datum/sabotage_objective/processing/power_sink + name = "Drain at least 1 gigajoule of power using a power sink." + sabotage_type = "powersink" + special_equipment = list(/obj/item/powersink) + var/sink_found = FALSE + var/count = 0 + +/datum/sabotage_objective/processing/power_sink/check_condition_processing() + count += 1 + if(count==10 || sink_found) // doesn't need to fire that often unless a sink exists + var/sink_found_this_time = FALSE + for(var/datum/powernet/PN in GLOB.powernets) + for(var/obj/item/powersink/sink in PN.nodes) + sink_found_this_time = TRUE + won = max(won,sink.power_drained/1e9) + sink_found = sink_found_this_time + count = 0 + return FALSE + +/obj/item/paper/guides/antag/supermatter_sabotage + info = "Ways to sabotage a supermatter:
    \ + " + +/datum/sabotage_objective/processing/supermatter + name = "Sabotage the supermatter so that it goes under 50% integrity. If it is delaminated, you will fail." + sabotage_type = "supermatter" + special_equipment = list(/obj/item/paper/guides/antag/supermatter_sabotage) + var/list/supermatters = list() + excludefromjob = list("Chief Engineer", "Station Engineer", "Atmospheric Technician") + +/datum/sabotage_objective/processing/supermatter/check_condition_processing() + if(!supermatters.len) + supermatters = list() + for(var/obj/machinery/power/supermatter_crystal/S in GLOB.machines) + // Delaminating, not within coverage, not on a tile. + if (!isturf(S.loc) || !(is_station_level(S.z) || is_mining_level(S.z))) + continue + supermatters.Add(S) + for(var/obj/machinery/power/supermatter_crystal/S in supermatters) // you can win this with a wishgranter... lol. + won = max(1-((S.get_integrity()-50)/50),won) + return FALSE + +/datum/sabotage_objective/station_integrity + name = "Make sure the station is at less than 80% integrity by the end. Smash walls, windows etc. to reach this goal." + sabotage_type = "integrity" + +/datum/sabotage_objective/station_integrity/check_conditions() + return 5-(max(SSticker.station_integrity*4,320)/80) + +/datum/sabotage_objective/cloner + name = "Destroy all Nanotrasen cloning machines." + sabotage_type = "cloner" + +/datum/sabotage_objective/cloner/check_conditions() + return !(locate(/obj/machinery/clonepod) in GLOB.machines) + +/datum/sabotage_objective/ai_law + name = "Upload a hacked law to the AI." + sabotage_type = "ailaw" + special_equipment = list(/obj/item/aiModule/syndicate) + excludefromjob = list("Chief Engineer","Research Director","Head of Personnel","Captain","Chief Medical Officer","Head Of Security") + +/datum/sabotage_objective/ai_law/check_conditions() + for (var/i in GLOB.ai_list) + var/mob/living/silicon/ai/aiPlayer = i + if(aiPlayer.mind && length(aiPlayer.laws.hacked)) + return TRUE + return FALSE diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index 5e5ced2269..1bef477a30 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -128,6 +128,9 @@ return . = ..() +/obj/item/card/emag/empty + uses = 0 + /obj/item/emagrecharge name = "electromagnet charging device" desc = "A small cell with two prongs lazily jabbed into it. It looks like it's made for charging the small batteries found in electromagnetic devices, sadly this can't be recharged like a normal cell." @@ -538,4 +541,4 @@ update_label("John Doe", "Clowny") id_color = "#0000FF" /obj/item/card/id/knight/captain - id_color = "#FFD700" \ No newline at end of file + id_color = "#FFD700" diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm index e47021c1f6..2762be8edf 100644 --- a/code/game/objects/items/storage/book.dm +++ b/code/game/objects/items/storage/book.dm @@ -244,3 +244,6 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "bible", /obj/item/storage/book/bible/syndicate/add_blood_DNA(list/blood_dna) return FALSE + +/obj/item/storage/book/bible/syndicate/empty + uses = 0 diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm index 1672310567..19e53b09b4 100644 --- a/code/modules/admin/antag_panel.dm +++ b/code/modules/admin/antag_panel.dm @@ -214,4 +214,4 @@ GLOBAL_VAR(antag_prototypes) var/datum/browser/panel = new(usr, "traitorpanel", "", 600, 600) panel.set_content(out) panel.open() - return \ No newline at end of file + return diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index e15225b388..0c05afb155 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -20,6 +20,7 @@ GLOBAL_LIST_EMPTY(antagonists) var/show_in_antagpanel = TRUE //This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind var/antagpanel_category = "Uncategorized" //Antagpanel will display these together, REQUIRED var/show_name_in_check_antagonists = FALSE //Will append antagonist name in admin listings - use for categories that share more than one antag type + var/list/blacklisted_quirks = list(/datum/quirk/nonviolent,/datum/quirk/mute) // Quirks that will be removed upon gaining this antag. Pacifist and mute are default. /datum/antagonist/New() GLOB.antagonists += src @@ -70,6 +71,7 @@ GLOBAL_LIST_EMPTY(antagonists) greet() apply_innate_effects() give_antag_moodies() + remove_blacklisted_quirks() if(is_banned(owner.current) && replace_banned) replace_banned_player() @@ -117,6 +119,17 @@ GLOBAL_LIST_EMPTY(antagonists) return SEND_SIGNAL(owner.current, COMSIG_CLEAR_MOOD_EVENT, "antag_moodlet") +/datum/antagonist/proc/remove_blacklisted_quirks() + var/mob/living/L = owner + if(istype(L)) + var/list/cut = list() + cut = SSquirks.filter_quirks(L.roundstart_quirks,blacklisted_quirks) + for(var/q in cut) + var/datum/quirk/Q = q + if(Q.antag_removal_text) + to_chat(L, "[Q.antag_removal_text]") + L.remove_quirk(Q.type) + //Returns the team antagonist belongs to if any. /datum/antagonist/proc/get_team() return @@ -134,7 +147,7 @@ GLOBAL_LIST_EMPTY(antagonists) if(objectives.len) report += printobjectives(objectives) for(var/datum/objective/objective in objectives) - if(!objective.check_completion()) + if(objective.completable && !objective.check_completion()) objectives_complete = FALSE break diff --git a/code/modules/antagonists/_common/antag_team.dm b/code/modules/antagonists/_common/antag_team.dm index 486b5b0414..027abc7c94 100644 --- a/code/modules/antagonists/_common/antag_team.dm +++ b/code/modules/antagonists/_common/antag_team.dm @@ -36,11 +36,17 @@ var/win = TRUE var/objective_count = 1 for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - report += "Objective #[objective_count]: [objective.explanation_text] Success!" + if(objective.completable) + var/completion = objective.check_completion() + if(completion >= 1) + report += "Objective #[objective_count]: [objective.explanation_text] Success!" + else if(completion <= 0) + report += "Objective #[objective_count]: [objective.explanation_text] Fail." + win = FALSE + else + report += "Objective #[objective_count]: [objective.explanation_text] [completion*100]%" else - report += "Objective #[objective_count]: [objective.explanation_text] Fail." - win = FALSE + report += "Objective #[objective_count]: [objective.explanation_text]" objective_count++ if(win) report += "The [name] was successful!" diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 7c589bb3ab..2292006da1 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -108,11 +108,17 @@ var/win = TRUE var/objective_count = 1 for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - parts += "Objective #[objective_count]: [objective.explanation_text] Success!" + if(objective.completable) + var/completion = objective.check_completion() + if(completion >= 1) + parts += "Objective #[objective_count]: [objective.explanation_text] Success!" + else if(completion <= 0) + parts += "Objective #[objective_count]: [objective.explanation_text] Fail." + win = FALSE + else + parts += "Objective #[objective_count]: [objective.explanation_text] [completion*100]%" else - parts += "Objective #[objective_count]: [objective.explanation_text] Fail." - win = FALSE + parts += "Objective #[objective_count]: [objective.explanation_text]" objective_count++ if(win) parts += "The blood brothers were successful!" diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index 0d68660d9d..a81c409233 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -54,8 +54,10 @@ var/honorific if(owner.current.gender == FEMALE) honorific = "Ms." - else + else if(owner.current.gender == MALE) honorific = "Mr." + else + honorific = "Mx." if(GLOB.possible_changeling_IDs.len) changelingID = pick(GLOB.possible_changeling_IDs) GLOB.possible_changeling_IDs -= changelingID @@ -552,11 +554,17 @@ if(objectives.len) var/count = 1 for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - parts += "Objective #[count]: [objective.explanation_text] Success!" + if(objective.completable) + var/completion = objective.check_completion() + if(completion >= 1) + parts += "Objective #[count]: [objective.explanation_text] Success!" + else if(completion <= 0) + parts += "Objective #[count]: [objective.explanation_text] Fail." + changelingwin = FALSE + else + parts += "Objective #[count]: [objective.explanation_text] [completion*100]%" else - parts += "Objective #[count]: [objective.explanation_text] Fail." - changelingwin = 0 + parts += "Objective #[count]: [objective.explanation_text]" count++ if(changelingwin) diff --git a/code/modules/antagonists/collector/collector.dm b/code/modules/antagonists/collector/collector.dm new file mode 100644 index 0000000000..0e7a3a52e7 --- /dev/null +++ b/code/modules/antagonists/collector/collector.dm @@ -0,0 +1,19 @@ +/datum/antagonist/collector + name = "Contraband Collector" + show_in_antagpanel = FALSE + show_name_in_check_antagonists = FALSE + blacklisted_quirks = list() // no blacklist, these guys are harmless + +/datum/antagonist/collector/proc/forge_objectives() + var/datum/objective/hoard/collector/O = new + O.owner = owner + O.find_target() + objectives += O + +/datum/antagonist/collector/on_gain() + forge_objectives() + . = ..() + +/datum/antagonist/collector/greet() + to_chat(owner, "You are a contraband collector!") + owner.announce_objectives() diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index ed912db044..4bab621674 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -425,10 +425,16 @@ parts += "The cultists' objectives were:" var/count = 1 for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - parts += "Objective #[count]: [objective.explanation_text] Success!" + if(objective.completable) + var/completion = objective.check_completion() + if(completion >= 1) + parts += "Objective #[count]: [objective.explanation_text] Success!" + else if(completion <= 0) + parts += "Objective #[count]: [objective.explanation_text] Fail." + else + parts += "Objective #[count]: [objective.explanation_text] [completion*100]%" else - parts += "Objective #[count]: [objective.explanation_text] Fail." + parts += "Objective #[count]: [objective.explanation_text]" count++ if(members.len) diff --git a/code/modules/antagonists/disease/disease_datum.dm b/code/modules/antagonists/disease/disease_datum.dm index 1aef9ceba6..3c7de589d9 100644 --- a/code/modules/antagonists/disease/disease_datum.dm +++ b/code/modules/antagonists/disease/disease_datum.dm @@ -44,11 +44,17 @@ var/objectives_text = "" var/count = 1 for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - objectives_text += "
    Objective #[count]: [objective.explanation_text] Success!" + if(objective.completable) + var/completion = objective.check_completion() + if(completion >= 1) + result += "Objective #[count]: [objective.explanation_text] Success!" + else if(completion <= 0) + result += "Objective #[count]: [objective.explanation_text] Fail." + win = FALSE + else + result += "Objective #[count]: [objective.explanation_text] [completion*100]%" else - objectives_text += "
    Objective #[count]: [objective.explanation_text] Fail." - win = FALSE + result += "Objective #[count]: [objective.explanation_text]" count++ result += objectives_text diff --git a/code/modules/antagonists/ninja/ninja.dm b/code/modules/antagonists/ninja/ninja.dm index 2a91f53ed4..943cb02696 100644 --- a/code/modules/antagonists/ninja/ninja.dm +++ b/code/modules/antagonists/ninja/ninja.dm @@ -63,10 +63,10 @@ possible_targets.Cut(index,index+1) if(is_bad_guy ^ helping_station) //kill (good-ninja + bad-guy or bad-ninja + good-guy) - var/datum/objective/assassinate/O = new /datum/objective/assassinate() + var/datum/objective/assassinate/once/O = new /datum/objective/assassinate() O.owner = owner O.target = M - O.explanation_text = "Slay \the [M.current.real_name], the [M.assigned_role]." + O.explanation_text = "Slay \the [M.current.real_name], the [M.assigned_role]. You may let [M.p_they()] live, if they come back from death." objectives += O else //protect var/datum/objective/protect/O = new /datum/objective/protect() @@ -74,23 +74,16 @@ O.target = M O.explanation_text = "Protect \the [M.current.real_name], the [M.assigned_role], from harm." objectives += O - if(4) //debrain/capture - if(!possible_targets.len) continue - var/selected = rand(1,possible_targets.len) - var/datum/mind/M = possible_targets[selected] - var/is_bad_guy = possible_targets[M] - possible_targets.Cut(selected,selected+1) - - if(is_bad_guy ^ helping_station) //debrain (good-ninja + bad-guy or bad-ninja + good-guy) - var/datum/objective/debrain/O = new /datum/objective/debrain() + if(4) //flavor + if(helping_station) + var/datum/objective/flavor/ninja_helping/O = new /datum/objective/flavor/ninja_helping O.owner = owner - O.target = M - O.explanation_text = "Steal the brain of [M.current.real_name]." + O.forge_objective() objectives += O - else //capture - var/datum/objective/capture/O = new /datum/objective/capture() + else + var/datum/objective/flavor/ninja_syndie/O = new /datum/objective/flavor/ninja_helping O.owner = owner - O.gen_amount_goal() + O.forge_objective() objectives += O else break diff --git a/code/modules/antagonists/survivalist/survivalist.dm b/code/modules/antagonists/survivalist/survivalist.dm index 4e0184b7b6..0b66e8cb23 100644 --- a/code/modules/antagonists/survivalist/survivalist.dm +++ b/code/modules/antagonists/survivalist/survivalist.dm @@ -2,6 +2,7 @@ name = "Survivalist" show_in_antagpanel = FALSE show_name_in_check_antagonists = TRUE + blacklisted_quirks = list(/datum/quirk/nonviolent) // mutes are allowed var/greet_message = "" /datum/antagonist/survivalist/proc/forge_objectives() @@ -19,20 +20,8 @@ owner.announce_objectives() /datum/antagonist/survivalist/guns - greet_message = "Your own safety matters above all else, and the only way to ensure your safety is to stockpile weapons! Grab as many guns as possible, by any means necessary. Kill anyone who gets in your way." - -/datum/antagonist/survivalist/guns/forge_objectives() - var/datum/objective/steal_five_of_type/summon_guns/guns = new - guns.owner = owner - objectives += guns - ..() + greet_message = "Your own safety matters above all else, and the only way to ensure your safety is to stockpile weapons! Grab as many guns as possible, and don't let anyone take them!" /datum/antagonist/survivalist/magic name = "Amateur Magician" - greet_message = "Grow your newfound talent! Grab as many magical artefacts as possible, by any means necessary. Kill anyone who gets in your way." - -/datum/antagonist/survivalist/magic/forge_objectives() - var/datum/objective/steal_five_of_type/summon_magic/magic = new - magic.owner = owner - objectives += magic - ..() \ No newline at end of file + greet_message = "This magic stuff is... so powerful. You want more. More! They want your power. They can't have it! Don't let them have it!" diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index 595ce5bb90..1c7ee48f71 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -77,19 +77,23 @@ var/is_hijacker = FALSE var/datum/game_mode/dynamic/mode var/is_dynamic = FALSE + var/hijack_prob = 0 if(istype(SSticker.mode,/datum/game_mode/dynamic)) mode = SSticker.mode is_dynamic = TRUE - if(mode.storyteller.flags & NO_ASSASSIN) - is_hijacker = FALSE + if(mode.threat >= CONFIG_GET(number/dynamic_hijack_cost)) + hijack_prob = CLAMP(mode.threat_level-50,0,20) 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(hijack_prob) && mode.threat_level > CONFIG_GET(number/dynamic_hijack_high_population_requirement)) else var/indice_pop = min(10,round(GLOB.joined_player_list.len/mode.pop_per_requirement)+1) - is_hijacker = (prob(10) && (mode.threat_level >= CONFIG_GET(number_list/dynamic_hijack_requirements)[indice_pop])) + is_hijacker = (prob(hijack_prob) && (mode.threat_level >= CONFIG_GET(number_list/dynamic_hijack_requirements)[indice_pop])) + if(mode.storyteller.flags & NO_ASSASSIN) + is_hijacker = FALSE else if (GLOB.joined_player_list.len >= 30) // Less murderboning on lowpop thanks + hijack_prob = 10 is_hijacker = prob(10) - var/martyr_chance = prob(20) + var/martyr_chance = prob(hijack_prob*2) var/objective_count = is_hijacker //Hijacking counts towards number of objectives if(!SSticker.mode.exchange_blue && SSticker.mode.traitors.len >= 8) //Set up an exchange if there are enough traitors if(!SSticker.mode.exchange_red) @@ -170,7 +174,7 @@ if(istype(SSticker.mode,/datum/game_mode/dynamic)) mode = SSticker.mode is_dynamic = TRUE - assassin_prob = mode.threat_level*(2/3) + assassin_prob = max(0,mode.threat_level-20) if(prob(assassin_prob)) if(is_dynamic) var/threat_spent = CONFIG_GET(number/dynamic_assassinate_cost) @@ -187,22 +191,37 @@ maroon_objective.owner = owner maroon_objective.find_target() add_objective(maroon_objective) - else + else if(prob(max(0,assassin_prob-20))) var/datum/objective/assassinate/kill_objective = new kill_objective.owner = owner kill_objective.find_target() add_objective(kill_objective) + else + var/datum/objective/assassinate/once/kill_objective = new + kill_objective.owner = owner + kill_objective.find_target() + add_objective(kill_objective) else if(prob(15) && !(locate(/datum/objective/download) in objectives) && !(owner.assigned_role in list("Research Director", "Scientist", "Roboticist"))) var/datum/objective/download/download_objective = new download_objective.owner = owner download_objective.gen_amount_goal() add_objective(download_objective) - else + else if(prob(40)) // cum. not counting download: 40%. var/datum/objective/steal/steal_objective = new steal_objective.owner = owner steal_objective.find_target() add_objective(steal_objective) + else if(prob(100/3)) // cum. not counting download: 20%. + var/datum/objective/sabotage/sabotage_objective = new + sabotage_objective.owner = owner + sabotage_objective.find_target() + add_objective(sabotage_objective) + else // cum. not counting download: 40% + var/datum/objective/flavor/traitor/flavor_objective = new + flavor_objective.owner = owner + flavor_objective.forge_objective() + add_objective(flavor_objective) /datum/antagonist/traitor/proc/forge_single_AI_objective() .=1 @@ -369,11 +388,17 @@ if(objectives.len)//If the traitor had no objectives, don't need to process this. var/count = 1 for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - objectives_text += "
    Objective #[count]: [objective.explanation_text] Success!" + if(objective.completable) + var/completion = objective.check_completion() + if(completion >= 1) + objectives_text += "
    Objective #[count]: [objective.explanation_text] Success!" + else if(completion <= 0) + objectives_text += "
    Objective #[count]: [objective.explanation_text] Fail." + traitorwin = FALSE + else + objectives_text += "
    Objective #[count]: [objective.explanation_text] [completion*100]%" else - objectives_text += "
    Objective #[count]: [objective.explanation_text] Fail." - traitorwin = FALSE + objectives_text += "
    Objective #[count]: [objective.explanation_text]" count++ if(uplink_true) diff --git a/code/modules/antagonists/valentines/valentine.dm b/code/modules/antagonists/valentines/valentine.dm index 19c08f3076..d3bcdf0c04 100644 --- a/code/modules/antagonists/valentines/valentine.dm +++ b/code/modules/antagonists/valentines/valentine.dm @@ -35,7 +35,7 @@ var/objectives_complete = TRUE if(objectives.len) for(var/datum/objective/objective in objectives) - if(!objective.check_completion()) + if(objective.completable && !objective.check_completion()) objectives_complete = FALSE break diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm index f75d32e781..5e4cc17541 100644 --- a/code/modules/antagonists/wizard/wizard.dm +++ b/code/modules/antagonists/wizard/wizard.dm @@ -61,9 +61,9 @@ owner.current.forceMove(pick(GLOB.wizardstart)) /datum/antagonist/wizard/proc/create_objectives() - var/datum/objective/new_objective = new("Cause as much creative mayhem as you can aboard the station! The more outlandish your methods of achieving this, the better! Make sure there's a decent amount of crew alive to tell of your tale.") - new_objective.completed = TRUE //So they can greentext without admin intervention. + var/datum/objective/flavor/wizard/new_objective = new new_objective.owner = owner + new_objective.forge_objective() objectives += new_objective if (!(locate(/datum/objective/escape) in objectives)) @@ -94,6 +94,7 @@ to_chat(owner, "You are the Space Wizard!") to_chat(owner, "The Space Wizards Federation has given you the following tasks:") owner.announce_objectives() + to_chat(owner, "These are merely guidelines! The federation are your masters, but you forge your own path!") to_chat(owner, "You will find a list of available spells in your spell book. Choose your magic arsenal carefully.") to_chat(owner, "The spellbook is bound to you, and others cannot use it.") to_chat(owner, "In your pockets you will find a teleport scroll. Use it as needed.") @@ -265,11 +266,17 @@ var/count = 1 var/wizardwin = 1 for(var/datum/objective/objective in objectives) - if(objective.check_completion()) - parts += "Objective #[count]: [objective.explanation_text] Success!" + if(objective.completable) + var/completion = objective.check_completion() + if(completion >= 1) + parts += "Objective #[count]: [objective.explanation_text] Success!" + else if(completion <= 0) + parts += "Objective #[count]: [objective.explanation_text] Fail." + wizardwin = FALSE + else + parts += "Objective #[count]: [objective.explanation_text] [completion*100]%" else - parts += "Objective #[count]: [objective.explanation_text] Fail." - wizardwin = 0 + parts += "Objective #[count]: [objective.explanation_text]" count++ if(wizardwin) diff --git a/code/modules/spells/spell_types/rightandwrong.dm b/code/modules/spells/spell_types/rightandwrong.dm index e5e856c630..ea131fb996 100644 --- a/code/modules/spells/spell_types/rightandwrong.dm +++ b/code/modules/spells/spell_types/rightandwrong.dm @@ -102,6 +102,12 @@ GLOBAL_VAR_INIT(summon_magic_triggered, FALSE) var/gun_type = pick(GLOB.summoned_guns) var/obj/item/gun/G = new gun_type(get_turf(H)) G.unlock() + var/datum/antagonist/survivalist/guns/our_antag_datum = H.mind.has_antag_datum(/datum/antagonist/survivalist/guns) + if(our_antag_datum) + var/datum/objective/hoard/O = new() + O.owner = H + O.set_target(G) + our_antag_datum.objectives += O playsound(get_turf(H),'sound/magic/summon_guns.ogg', 50, 1) var/in_hand = H.put_in_hands(G) // not always successful @@ -128,6 +134,13 @@ GLOBAL_VAR_INIT(summon_magic_triggered, FALSE) var/obj/item/M = new magic_type(get_turf(H)) playsound(get_turf(H),'sound/magic/summon_magic.ogg', 50, 1) + var/datum/antagonist/survivalist/magic/our_antag_datum = H.mind.has_antag_datum(/datum/antagonist/survivalist/magic) + if(istype(our_antag_datum)) + var/datum/objective/hoard/O = new() + O.owner = H + O.set_target(M) + our_antag_datum.objectives += O + var/in_hand = H.put_in_hands(M) to_chat(H, "\A [M] appears [in_hand ? "in your hand" : "at your feet"]!") diff --git a/config/dynamic_config.txt b/config/dynamic_config.txt index 2ddebdf37a..25bc763d0b 100644 --- a/config/dynamic_config.txt +++ b/config/dynamic_config.txt @@ -1,3 +1,13 @@ +## Dynamic storytellers weights: how likely each storyteller is to show up. This is adjusted by voting and recency. +STORYTELLER_WEIGHT CHAOTIC 2 +STORYTELLER_WEIGHT TEAMWORK 4 +STORYTELLER_WEIGHT CONVERSION 2 +STORYTELLER_WEIGHT RANDOM 6 +STORYTELLER_WEIGHT INTRIGUE 6 +STORYTELLER_WEIGHT STORY 6 +STORYTELLER_WEIGHT CALM 6 +STORYTELLER_WEIGHT EXTENDED 0 + ## Injection delays: how long (in minutes) will pass before a midround or latejoin antag is injected. DYNAMIC_MIDROUND_DELAY_MIN 5 DYNAMIC_MIDROUND_DELAY_MAX 15 @@ -92,6 +102,7 @@ DYNAMIC_WEIGHT RADIATION_STORM 1 DYNAMIC_WEIGHT LATEJOIN_TRAITOR 7 DYNAMIC_WEIGHT LATEJOIN_REVOLUTION 2 DYNAMIC_WEIGHT LATEJOIN_BLOODSUCKER 4 +DYNAMIC_WEIGHT LATEJOIN_COLLECTOR 5 ## Threat cost. This is decreased from the mode's threat when the rule is executed. DYNAMIC_COST TRAITOR 10 @@ -153,6 +164,7 @@ DYNAMIC_COST RADIATION_STORM 3 DYNAMIC_COST LATEJOIN_TRAITOR 5 DYNAMIC_COST LATEJOIN_REVOLUTION 20 DYNAMIC_COST LATEJOIN_BLOODSUCKER 10 +DYNAMIC_COST LATEJOIN_COLLECTOR 1 ## Rule will not be generated with threat levels below requirement at a pop value. Pop values are determined by dynamic's pop-per-requirement. ## By default it's 0-8, 9-17, 18-26, 27-35, 36-44, 45-53, 54-60, 61-69, 70-78, 79+. @@ -210,6 +222,7 @@ DYNAMIC_REQUIREMENTS RADIATION_STORM 5 5 5 5 5 5 5 5 5 5 DYNAMIC_REQUIREMENTS LATEJOIN_TRAITOR 40 30 20 15 15 15 15 15 15 15 DYNAMIC_REQUIREMENTS LATEJOIN_REVOLUTION 101 101 70 40 40 40 40 40 40 40 DYNAMIC_REQUIREMENTS LATEJOIN_BLOODSUCKER 40 30 20 15 15 15 15 15 15 15 +DYNAMIC_REQUIREMENTS LATEJOIN_COLLECTOR 10 10 10 10 10 10 10 10 10 10 ## An alternative, static requirement used instead when pop is over mode's high_pop_limit. DYNAMIC_HIGH_POPULATION_REQUIREMENT TRAITOR 50 @@ -267,6 +280,7 @@ DYNAMIC_HIGH_POPULATION_REQUIREMENT RADIATION_STORM 5 DYNAMIC_HIGH_POPULATION_REQUIREMENT LATEJOIN_TRAITOR 15 DYNAMIC_HIGH_POPULATION_REQUIREMENT LATEJOIN_REVOLUTION 50 DYNAMIC_HIGH_POPULATION_REQUIREMENT LATEJOIN_BLOODSUCKER 15 +DYNAMIC_HIGH_POPULATION_REQUIREMENT LATEJOIN_COLLECTOR 10 ## Dynamic traitor stuff diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm index 6ee9e6e824..9f3d8911ec 100644 --- a/interface/stylesheet.dm +++ b/interface/stylesheet.dm @@ -151,6 +151,7 @@ h1.alert, h2.alert {color: #000000;} .extremelybig {font-size: 5;} .greentext {color: #00FF00; font-size: 3;} .redtext {color: #FF0000; font-size: 3;} +.yellowtext {color: #FFCC00; font-size: 3;} .clown {color: #FF69Bf; font-size: 3; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;} .his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;} .spooky {color: #FF9100;} diff --git a/strings/flavor_objectives/ninja_helping.txt b/strings/flavor_objectives/ninja_helping.txt new file mode 100644 index 0000000000..1280939b5c --- /dev/null +++ b/strings/flavor_objectives/ninja_helping.txt @@ -0,0 +1,6 @@ +Nanotrasen want to make sure that their employees are on the up-and-up. Try to find any blackmail you can. +Increase productivity however you can. +You are a security ninja. Answer to the Head of Security, and follow space law. +You are a cargo ninja. Answer to the Quartermaster, and do what they say. +Nanotrasen want you to ensure maximum morale. Protect the members of the crew who are on break. +Ensure that all the paperwork is being done. \ No newline at end of file diff --git a/strings/flavor_objectives/ninja_syndie.txt b/strings/flavor_objectives/ninja_syndie.txt new file mode 100644 index 0000000000..4527c693f4 --- /dev/null +++ b/strings/flavor_objectives/ninja_syndie.txt @@ -0,0 +1,5 @@ +The Gorlex Marauders want you to teach the heads of staff a lesson they will never forget. +Waffle Co. wants you To cause as much humorous terrorism against Nanotrasen as possible! How? We don’t care as long as it’s entertaining! Be as creative and exciting as possible when carrying out your dirty deeds. Have fun! +The Tiger Cooperative want you to kill one of the station's beloved pets. Make a show of it, though you don't have to reveal yourself. +The Animal Rights Consortium needs you to save the innocent non-humanoid creatures aboard Citadel Station by any means necessary. Use your best judgement to decide whether an animal or xenobiological is abused, but if they are, ensure the abuser is punished. Avoid killing too many people if possible, and if you do harm any creatures, you will be terminated upon extraction. +Donk Co. wants ransom money, and you are going to get it. Your goal is to kidnap and crewmember you can get your hands on and hold them hostage until you get something of significant value. Try to work out the best deal you can. Remember that Higher Value Targets are generally going to get a better deal so try to prioritize heads of staff if possible. We do not approve of mindless killing of Nanotrasen employees, so don’t do it. \ No newline at end of file diff --git a/strings/flavor_objectives/traitor.txt b/strings/flavor_objectives/traitor.txt new file mode 100644 index 0000000000..6d54c8ed9d --- /dev/null +++ b/strings/flavor_objectives/traitor.txt @@ -0,0 +1,8 @@ +The Gorlex Marauders want you to teach the heads of staff a lesson they will never forget. +Show Nanotrasen the utility of a 40% oxygen atmosphere. +Waffle Co. wants you To cause as much humorous terrorism against Nanotrasen as possible! How? We don’t care as long as it’s entertaining! Be as creative and exciting as possible when carrying out your dirty deeds. Have fun! +Kill one of the station's beloved pets. Make a show of it, though you don't have to reveal yourself. +The Tiger Cooperative want you to get their illegal technology spread through the station. +The Animal Rights Consortium needs you to save the innocent non-humanoid creatures aboard Citadel Station by any means necessary. Use your best judgement to decide whether an animal or xenobiological is abused, but if they are, ensure the abuser is punished. Avoid killing too many people if possible, and if you do harm any creatures, you will be terminated upon extraction. +Donk Co. wants ransom money, and you are going to get it. Your goal is to kidnap and crewmember you can get your hands on and hold them hostage until you get something of significant value. Try to work out the best deal you can. Remember that Higher Value Targets are generally going to get a better deal so try to prioritize heads of staff if possible. We do not approve of mindless killing of Nanotrasen employees, so don’t do it. +The Gorlex Marauders want you to steal as many shoes as possible. Lay broken glass everywhere. \ No newline at end of file diff --git a/strings/flavor_objectives/traitor/assistant.txt b/strings/flavor_objectives/traitor/assistant.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/strings/flavor_objectives/traitor/engineering.txt b/strings/flavor_objectives/traitor/engineering.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/strings/flavor_objectives/traitor/medical.txt b/strings/flavor_objectives/traitor/medical.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/strings/flavor_objectives/traitor/science.txt b/strings/flavor_objectives/traitor/science.txt new file mode 100644 index 0000000000..6367e78da2 --- /dev/null +++ b/strings/flavor_objectives/traitor/science.txt @@ -0,0 +1 @@ +The Tiger Cooperative want you to slow down the process of research as much as possible. \ No newline at end of file diff --git a/strings/flavor_objectives/wizard.txt b/strings/flavor_objectives/wizard.txt new file mode 100644 index 0000000000..e65a6052bc --- /dev/null +++ b/strings/flavor_objectives/wizard.txt @@ -0,0 +1,4 @@ +Cause as much creative mayhem as you can aboard the station! The more outlandish your methods of achieving this, the better! Make sure there's a decent amount of crew alive to tell of your tale. +The Wizard Federation have sent you to take over the station by force. Kill the captain and take their place. The magocracy starts now. +You are the slipperiest wizard there ever was. Be loud, cause a ruckus, start a manhunt, and keep it going for as long as you can. +The crew must be shown the wonders of magic. Do some tricks for them. If they stop moving, that means they're in awe. \ No newline at end of file diff --git a/tgstation.dme b/tgstation.dme index d67af98920..28ae7eee99 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -582,6 +582,7 @@ #include "code\game\gamemodes\game_mode.dm" #include "code\game\gamemodes\objective.dm" #include "code\game\gamemodes\objective_items.dm" +#include "code\game\gamemodes\objective_sabotage.dm" #include "code\game\gamemodes\bloodsucker\bloodsucker.dm" #include "code\game\gamemodes\bloodsucker\hunter.dm" #include "code\game\gamemodes\brother\traitor_bro.dm" @@ -1391,6 +1392,7 @@ #include "code\modules\antagonists\clockcult\clock_structures\traps\brass_skewer.dm" #include "code\modules\antagonists\clockcult\clock_structures\traps\power_null.dm" #include "code\modules\antagonists\clockcult\clock_structures\traps\steam_vent.dm" +#include "code\modules\antagonists\collector\collector.dm" #include "code\modules\antagonists\cult\blood_magic.dm" #include "code\modules\antagonists\cult\cult.dm" #include "code\modules\antagonists\cult\cult_comms.dm"