From 1511e1afaee8687a51027f99b4779b074cba27ce Mon Sep 17 00:00:00 2001 From: Zwei <35403274+Gottfrei@users.noreply.github.com> Date: Fri, 17 Nov 2023 21:18:52 +0200 Subject: [PATCH] add: Antag Paradise Gamemode (#3578) --- code/__DEFINES/gamemode.dm | 6 + code/__DEFINES/role_preferences.dm | 2 + code/__HELPERS/lists.dm | 61 +++- code/_globalvars/game_modes.dm | 20 ++ .../configuration/entries/config.dm | 64 ++++ .../antag_paradise/antag_paradise.dm | 301 ++++++++++++++++++ code/game/gamemodes/objective.dm | 23 +- code/modules/admin/admin.dm | 2 + code/modules/admin/topic.dm | 86 +++++ .../antagonists/space_ninja/ninja_datum.dm | 120 +++++-- .../antagonists/traitor/datum_traitor.dm | 3 +- config/example/config.txt | 29 ++ paradise.dme | 1 + 13 files changed, 676 insertions(+), 42 deletions(-) create mode 100644 code/game/gamemodes/antag_paradise/antag_paradise.dm diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm index f2fd4f881b5..2f2f64db4f5 100644 --- a/code/__DEFINES/gamemode.dm +++ b/code/__DEFINES/gamemode.dm @@ -1,3 +1,9 @@ +//antag paradise gamemode type defines +#define ANTAG_SINGLE "antag_single" +#define ANTAG_DOUBLE "antag_double" +#define ANTAG_TRIPPLE "antag_tripple" +#define ANTAG_RANDOM "antag_random" + //objective defines #define TARGET_INVALID_IS_OWNER 1 #define TARGET_INVALID_NOT_HUMAN 2 diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index c58879d2614..95cdb0382a0 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -52,6 +52,8 @@ #define ROLE_SPACE_DRAGON "space dragon" #define ROLE_MALF_AI "Malfunctioning AI" +#define ROLE_NONE "nothing" // special define used as a marker + //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/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 81a0ba31cd5..4d45c7f4432 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -232,18 +232,61 @@ result = first ^ second return result -//Pretends to pick an element based on its weight but really just seems to pick a random element. -/proc/pickweight(list/L) +/** + * Picks a random element from a list based on a weighting system. + * All keys with zero or non integer weight will be considered as one + * For example, given the following list: + * A = 5, B = 3, C = 1, D = 0 + * A would have a 50% chance of being picked, + * B would have a 30% chance of being picked, + * C would have a 10% chance of being picked, + * and D would have a 10% chance of being picked. + * This proc not modify input list + */ +/proc/pickweight(list/list_to_pick) + var/total = 0 + for(var/item in list_to_pick) + var/weight = list_to_pick[item] + if(!weight) + weight = 1 + total += weight + + total = rand(1, total) + for(var/item in list_to_pick) + var/weight = list_to_pick[item] + if(!weight) + weight = 1 + total -= weight + if(total <= 0) + return item + + return null + +/** + * Picks a random element from a list based on a weighting system. + * All keys with zero or non integer weight will be considered as zero + * For example, given the following list: + * A = 6, B = 3, C = 1, D = 0 + * A would have a 60% chance of being picked, + * B would have a 30% chance of being picked, + * C would have a 10% chance of being picked, + * and D would have a 0% chance of being picked. + * This proc not modify input list + */ +/proc/pick_weight_classic(list/list_to_pick) var/total = 0 - var/item - for(item in L) - if(!L[item]) - L[item] = 1 - total += L[item] + for(var/item in list_to_pick) + var/weight = list_to_pick[item] + if(!weight) + continue + total += weight total = rand(1, total) - for(item in L) - total -=L [item] + for(var/item in list_to_pick) + var/weight = list_to_pick[item] + if(!weight) + continue + total -= weight if(total <= 0) return item diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm index e6ffa0a7be9..a336ac4a29e 100644 --- a/code/_globalvars/game_modes.dm +++ b/code/_globalvars/game_modes.dm @@ -17,3 +17,23 @@ GLOBAL_VAR(custom_event_admin_msg) GLOBAL_VAR_INIT(morphs_announced, FALSE) GLOBAL_VAR_INIT(disable_robotics_consoles, FALSE) + +/// Chance to roll double antag for traitors in ANTAG-PARADISE gamemode. +GLOBAL_VAR(antag_paradise_double_antag_chance) + +/// Weights for all minor antags in ANTAG-PARADISE gamemode. Highter the weight higher the chance for antag to roll. +GLOBAL_LIST_INIT(antag_paradise_weights, list( + ROLE_TRAITOR = 0, + ROLE_THIEF = 0, + ROLE_VAMPIRE = 0, + ROLE_CHANGELING = 0, +)) + +/// Weights for all special antags in ANTAG-PARADISE gamemode. +GLOBAL_LIST_INIT(antag_paradise_special_weights, list( + ROLE_TRAITOR = 0, // hijacker actually + ROLE_MALF_AI = 0, + ROLE_NINJA = 0, + ROLE_NONE = 0, // to skip all roles entirely +)) + diff --git a/code/controllers/configuration/entries/config.dm b/code/controllers/configuration/entries/config.dm index 21a06577290..22426ae51b4 100644 --- a/code/controllers/configuration/entries/config.dm +++ b/code/controllers/configuration/entries/config.dm @@ -412,6 +412,70 @@ /datum/config_entry/number/expected_round_length default = 2 HOURS + +/datum/config_entry/number/antag_paradise_double_antag_chance + + +/datum/config_entry/number/antag_paradise_double_antag_chance/ValidateAndSet(str_val) + . = ..() + if(.) + GLOB.antag_paradise_double_antag_chance = config_entry_value + + +/datum/config_entry/keyed_list/antag_paradise_weights + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + + +/datum/config_entry/keyed_list/antag_paradise_weights/ValidateAndSet(str_val) + . = ..() + if(.) + for(var/role in config_entry_value) + GLOB.antag_paradise_weights[role] = config_entry_value[role] + + +/datum/config_entry/keyed_list/antag_paradise_special_weights + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + default = list( + "hijacker" = 10, + "malfai" = 10, + "ninja" = 10, + "nothing" = 30, + ) + + +/datum/config_entry/keyed_list/antag_paradise_special_weights/ValidateAndSet(str_val) + . = ..() + if(.) + GLOB.antag_paradise_special_weights[ROLE_TRAITOR] = config_entry_value["hijacker"] + GLOB.antag_paradise_special_weights[ROLE_MALF_AI] = config_entry_value["malfai"] + GLOB.antag_paradise_special_weights[ROLE_NINJA] = config_entry_value["ninja"] + GLOB.antag_paradise_special_weights[ROLE_NONE] = config_entry_value["nothing"] + + +/datum/config_entry/keyed_list/antag_paradise_mode_subtypes + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + default = list( + ANTAG_SINGLE = 10, + ANTAG_DOUBLE = 10, + ANTAG_TRIPPLE = 10, + ANTAG_RANDOM = 10, + ) + + +/datum/config_entry/keyed_list/antag_paradise_subtype_weights + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + default = list( + ANTAG_SINGLE = 6, + ANTAG_DOUBLE = 4, + ANTAG_TRIPPLE = 2, + ANTAG_RANDOM = 10, + ) + + //Made that way because compatibility reasons. /datum/config_entry/keyed_list/event_delay_lower default = list("ev_level_mundane" = 10, "ev_level_moderate" = 30, "ev_level_major" = 50) //minutes diff --git a/code/game/gamemodes/antag_paradise/antag_paradise.dm b/code/game/gamemodes/antag_paradise/antag_paradise.dm new file mode 100644 index 00000000000..a8ead89287e --- /dev/null +++ b/code/game/gamemodes/antag_paradise/antag_paradise.dm @@ -0,0 +1,301 @@ +/** + * This is a game mode which has a chance to spawn any minor antagonist. + */ +/datum/game_mode/antag_paradise + name = "Antag Paradise" + config_tag = "antag-paradise" + protected_jobs = list("Security Officer", "Security Cadet", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Nanotrasen Navy Field Officer", "Special Operations Officer", "Supreme Commander", "Syndicate Officer") + restricted_jobs = list("Cyborg", "AI") + required_players = 10 + required_enemies = 1 + var/list/protected_jobs_AI = list("Civilian","Chief Engineer","Station Engineer","Trainee Engineer","Life Support Specialist","Mechanic","Chief Medical Officer","Medical Doctor","Intern","Coroner","Chemist","Geneticist","Virologist","Psychiatrist","Paramedic","Research Director","Scientist","Student Scientist","Roboticist","Head of Personnel","Chaplain","Bartender","Chef","Botanist","Quartermaster","Cargo Technician","Shaft Miner","Clown","Mime","Janitor","Librarian","Barber","Explorer") // Basically all jobs, except AI. + var/secondary_protected_species = list("Machine") + var/vampire_restricted_jobs = list("Chaplain") + var/thief_prefered_species = list("Vox") + var/thief_prefered_species_mod = 4 + var/list/datum/mind/pre_traitors = list() + var/list/datum/mind/pre_thieves = list() + var/list/datum/mind/pre_changelings = list() + var/list/datum/mind/pre_vampires = list() + var/list/datum/mind/traitor_vampires = list() + var/list/datum/mind/traitor_changelings = list() + var/list/antag_required_players = list( + ROLE_TRAITOR = 10, + ROLE_THIEF = 10, + ROLE_VAMPIRE = 15, + ROLE_CHANGELING = 15, + ) + var/list/special_antag_required_players = list( + ROLE_TRAITOR = 30, // hijacker + ROLE_MALF_AI = 30, + ROLE_NINJA = 30, + ) + var/list/antag_amount = list( + ROLE_TRAITOR = 0, + ROLE_THIEF = 0, + ROLE_VAMPIRE = 0, + ROLE_CHANGELING = 0, + ) + + /// Weight ratio for antags. Higher the weight higher the chance to roll this antag. This values will be modified by config or by admins. + var/list/antag_weights = list( + ROLE_TRAITOR = 0, + ROLE_THIEF = 0, + ROLE_VAMPIRE = 0, + ROLE_CHANGELING = 0, + ) + + /// Default chance for traitor to get another antag role, available in prefs. + var/chance_double_antag = 10 + + /// Chosen antag type. + var/special_antag_type + /// Chosen special antag if any. + var/datum/mind/special_antag + + +/datum/game_mode/antag_paradise/announce() + to_chat(world, "The current game mode is - Antag Paradise") + to_chat(world, "Traitors, thieves, vampires and changelings, oh my! Stay safe as these forces work to bring down the station.") + + +/datum/game_mode/antag_paradise/can_start() + if(!..()) + return FALSE + + // we need to setup ninja before all the jobs assignment + // but we can start even if ninja wasn't rolled + . = TRUE + + calculate_antags() + + if(special_antag_type != ROLE_NINJA) + return + + if(!length(GLOB.ninjastart)) + log_and_message_admins("No positions are found to spawn space ninja antag. Report this to coders.") + special_antag_type = null // its a shame :( + return + + special_antag = safepick(get_players_for_role(ROLE_NINJA)) + if(!special_antag) + return + + special_antag.assigned_role = ROLE_NINJA // so they aren't chosen for other jobs. + special_antag.special_role = SPECIAL_ROLE_SPACE_NINJA + special_antag.offstation_role = TRUE // ninja can't be targeted as a victim for some pity traitors + special_antag.set_original_mob(special_antag.current) + + +/datum/game_mode/antag_paradise/pre_setup() + . = FALSE + + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + + switch(special_antag_type) + if(ROLE_TRAITOR) // hijacker + special_antag = safepick(get_players_for_role(ROLE_TRAITOR)) + if(special_antag) + special_antag.restricted_roles = restricted_jobs + else + special_antag_type = null + + if(ROLE_MALF_AI) + special_antag = safepick(get_players_for_role(ROLE_MALF_AI)) + if(special_antag) + special_antag.restricted_roles = (restricted_jobs|protected_jobs_AI) + special_antag.restricted_roles -= "AI" + SSjobs.new_malf = special_antag.current + else + special_antag_type = null + + if(ROLE_NINJA) + special_antag.current.loc = pick(GLOB.ninjastart) + + if(antag_amount[ROLE_VAMPIRE]) + var/list/datum/mind/possible_vampires = get_players_for_role(ROLE_VAMPIRE) + while(length(possible_vampires) && length(pre_vampires) <= antag_amount[ROLE_VAMPIRE]) + var/datum/mind/vampire = pick_n_take(possible_vampires) + if(vampire.current.client.prefs.species in secondary_protected_species) + continue + if(vampire == special_antag) + continue + pre_vampires += vampire + vampire.special_role = SPECIAL_ROLE_VAMPIRE + vampire.restricted_roles = (restricted_jobs|vampire_restricted_jobs) + + if(antag_amount[ROLE_CHANGELING]) + var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) + while(length(possible_changelings) && length(pre_changelings) <= antag_amount[ROLE_CHANGELING]) + var/datum/mind/changeling = pick_n_take(possible_changelings) + if(changeling.current.client.prefs.species in secondary_protected_species) + continue + if(changeling.special_role || changeling == special_antag) + continue + pre_changelings += changeling + changeling.special_role = SPECIAL_ROLE_CHANGELING + changeling.restricted_roles = restricted_jobs + + if(antag_amount[ROLE_TRAITOR]) + var/list/datum/mind/possible_traitors = get_players_for_role(ROLE_TRAITOR) + while(length(possible_traitors) && length(pre_traitors) <= antag_amount[ROLE_TRAITOR]) + var/datum/mind/traitor = pick_n_take(possible_traitors) + if(traitor.special_role || traitor == special_antag) + continue + pre_traitors += traitor + traitor.special_role = SPECIAL_ROLE_TRAITOR + traitor.restricted_roles = restricted_jobs + + if(antag_amount[ROLE_THIEF]) + var/list/datum/mind/possible_thieves = get_players_for_role(ROLE_THIEF) + if(length(possible_thieves)) + var/list/thief_list = list() + for(var/datum/mind/thief in possible_thieves) + thief_list += thief + if(thief.current.client.prefs.species in thief_prefered_species) + for(var/i in 1 to thief_prefered_species_mod) + thief_list += thief + + while(length(thief_list) && length(pre_thieves) <= antag_amount[ROLE_THIEF]) + var/datum/mind/thief = pick_n_take(thief_list) + listclearduplicates(thief, thief_list) + if(thief.special_role || thief == special_antag) + continue + pre_thieves += thief + thief.special_role = SPECIAL_ROLE_THIEF + thief.restricted_roles = restricted_jobs + + if(!(length(pre_vampires) + length(pre_changelings) + length(pre_traitors) + length(pre_thieves)) && !special_antag) + return + + . = TRUE + + if(!chance_double_antag || !length(pre_traitors)) + return + + var/list/pre_traitors_copy = pre_traitors.Copy() + while(length(pre_traitors_copy)) + if(!prob(chance_double_antag)) + continue + + var/datum/mind/traitor = pick_n_take(pre_traitors_copy) + var/list/available_roles = list(ROLE_VAMPIRE, ROLE_CHANGELING) + while(length(available_roles)) + var/second_role = pick_n_take(available_roles) + + if(second_role == ROLE_VAMPIRE && \ + !jobban_isbanned(traitor.current, get_roletext(second_role)) && \ + player_old_enough_antag(traitor.current.client, second_role) && \ + (second_role in traitor.current.client.prefs.be_special) && \ + !(traitor.current.client.prefs.species in secondary_protected_species)) + + traitor_vampires += traitor + traitor.restricted_roles |= vampire_restricted_jobs + break + + if(second_role == ROLE_CHANGELING && \ + !jobban_isbanned(traitor.current, get_roletext(second_role)) && \ + player_old_enough_antag(traitor.current.client, second_role) && \ + (second_role in traitor.current.client.prefs.be_special) && \ + !(traitor.current.client.prefs.species in secondary_protected_species)) + + traitor_changelings += traitor + break + + +/datum/game_mode/antag_paradise/proc/calculate_antags() + var/players = num_players() + var/scale = CONFIG_GET(number/traitor_scaling) ? CONFIG_GET(number/traitor_scaling) : 10 + var/antags_amount = 1 + round(players / scale) + + chance_double_antag = isnull(GLOB.antag_paradise_double_antag_chance) ? chance_double_antag : GLOB.antag_paradise_double_antag_chance + + var/list/available_special_antags = list() + for(var/antag in special_antag_required_players) + if(players < special_antag_required_players[antag]) + continue + available_special_antags += antag + + special_antag_type = pick_weight_classic(GLOB.antag_paradise_special_weights) + if(special_antag_type in available_special_antags) + antags_amount-- + else + special_antag_type = null + + var/list/available_antags = list() + for(var/antag in antag_required_players) + if(players < antag_required_players[antag]) + continue + available_antags += antag + + var/modifed_weights = FALSE + for(var/antag in antag_weights) + if(!(antag in available_antags)) + continue + antag_weights[antag] = GLOB.antag_paradise_weights[antag] + if(GLOB.antag_paradise_weights[antag] > 0) + modifed_weights = TRUE + + if(!modifed_weights) + var/mode_type = pick_weight_classic(CONFIG_GET(keyed_list/antag_paradise_mode_subtypes)) + var/list/subtype_weights = CONFIG_GET(keyed_list/antag_paradise_subtype_weights) + if(mode_type == ANTAG_RANDOM) + for(var/antag in antag_weights) + if(!(antag in available_antags)) + continue + var/random = rand(-subtype_weights[ANTAG_RANDOM], subtype_weights[ANTAG_RANDOM]) + antag_weights[antag] = random < 0 ? 0 : random + else + while(length(available_antags)) + antag_weights[pick_n_take(available_antags)] = subtype_weights[ANTAG_SINGLE] + if(!length(available_antags) || mode_type == ANTAG_SINGLE) + break + antag_weights[pick_n_take(available_antags)] = subtype_weights[ANTAG_DOUBLE] + if(!length(available_antags) || mode_type == ANTAG_DOUBLE) + break + antag_weights[pick_n_take(available_antags)] = subtype_weights[ANTAG_TRIPPLE] + break + + for(var/i in 1 to antags_amount) + antag_amount[pick_weight_classic(antag_weights)]++ + + +/datum/game_mode/antag_paradise/post_setup() + switch(special_antag_type) + if(ROLE_TRAITOR) // hijacker + var/datum/antagonist/traitor/hijacker_datum = new + hijacker_datum.is_hijacker = TRUE + addtimer(CALLBACK(special_antag, TYPE_PROC_REF(/datum/mind, add_antag_datum), hijacker_datum), rand(1 SECONDS, 10 SECONDS)) + + if(ROLE_MALF_AI) + if(isAI(special_antag.current)) + addtimer(CALLBACK(special_antag, TYPE_PROC_REF(/datum/mind, add_antag_datum), /datum/antagonist/malf_ai), rand(1 SECONDS, 10 SECONDS)) + else + log_and_message_admins("[special_antag] was not assigned for AI role. Report this to coders.") + + if(ROLE_NINJA) + var/datum/antagonist/ninja/ninja_datum = new + ninja_datum.antag_paradise_mode_chosen = TRUE + special_antag.add_antag_datum(ninja_datum) + + addtimer(CALLBACK(src, PROC_REF(initiate_minor_antags)), rand(1 SECONDS, 10 SECONDS)) + ..() + + +/datum/game_mode/antag_paradise/proc/initiate_minor_antags() + for(var/datum/mind/vampire in pre_vampires) + vampire.add_antag_datum(/datum/antagonist/vampire) + for(var/datum/mind/changeling in pre_changelings) + changeling.add_antag_datum(/datum/antagonist/changeling) + for(var/datum/mind/traitor in pre_traitors) + traitor.add_antag_datum(/datum/antagonist/traitor) + for(var/datum/mind/thief in pre_thieves) + thief.add_antag_datum(/datum/antagonist/thief) + + // traitor double antags + for(var/datum/mind/vampire in traitor_vampires) + vampire.add_antag_datum(/datum/antagonist/vampire) + for(var/datum/mind/changeling in traitor_changelings) + changeling.add_antag_datum(/datum/antagonist/changeling) + diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 721b9354a3c..023f0e5d951 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -140,14 +140,16 @@ GLOBAL_LIST_EMPTY(admin_objective_list) if(!check_cryo) return - var/list/owners = get_owners() - for(var/datum/mind/user in owners) - to_chat(owner.current, "
You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!") - SEND_SOUND(owner.current, 'sound/ambience/alarm4.ogg') - + alarm_changes() SSticker.mode.victims.Remove(target) target = null - INVOKE_ASYNC(src, PROC_REF(post_target_cryo), owners) + INVOKE_ASYNC(src, PROC_REF(post_target_cryo), get_owners()) + + +/datum/objective/proc/alarm_changes() + for(var/datum/mind/user in get_owners()) + to_chat(user.current, span_userdanger("
You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")) + SEND_SOUND(user.current, 'sound/ambience/alarm4.ogg') /** @@ -269,15 +271,16 @@ GLOBAL_LIST_EMPTY(admin_objective_list) /datum/objective/maroon/find_target(list/target_blacklist) ..() + update_explanation() + return target + + +/datum/objective/maroon/proc/update_explanation() if(target?.current) explanation_text = "Prevent from escaping alive or free [target.current.real_name], the [target.assigned_role]." - if(!(target in SSticker.mode.victims)) - SSticker.mode.victims.Add(target) else explanation_text = "Free Objective" - return target - /datum/objective/maroon/check_completion() if(target && target.current) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index ac2eefafc59..1c4baddf47f 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -351,6 +351,8 @@ GLOBAL_VAR_INIT(nologevent, 0) "} if(GLOB.master_mode == "secret") dat += "(Force Secret Mode)
" + if(GLOB.master_mode == "antag-paradise" || GLOB.secret_force_mode == "antag-paradise") + dat += "Change Antag Weights
" dat += {"
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 84855e6a4fa..378865bcb6a 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1239,6 +1239,92 @@ Game() // updates the main game menu .(href, list("f_secret"=1)) + else if(href_list["change_weights"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "antag-paradise" && GLOB.secret_force_mode != "antag-paradise") + return alert(usr, "The game mode has to be Antag Paradise!", null, null, null, null) + + var/dat = {"What antag weights you want to change. Higher the weight higher the chance for antag to roll. Leave everything at zero if you want total randomness.
"} + dat += {""} + for(var/antag in GLOB.antag_paradise_weights) + dat += {""} + + dat += {"
AntagWeight
[capitalize(antag)]\[[GLOB.antag_paradise_weights[antag]]\]

Edit the chances to spawn special antags. Only one antag from below will be chosen. Rolling NOTHING means no special antag at all.
"} + dat += {""} + for(var/antag in GLOB.antag_paradise_special_weights) + var/flavour = antag + if(antag == ROLE_TRAITOR) + flavour = "hijacker" + dat += {""} + + dat += {"
AntagWeight
[capitalize(flavour)]\[[GLOB.antag_paradise_special_weights[antag]]\]

Edit the chance to roll double antag for Traitor role. Leave it as it is if you want default 10% chance.
"} + dat += {"
Chance = [isnull(GLOB.antag_paradise_double_antag_chance) ? "DEFAULT (10%)" : "[GLOB.antag_paradise_double_antag_chance]%"]
"} + + dat += {"

Reset everything to default.
"} + + usr << browse(dat, "window=change_weights") + + else if(href_list["change_weights2"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "antag-paradise" && GLOB.secret_force_mode != "antag-paradise") + return alert(usr, "The game mode has to be Antag Paradise!", null, null, null, null) + + var/command = href_list["change_weights2"] + if(command == "reset") + var/list/config_weights = CONFIG_GET(keyed_list/antag_paradise_weights) + if(islist(config_weights) && length(config_weights)) + for(var/antag in GLOB.antag_paradise_weights) + GLOB.antag_paradise_weights[antag] = isnull(config_weights[antag]) ? 0 : config_weights[antag] + else + for(var/antag in GLOB.antag_paradise_weights) + GLOB.antag_paradise_weights[antag] = 0 + + var/list/config_weights_special = CONFIG_GET(keyed_list/antag_paradise_special_weights) + GLOB.antag_paradise_special_weights[ROLE_TRAITOR] = config_weights_special["hijacker"] + GLOB.antag_paradise_special_weights[ROLE_MALF_AI] = config_weights_special["malfai"] + GLOB.antag_paradise_special_weights[ROLE_NINJA] = config_weights_special["ninja"] + GLOB.antag_paradise_special_weights[ROLE_NONE] = config_weights_special["nothing"] + + var/double_antag_chance = CONFIG_GET(number/antag_paradise_double_antag_chance) + GLOB.antag_paradise_double_antag_chance = double_antag_chance ? double_antag_chance : null + log_and_message_admins(span_notice("resets everything to default in Antag Paradise gamemode.")) + + else if(command == "chance") + var/choice = input(usr, "Adjust the chance for [capitalize(ROLE_TRAITOR)] antag to roll additional role on top", "Double Antag Adjustment", 0) as null|num + if(isnull(choice)) + return + choice = round(clamp(choice, 0, 100)) + GLOB.antag_paradise_double_antag_chance = choice + log_and_message_admins(span_notice("set the [choice]% chance to roll double antag for [capitalize(ROLE_TRAITOR)] antagonist in Antag Paradise gamemode.")) + + else if(findtext(command, "weights_normal_")) + var/antag = replacetext(command, "weights_normal_", "") + var/choice = input(usr, "Adjust the weight for [capitalize(antag)]", "Antag Weight Adjustment", 0) as null|num + if(isnull(choice)) + return + choice = round(clamp(choice, 0, 100)) + GLOB.antag_paradise_weights[antag] = choice + log_and_message_admins(span_notice("set the weight for [capitalize(antag)] as antagonist to [choice] in Antag Paradise gamemode.")) + + else if(findtext(command, "weights_special_")) + var/antag = replacetext(command, "weights_special_", "") + var/flavour = antag + if(antag == ROLE_TRAITOR) + flavour = "hijacker" + var/choice = input(usr, "Adjust the weight for [capitalize(flavour)]", "Antag Weight Adjustment", 0) as null|num + if(isnull(choice)) + return + choice = round(clamp(choice, 0, 100)) + GLOB.antag_paradise_special_weights[antag] = choice + log_and_message_admins(span_notice("set the weight for [capitalize(flavour)] as special antagonist to [choice] in Antag Paradise gamemode.")) + .(href, list("change_weights"=1)) + else if(href_list["monkeyone"]) if(!check_rights(R_SPAWN)) return diff --git a/code/modules/antagonists/space_ninja/ninja_datum.dm b/code/modules/antagonists/space_ninja/ninja_datum.dm index a4f8f56f9ff..48a2b21afb5 100644 --- a/code/modules/antagonists/space_ninja/ninja_datum.dm +++ b/code/modules/antagonists/space_ninja/ninja_datum.dm @@ -22,6 +22,8 @@ var/ninja_type = NINJA_TYPE_GENERIC /// Minds thats will be minor antags soon. var/list/pre_antags = list() + /// Special rules for antag if it was was created during antag paradise gamemode. + var/antag_paradise_mode_chosen = FALSE /// Quick access links. var/mob/living/carbon/human/human_ninja @@ -42,11 +44,12 @@ add_owner_to_gamemode() apply_innate_effects() - if(generate_antags) + if(generate_antags || antag_paradise_mode_chosen) ninja_type = pick(NINJA_TYPE_PROTECTOR, NINJA_TYPE_HACKER, NINJA_TYPE_KILLER) - pick_antags() + if(generate_antags) + pick_antags() - if(give_objectives) + if(give_objectives && !antag_paradise_mode_chosen) give_objectives() finalize_antag() @@ -128,9 +131,20 @@ if(give_equip) equip_ninja() - if(generate_antags) + if(generate_antags && !antag_paradise_mode_chosen) generate_antags() + if(antag_paradise_mode_chosen) + // to ensure all antags were properly generated + addtimer(CALLBACK(src, PROC_REF(finalize_antag_paradise_mode)), 15 SECONDS) + + +/datum/antagonist/ninja/proc/finalize_antag_paradise_mode() + give_objectives() + announce_objectives() + SEND_SOUND(owner.current, 'sound/ambience/alarm4.ogg') + basic_ninja_needs_check() + /datum/antagonist/ninja/proc/name_ninja() var/ninja_name_first = pick(GLOB.ninja_titles) @@ -380,7 +394,7 @@ for(var/i in 1 to objective_amount) traitor_datum.forge_single_human_objective() - var/all_objectives = traitor.get_all_objectives() + var/list/all_objectives = traitor.get_all_objectives() var/martyr_compatibility = TRUE for(var/datum/objective/objective in all_objectives) if(!objective.martyr_compatible) @@ -493,15 +507,7 @@ /datum/antagonist/ninja/proc/forge_protector_ninja_objectives() - // ninja protect. if traitors have been generated they will all hunt for our target. - var/datum/objective/protect/ninja/protect_objective = new - protect_objective.killers = pre_antags // chosen antags will be blacklisted - protect_objective.owner = owner - protect_objective.find_target(protect_objective.existing_targets_blacklist()) - objectives += protect_objective - - if(!protect_objective.target) - qdel(protect_objective) + try_protect_objective() if(prob(50)) //Cyborg Hijack: Flag set to complete in the DrainAct in ninjaDrainAct.dm @@ -525,12 +531,59 @@ add_objective(/datum/objective/survive) +/** + * Ninja protect. If traitors have been generated they will all hunt for our target. + */ +/datum/antagonist/ninja/proc/try_protect_objective() + + if(!antag_paradise_mode_chosen) + var/datum/objective/protect/ninja/protect_objective = new + protect_objective.killers = pre_antags // chosen antags will be blacklisted + protect_objective.owner = owner + protect_objective.find_target(protect_objective.existing_targets_blacklist()) + objectives += protect_objective + + if(!protect_objective.target) + qdel(protect_objective) + + return + + // this part will only proceed in antag paradise gamemode, long after antags have been generated + var/list/all_traitors = (SSticker.mode.traitors|SSticker.mode.vampires|SSticker.mode.changelings) + if(!length(all_traitors)) + return + + var/list/maroon_objectives = list() + var/list/killers = list() + for(var/datum/mind/traitor in all_traitors) + var/datum/objective/maroon/maroon_objective = locate() in traitor.get_all_objectives() + if(maroon_objective) // only one maroon objective will be modified + maroon_objectives |= maroon_objective + killers |= traitor + + if(!length(maroon_objectives)) + return + + var/datum/objective/protect/ninja/protect_objective = new + protect_objective.killers = killers // antags with maroon objectives will be blacklisted + protect_objective.owner = owner + protect_objective.find_target(protect_objective.existing_targets_blacklist()) + objectives += protect_objective + + if(!protect_objective.target) + qdel(protect_objective) + return + + for(var/datum/objective/maroon/maroon_objective in maroon_objectives) + maroon_objective.target = protect_objective.target // swapping target + maroon_objective.update_explanation() + maroon_objective.alarm_changes() + maroon_objective.owner.announce_objectives() + + /datum/antagonist/ninja/proc/forge_hacker_ninja_objectives() - // vampire blood collecting - var/datum/objective/collect_blood/blood_objective = add_objective(/datum/objective/collect_blood) - if(length(pre_antags) < blood_objective.samples_to_win) // no objective if there are fewer antagonists than needed - qdel(blood_objective) + try_blood_collect_objective() if(prob(75)) //Cyborg Hijack: Flag set to complete in the DrainAct in ninjaDrainAct.dm @@ -564,12 +617,22 @@ add_objective(/datum/objective/survive) +/** + * Vampire blood collecting objective. + */ +/datum/antagonist/ninja/proc/try_blood_collect_objective() + + // if its antag paradise gamemode vampires will generate later + var/vampires_amount = antag_paradise_mode_chosen ? length(SSticker.mode.vampires) : length(pre_antags) + + var/datum/objective/collect_blood/blood_objective = add_objective(/datum/objective/collect_blood) + if(length(vampires_amount) < blood_objective.samples_to_win) // no objective if there are fewer antagonists than needed + qdel(blood_objective) + + /datum/antagonist/ninja/proc/forge_killer_ninja_objectives() - // changelings massacre if they were generated - if(length(pre_antags)) - var/datum/objective/vermit_hunt/hunt_changelings = add_objective(/datum/objective/vermit_hunt) - hunt_changelings.update_objective(round(length(pre_antags) / 2)) + try_vermit_hunt_objective() if(prob(50)) //Cyborg Hijack: Flag set to complete in the DrainAct in ninjaDrainAct.dm @@ -591,6 +654,19 @@ add_objective(/datum/objective/survive) +/** + * Changelings massacre objective. + */ +/datum/antagonist/ninja/proc/try_vermit_hunt_objective() + + // if its antag paradise gamemode changelingss will generate later + var/changelings_amount = antag_paradise_mode_chosen ? length(SSticker.mode.changelings) : length(pre_antags) + + if(changelings_amount > 1) // we will not hunt if only one ling is available + var/datum/objective/vermit_hunt/hunt_changelings = add_objective(/datum/objective/vermit_hunt) + hunt_changelings.update_objective(round(changelings_amount / 2)) + + /** * Takes any datum `source` and checks it for ninja datum. */ diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index 705a99c4379..85d8b7984a2 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -16,6 +16,8 @@ var/give_uplink = TRUE /// Whether the traitor can specialize into a contractor. var/is_contractor = FALSE + /// Whether the traitor will receive only hijack objective. + var/is_hijacker = FALSE /// The associated traitor's uplink. Only present if `give_uplink` is set to `TRUE`. var/obj/item/uplink/hidden/hidden_uplink = null @@ -79,7 +81,6 @@ /datum/antagonist/traitor/give_objectives() - var/is_hijacker = prob(10) var/objective_count = is_hijacker //Hijacking counts towards number of objectives if(!SSticker.mode.exchange_blue && SSticker.mode.traitors.len >= EXCHANGE_OBJECTIVE_TRAITORS_REQUIRED) //Set up an exchange if there are enough traitors if(!SSticker.mode.exchange_red) diff --git a/config/example/config.txt b/config/example/config.txt index 10d74004ef4..882a80b78d8 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -108,6 +108,7 @@ AUTO_DESPAWN_AFK 0 ## ## default probablity is 1, increase to make that mode more likely to be picked ## set to 0 to disable that mode +PROBABILITY ANTAG-PARADISE 3 PROBABILITY EXTEND-A-TRAITORMONGOUS 3 PROBABILITY TRAITOR 3 PROBABILITY TRAITORCHAN 3 @@ -137,6 +138,7 @@ PROBABILITY ABDUCTION 0 PROBABILITY DEVIL 0 PROBABILITY DEVILAGENTS 0 +MINPLAYERS ANTAG-PARADISE 10 MINPLAYERS EXTEND-A-TRAITORMONGOUS 0 MINPLAYERS TRAITOR 0 MINPLAYERS TRAITORCHAN 10 @@ -166,6 +168,33 @@ MINPLAYERS ABDUCTION 15 MINPLAYERS DEVIL 2 MINPLAYERS DEVILAGENTS 25 +## Weights for all minor antags in ANTAG-PARADISE gamemode. Highter the weight higher the chance for antag to roll. Leave it commented if you prefer total randomness or if you are using mode subtypes below. +#ANTAG_PARADISE_WEIGHT TRAITOR 10 +#ANTAG_PARADISE_WEIGHT THIEF 10 +#ANTAG_PARADISE_WEIGHT VAMPIRE 10 +#ANTAG_PARADISE_WEIGHT CHANGELING 10 + +## Chances for ANTAG-PARADISE gamemode subtypes. This will NOT work if you are modifying antag weights directly in config above. +#ANTAG_PARADISE_MODE_SUBTYPES ANTAG_SINGLE 10 +#ANTAG_PARADISE_MODE_SUBTYPES ANTAG_DOUBLE 10 +#ANTAG_PARADISE_MODE_SUBTYPES ANTAG_TRIPPLE 10 +#ANTAG_PARADISE_MODE_SUBTYPES ANTAG_RANDOM 10 + +## Relative weights for each antag in gamemode subtypes for ANTAG-PARADISE gamemode. Works in pair with ANTAG_PARADISE_MODE_SUBTYPES. +#ANTAG_PARADISE_SUBTYPE_WEIGHTS ANTAG_SINGLE 6 +#ANTAG_PARADISE_SUBTYPE_WEIGHTS ANTAG_DOUBLE 4 +#ANTAG_PARADISE_SUBTYPE_WEIGHTS ANTAG_TRIPPLE 2 +#ANTAG_PARADISE_SUBTYPE_WEIGHTS ANTAG_RANDOM 10 + +## Weights for all special antags in ANTAG-PARADISE gamemode. Hijacker, Malf AI and Ninja currently. +#ANTAG_PARADISE_SPECIAL_WEIGHTS HIJACKER 10 +#ANTAG_PARADISE_SPECIAL_WEIGHTS MALFAI 10 +#ANTAG_PARADISE_SPECIAL_WEIGHTS NINJA 10 +#ANTAG_PARADISE_SPECIAL_WEIGHTS NOTHING 30 + +## Chance for traitors to roll additional antag role in ANTAG-PARADISE gamemode. Leave it commented for default 10% chance. +#ANTAG_PARADISE_DOUBLE_ANTAG_CHANCE 10 + ## Maximum cycles shadowlings can remain unhatched before they take damage. 1800 = 60 minutes, 900 = 30 minutes, 0 = feature disabled. SHADOWLING_MAX_AGE 0 diff --git a/paradise.dme b/paradise.dme index c92e9467636..971f22d0c35 100644 --- a/paradise.dme +++ b/paradise.dme @@ -638,6 +638,7 @@ #include "code\game\gamemodes\scoreboard.dm" #include "code\game\gamemodes\setupgame.dm" #include "code\game\gamemodes\steal_items.dm" +#include "code\game\gamemodes\antag_paradise\antag_paradise.dm" #include "code\game\gamemodes\autotraitor\autotraitor.dm" #include "code\game\gamemodes\blob\blob.dm" #include "code\game\gamemodes\blob\blob_finish.dm"