diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index cd94e5790b..665c0340a4 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -347,3 +347,5 @@ /// obtained from mapping helper #define MAPPING_HELPER_TRAIT "mapping-helper" +/// Trait associated with mafia +#define MAFIA_TRAIT "mafia" diff --git a/code/modules/client/verbs/suicide.dm b/code/modules/client/verbs/suicide.dm index cae9a54484..453c22e7f2 100644 --- a/code/modules/client/verbs/suicide.dm +++ b/code/modules/client/verbs/suicide.dm @@ -207,6 +207,10 @@ message_admins("[key_name(src)] (job: [src.job ? "[src.job]" : "None"]) [is_special_character(src) ? "(ANTAG!) " : ""][ghosting ? "ghosted" : "committed suicide"] at [AREACOORD(src)].") /mob/living/proc/canSuicide() + var/area/A = get_area(src) + if(A.area_flags & BLOCK_SUICIDE) + to_chat(src, span_warning("You can't commit suicide here! You can ghost if you'd like.")) + return FALSE if(!CONFIG_GET(flag/suicide_allowed)) to_chat(src, "Suicide is not enabled in the config.") return FALSE @@ -214,11 +218,11 @@ if(CONSCIOUS) return TRUE if(SOFT_CRIT) - to_chat(src, "You can't commit suicide while in a critical condition!") + to_chat(src, span_warning("You can't commit suicide while in a critical condition!")) if(UNCONSCIOUS) - to_chat(src, "You need to be conscious to commit suicide!") + to_chat(src, span_warning("You need to be conscious to commit suicide!")) if(DEAD) - to_chat(src, "You're already dead!") + to_chat(src, span_warning("You're already dead!")) return /mob/living/carbon/canSuicide() diff --git a/code/modules/mafia/_defines.dm b/code/modules/mafia/_defines.dm index 194851beed..6c0c73e0d0 100644 --- a/code/modules/mafia/_defines.dm +++ b/code/modules/mafia/_defines.dm @@ -6,14 +6,16 @@ #define MAFIA_TEAM_SOLO "solo" //types of town roles for random setup gen -/// assistants it's just assistants filling up the rest of the roles +/// Add this if you don't want a role to be a choice in the selection #define TOWN_OVERFLOW "overflow" /// roles that learn info about others in the game (chaplain, detective, psych) #define TOWN_INVEST "invest" -/// roles that keep other roles safe (doctor, and weirdly enough lawyer counts) +/// roles that keep other roles safe (doctor, sec officer, and weirdly enough lawyer counts) #define TOWN_PROTECT "protect" +/// roles that are only there to kill bad guys. +#define TOWN_KILLING "killing" /// roles that don't fit into anything else (hop) -#define TOWN_MISC "misc" +#define TOWN_SUPPORT "support" //other types (mafia team, neutrals) /// normal vote kill changelings @@ -25,6 +27,20 @@ /// role that upsets the game aka obsessed, usually worse for town than mafia but they can vote against mafia #define NEUTRAL_DISRUPT "disrupt" +//role flags (special status of roles like detection immune) +///to all forms of detection, shows themselves as an assistant. +#define ROLE_UNDETECTABLE (1<<0) +///has the ability to kill at night and thus, blocks the game from ending with other teams alive. +#define ROLE_CAN_KILL (1<<1) +///can only be one in a randomly generated game +#define ROLE_UNIQUE (1<<2) +///role is public to all other players in the game. +#define ROLE_REVEALED (1<<3) +///can not be defended, protected, or any other form of protection. all kills succeed no matter what. +#define ROLE_VULNERABLE (1<<4) +///cannot perform any actions that night, preselected actions fail +#define ROLE_ROLEBLOCKED (1<<5) + #define MAFIA_PHASE_SETUP 1 #define MAFIA_PHASE_DAY 2 #define MAFIA_PHASE_VOTING 3 @@ -35,12 +51,12 @@ #define MAFIA_ALIVE 1 #define MAFIA_DEAD 2 +#define COMSIG_MAFIA_ON_VISIT "mafia_onvisit" +#define MAFIA_VISIT_INTERRUPTED 1 + #define COMSIG_MAFIA_ON_KILL "mafia_onkill" #define MAFIA_PREVENT_KILL 1 -#define COMSIG_MAFIA_CAN_PERFORM_ACTION "mafia_can_perform_action" -#define MAFIA_PREVENT_ACTION 1 - //in order of events + game end /// when the shutters fall, before the 45 second wait and night event resolution diff --git a/code/modules/mafia/controller.dm b/code/modules/mafia/controller.dm index 54dcaebeec..43d15ebffe 100644 --- a/code/modules/mafia/controller.dm +++ b/code/modules/mafia/controller.dm @@ -15,6 +15,8 @@ var/phase = MAFIA_PHASE_SETUP ///how long the game has gone on for, changes with every sunrise. day one, night one, day two, etc. var/turn = 0 + ///if enabled, the game has fallen under half pop and is sped up + var/speed_up = FALSE ///for debugging and testing a full game, or adminbuse. If this is not empty, it will use this as a setup. clears when game is over var/list/custom_setup = list() ///first day has no voting, and thus is shorter @@ -187,8 +189,22 @@ turn += 1 phase = MAFIA_PHASE_DAY if(!check_victory()) + if(!speed_up)//lets check if the game should be sped up, if not already. + var/living_players = 0 + for(var/i in all_roles) + var/datum/mafia_role/player = i + if(player.game_status == MAFIA_ALIVE) + living_players += 1 + if(living_players < all_roles.len / 2) + speed_up = TRUE + send_message("With only [living_players] living players left, the game timers have been sped up.") + day_phase_period /= 2 + voting_phase_period /= 2 + judgement_phase_period /= 2 + judgement_lynch_period /= 2 + night_phase_period /= 2 if(turn == 1) - send_message("The selected map is [current_map.name]!
[current_map.description]
") + send_message(span_notice("The selected map is [current_map.name]!
[current_map.description]")) send_message("Day [turn] started! There is no voting on the first day. Say hello to everybody!") next_phase_timer = addtimer(CALLBACK(src,.proc/check_trial, FALSE),first_day_phase_period,TIMER_STOPPABLE) //no voting period = no votes = instant night else @@ -257,19 +273,19 @@ /datum/mafia_controller/proc/lynch() for(var/i in judgement_innocent_votes) var/datum/mafia_role/role = i - send_message("[role.body.real_name] voted innocent.") + send_message(span_green("[role.body.real_name] voted innocent.")) for(var/ii in judgement_abstain_votes) var/datum/mafia_role/role = ii - send_message("[role.body.real_name] abstained.") + send_message(span_comradio("[role.body.real_name] abstained.")) for(var/iii in judgement_guilty_votes) var/datum/mafia_role/role = iii - send_message("[role.body.real_name] voted guilty.") + send_message(span_red("[role.body.real_name] voted guilty.")) if(judgement_guilty_votes.len > judgement_innocent_votes.len) //strictly need majority guilty to lynch - send_message("Guilty wins majority, [on_trial.body.real_name] has been lynched.") - on_trial.kill(src, lynch = TRUE) + send_message(span_red("Guilty wins majority, [on_trial.body.real_name] has been lynched.")) + on_trial.kill(src,lynch = TRUE) addtimer(CALLBACK(src, .proc/send_home, on_trial),judgement_lynch_period) else - send_message("Innocent wins majority, [on_trial.body.real_name] has been spared.") + send_message(span_green("Innocent wins majority, [on_trial.body.real_name] has been spared.")) on_trial.body.forceMove(get_turf(on_trial.assigned_landmark)) on_trial = null //day votes are already cleared, so this will skip the trial and check victory/lockdown/whatever else @@ -300,11 +316,14 @@ var/list/total_town = list() var/list/total_mafia = list() - var/alive_town = 0 + //voting power of town + solos (since they don't want mafia to overpower) + var/anti_mafia_power = 0 + //voting power of mafia (greater than anti mafia power + team end not blocked = mafia victory) var/alive_mafia = 0 var/list/solos_to_ask = list() //need to ask after because first round is counting team sizes var/list/total_victors = list() //if this list gets filled with anyone, they win. list because side antags can with with people var/blocked_victory = FALSE //if a solo antagonist is stopping the town or mafia from finishing the game. + var/town_can_kill = FALSE //Town has a killing role and it cannot allow mafia to win ///PHASE ONE: TALLY UP ALL NUMBERS OF PEOPLE STILL ALIVE @@ -313,23 +332,24 @@ if(MAFIA_TEAM_MAFIA) total_mafia += R if(R.game_status == MAFIA_ALIVE) - alive_mafia += R.vote_power + alive_mafia += R.vote_potential if(MAFIA_TEAM_TOWN) total_town += R if(R.game_status == MAFIA_ALIVE) - alive_town += R.vote_power + anti_mafia_power += R.vote_potential + if(R.role_flags & ROLE_CAN_KILL) //the game cannot autoresolve with killing roles (unless a solo wins anyways, like traitors who are immune) + town_can_kill = TRUE if(MAFIA_TEAM_SOLO) if(R.game_status == MAFIA_ALIVE) - if(R.solo_counts_as_town) - alive_town += R.vote_power + anti_mafia_power += R.vote_potential solos_to_ask += R ///PHASE TWO: SEND STATS TO SOLO ANTAGS, SEE IF THEY WON OR TEAMS CANNOT WIN for(var/datum/mafia_role/solo in solos_to_ask) - if(solo.check_total_victory(alive_town, alive_mafia)) + if(solo.check_total_victory(anti_mafia_power, alive_mafia)) total_victors += solo - if(solo.block_team_victory(alive_town, alive_mafia)) + if(solo.block_team_victory(anti_mafia_power, alive_mafia)) blocked_victory = TRUE //solo victories! @@ -348,7 +368,7 @@ award_role(townie.winner_award, townie) start_the_end("!! TOWN VICTORY !!") return TRUE - else if(alive_mafia >= alive_town) //guess could change if town nightkill is added + else if(alive_mafia >= anti_mafia_power && !town_can_kill) start_the_end("!! MAFIA VICTORY !!") for(var/datum/mafia_role/changeling in total_mafia) award_role(changeling.winner_award, changeling) @@ -396,6 +416,13 @@ custom_setup = list() turn = 0 votes = list() + + day_phase_period = initial(day_phase_period) + voting_phase_period = initial(voting_phase_period) + judgement_phase_period = initial(judgement_phase_period) + judgement_lynch_period = initial(judgement_lynch_period) + night_phase_period = initial(night_phase_period) + //map gen does not deal with landmarks QDEL_LIST(landmarks) QDEL_NULL(town_center_landmark) @@ -451,14 +478,15 @@ SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_START) SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_ACTION_PHASE) //resolve mafia kill, todo unsnowflake this - var/datum/mafia_role/R = get_vote_winner("Mafia") - if(R) + var/datum/mafia_role/victim = get_vote_winner("Mafia") + if(victim) var/datum/mafia_role/killer = get_random_voter("Mafia") - if(SEND_SIGNAL(killer,COMSIG_MAFIA_CAN_PERFORM_ACTION,src,"mafia killing",R) & MAFIA_PREVENT_ACTION) - send_message("[killer.body.real_name] was unable to attack [R.body.real_name] tonight!",MAFIA_TEAM_MAFIA) + if(!victim.can_action(src, killer, "changeling murder")) + send_message(span_danger("[killer.body.real_name] was unable to attack [victim.body.real_name] tonight!"),MAFIA_TEAM_MAFIA) else - send_message("[killer.body.real_name] has attacked [R.body.real_name]!",MAFIA_TEAM_MAFIA) - R.kill(src) + send_message(span_danger("[killer.body.real_name] has attacked [victim.body.real_name]!"),MAFIA_TEAM_MAFIA) + if(victim.kill(src,killer,lynch=FALSE)) + to_chat(victim.body, span_userdanger("You have been killed by a Changeling!")) reset_votes("Mafia") SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_KILL_PHASE) SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_END) @@ -485,14 +513,14 @@ else votes[vote_type][voter] = target if(old_vote && old_vote == target) - send_message("[voter.body.real_name] retracts their vote for [target.body.real_name]!", team = teams) + send_message(span_notice("[voter.body.real_name] retracts their vote for [target.body.real_name]!"), team = teams) else - send_message("[voter.body.real_name] voted for [target.body.real_name]!",team = teams) + send_message(span_notice("[voter.body.real_name] voted for [target.body.real_name]!"),team = teams) if(!teams) - target.body.update_icon() //Update the vote display if it's a public vote + target.body.update_appearance() //Update the vote display if it's a public vote var/datum/mafia_role/old = old_vote if(old) - old.body.update_icon() + old.body.update_appearance() /** * Clears out the votes of a certain type (day votes, mafia kill votes) while leaving others untouched @@ -504,7 +532,7 @@ bodies_to_update += R.body votes[vote_type] = list() for(var/mob/M in bodies_to_update) - M.update_icon() + M.update_appearance() /** * Returns how many people voted for the role, in whatever vote (day vote, night kill vote) @@ -571,6 +599,9 @@ /datum/mafia_controller/proc/create_bodies() for(var/datum/mafia_role/role in all_roles) var/mob/living/carbon/human/H = new(get_turf(role.assigned_landmark)) + ADD_TRAIT(H, TRAIT_NOFIRE, MAFIA_TRAIT) + ADD_TRAIT(H, TRAIT_NOBREATH, MAFIA_TRAIT) + // ADD_TRAIT(H, TRAIT_CANNOT_CRYSTALIZE, MAFIA_TRAIT) freon tomfoolery H.equipOutfit(player_outfit) H.status_flags |= GODMODE RegisterSignal(H,COMSIG_ATOM_UPDATE_OVERLAYS,.proc/display_votes) @@ -707,6 +738,7 @@ if(role_count > 0) debug_setup[found_path] = role_count custom_setup = debug_setup + try_autostart()//don't worry, this fails if there's a game in progress if("cancel_setup") custom_setup = list() switch(action) //both living and dead @@ -723,28 +755,28 @@ switch(action) if("mf_signup") if(!SSticker.HasRoundStarted()) - to_chat(usr, "Wait for the round to start.") + to_chat(usr, span_warning("Wait for the round to start.")) return if(GLOB.mafia_signup[C.ckey]) GLOB.mafia_signup -= C.ckey - to_chat(usr, "You unregister from Mafia.") + to_chat(usr, span_notice("You unregister from Mafia.")) return TRUE else GLOB.mafia_signup[C.ckey] = C - to_chat(usr, "You sign up for Mafia.") + to_chat(usr, span_notice("You sign up for Mafia.")) if(phase == MAFIA_PHASE_SETUP) check_signups() try_autostart() return TRUE if("mf_spectate") if(C.ckey in spectators) - to_chat(usr, "You will no longer get messages from the game.") + to_chat(usr, span_notice("You will no longer get messages from the game.")) spectators -= C.ckey else - to_chat(usr, "You will now get messages from the game.") + to_chat(usr, span_notice("You will now get messages from the game.")) spectators += C.ckey return TRUE - if(user_role.game_status == MAFIA_DEAD) + if(user_role && user_role.game_status == MAFIA_DEAD) return //User actions (just living) switch(action) @@ -814,58 +846,59 @@ . += L[key] /** - * Returns a semirandom setup, with... - * Town, Two invest roles, one protect role, sometimes a misc role, and the rest assistants for town. - * Mafia, 2 normal mafia and one special. - * Neutral, two disruption roles, sometimes one is a killing. + * Returns a semirandom setup with 12 roles. balance not guaranteed! * - * See _defines.dm in the mafia folder for a rundown on what these groups of roles include. + * please check the variables at the top of the proc to see how much of each role types it picks */ /datum/mafia_controller/proc/generate_random_setup() var/invests_left = 2 - var/protects_left = 1 - var/miscs_left = prob(35) + var/protects_left = 2 + var/killings_left = 1 + var/supports_left = 2 + var/mafiareg_left = 2 var/mafiaspe_left = 1 - var/killing_role = prob(50) - var/disruptors = killing_role ? 1 : 2 //still required to calculate overflow - var/overflow_left = max_player - (invests_left + protects_left + miscs_left + mafiareg_left + mafiaspe_left + killing_role + disruptors) + + // if there is one killing role, there will be less disruptors + var/neutral_killing_role = prob(50) var/list/random_setup = list() + var/list/unique_roles_added = list() for(var/i in 1 to max_player) //should match the number of roles to add - if(overflow_left) - add_setup_role(random_setup, TOWN_OVERFLOW) - overflow_left-- - else if(invests_left) - add_setup_role(random_setup, TOWN_INVEST) + if(invests_left) + add_setup_role(random_setup, unique_roles_added, TOWN_INVEST) invests_left-- else if(protects_left) - add_setup_role(random_setup, TOWN_PROTECT) + add_setup_role(random_setup, unique_roles_added, TOWN_PROTECT) protects_left-- - else if(miscs_left) - add_setup_role(random_setup, TOWN_MISC) - miscs_left-- + else if(killings_left) + add_setup_role(random_setup, unique_roles_added, TOWN_KILLING) + killings_left-- + else if(supports_left) + add_setup_role(random_setup, unique_roles_added, TOWN_SUPPORT) + supports_left-- else if(mafiareg_left) - add_setup_role(random_setup, MAFIA_REGULAR) + add_setup_role(random_setup, unique_roles_added, MAFIA_REGULAR) mafiareg_left-- else if(mafiaspe_left) - add_setup_role(random_setup, MAFIA_SPECIAL) + add_setup_role(random_setup, unique_roles_added, MAFIA_SPECIAL) mafiaspe_left-- - else if(killing_role) - add_setup_role(random_setup, NEUTRAL_KILL) - killing_role-- + else if(neutral_killing_role) + add_setup_role(random_setup, unique_roles_added, NEUTRAL_KILL) + neutral_killing_role-- else - add_setup_role(random_setup, NEUTRAL_DISRUPT) + add_setup_role(random_setup, unique_roles_added, NEUTRAL_DISRUPT) + debug = random_setup return random_setup /** - * Helper proc that adds a random role of a type to a setup. if it doesn't exist in the setup, it adds the path to the list and otherwise bumps the path in the list up one + * Helper proc that adds a random role of a type to a setup. if it doesn't exist in the setup, it adds the path to the list and otherwise bumps the path in the list up one. unique roles can only get added once. */ -/datum/mafia_controller/proc/add_setup_role(setup_list, wanted_role_type) +/datum/mafia_controller/proc/add_setup_role(setup_list, banned_roles, wanted_role_type) var/list/role_type_paths = list() for(var/path in typesof(/datum/mafia_role)) var/datum/mafia_role/instance = path - if(initial(instance.role_type) == wanted_role_type) + if(initial(instance.role_type) == wanted_role_type && !(path in banned_roles)) role_type_paths += instance var/mafia_path = pick(role_type_paths) @@ -880,6 +913,8 @@ setup_list[found_role] += 1 return setup_list[mafia_path] = 1 + if(initial(mafia_path_type.role_flags) & ROLE_UNIQUE) //check to see if we should no longer consider this okay to add to the game + banned_roles += mafia_path /** * Called when enough players have signed up to fill a setup. DOESN'T NECESSARILY MEAN THE GAME WILL START. @@ -893,6 +928,8 @@ var/list/setup = custom_setup if(!setup.len) req_players = max_player //MAFIA_MAX_PLAYER_COUNT + else if(low_pop_mode) + req_players = required_player else req_players = assoc_value_sum(setup) @@ -909,10 +946,10 @@ GLOB.mafia_signup -= key //not valid to play when we checked so remove them from signups //if there were not enough players, don't start. we already trimmed the list to now hold only valid signups + if(length(possible_keys) < req_players) return - else //hacky implementation of max players - req_players = clamp(length(possible_keys), 1, max_player) + req_players = clamp(length(possible_keys), required_player, max_player) //if there were too many players, still start but only make filtered keys as big as it needs to be (cut excess) //also removes people who do get into final player list from the signup so they have to sign up again when game ends @@ -923,8 +960,8 @@ //small message about not getting into this game for clarity on why they didn't get in for(var/unpicked in possible_keys) var/client/unpicked_client = GLOB.directory[unpicked] - to_chat(unpicked_client, "Sorry, the starting mafia game has too many players and you were not picked.") - to_chat(unpicked_client, "You're still signed up, getting messages from the current round, and have another chance to join when the one starting now finishes.") + to_chat(unpicked_client, span_danger("Sorry, the starting mafia game has too many players and you were not picked.")) + to_chat(unpicked_client, span_warning("You're still signed up, getting messages from the current round, and have another chance to join when the one starting now finishes.")) if(!setup.len) //don't actually have one yet, so generate a max player random setup. it's good to do this here instead of above so it doesn't generate one every time a game could possibly start. setup = generate_random_setup() diff --git a/code/modules/mafia/map_pieces.dm b/code/modules/mafia/map_pieces.dm index 3339c596b4..61b8e5dd3e 100644 --- a/code/modules/mafia/map_pieces.dm +++ b/code/modules/mafia/map_pieces.dm @@ -33,7 +33,7 @@ requires_power = FALSE has_gravity = STANDARD_GRAVITY flags_1 = NONE - // block_suicide = TRUE + area_flags = BLOCK_SUICIDE | UNIQUE_AREA /datum/map_template/mafia var/description = "" @@ -75,5 +75,5 @@ /datum/map_template/mafia/reebe name = "Reebe" - description = "Trouble in Reebe station! Copypaste guranteed by ClockCo™" + description = "Based of the place known as reebee. Syndicate spies have infiltrated and everyone has to find out who's who before they destroy the ark!" mappath = "_maps/map_files/Mafia/mafia_reebe.dmm" diff --git a/code/modules/mafia/outfits.dm b/code/modules/mafia/outfits.dm index bbc72bd120..fad628e392 100644 --- a/code/modules/mafia/outfits.dm +++ b/code/modules/mafia/outfits.dm @@ -30,6 +30,11 @@ uniform = /obj/item/clothing/under/suit/black shoes = /obj/item/clothing/shoes/laceup +/datum/outfit/mafia/chaplain + name = "Mafia Chaplain" + + uniform = /obj/item/clothing/under/rank/civilian/chaplain + /datum/outfit/mafia/md name = "Mafia Medical Doctor" @@ -37,10 +42,14 @@ shoes = /obj/item/clothing/shoes/sneakers/white suit = /obj/item/clothing/suit/toggle/labcoat -/datum/outfit/mafia/chaplain - name = "Mafia Chaplain" +/datum/outfit/mafia/security + name = "Mafia Security Officer" - uniform = /obj/item/clothing/under/rank/civilian/chaplain + uniform = /obj/item/clothing/under/rank/security/officer + gloves = /obj/item/clothing/gloves/color/black + head = /obj/item/clothing/head/helmet/sec + suit = /obj/item/clothing/suit/armor/vest/alt + shoes = /obj/item/clothing/shoes/jackboots /datum/outfit/mafia/lawyer name = "Mafia Lawyer" @@ -58,6 +67,26 @@ head = /obj/item/clothing/head/hopcap glasses = /obj/item/clothing/glasses/sunglasses +/datum/outfit/mafia/hos + name = "Mafia Head of Security" + + uniform = /obj/item/clothing/under/rank/security/head_of_security + shoes = /obj/item/clothing/shoes/jackboots + suit = /obj/item/clothing/suit/armor/hos/trenchcoat + gloves = /obj/item/clothing/gloves/color/black + head = /obj/item/clothing/head/beret/sec + glasses = /obj/item/clothing/glasses/hud/security/sunglasses + +/datum/outfit/mafia/warden + name = "Mafia Warden" + + uniform = /obj/item/clothing/under/rank/security/warden + shoes = /obj/item/clothing/shoes/jackboots + suit = /obj/item/clothing/suit/armor/vest/warden/alt + gloves = /obj/item/clothing/gloves/color/black + head = /obj/item/clothing/head/warden + glasses = /obj/item/clothing/glasses/hud/security/sunglasses + //mafia /datum/outfit/mafia/changeling diff --git a/code/modules/mafia/roles.dm b/code/modules/mafia/roles.dm index a210c4994f..a48f890d10 100644 --- a/code/modules/mafia/roles.dm +++ b/code/modules/mafia/roles.dm @@ -10,19 +10,21 @@ var/mob/living/carbon/human/body var/obj/effect/landmark/mafia/assigned_landmark - ///how many votes submitted when you vote. + ///role flags (special status of roles like detection immune) + var/role_flags = NONE + ///how many votes submitted when you vote. used in voting, but not victory var/vote_power = 1 - var/detect_immune = FALSE - var/revealed = FALSE - var/datum/outfit/revealed_outfit = /datum/outfit/mafia/assistant //the assistants need a special path to call out they were in fact assistant, everything else can just use job equipment - //action = uses + ///how many votes your role COULD count for, now or later. used in checking victory + var/vote_potential = 1 + ///what they get equipped with when they are revealed + var/datum/outfit/revealed_outfit = /datum/outfit/mafia/assistant + ///action = uses var/list/actions = list() var/list/targeted_actions = list() - //what the role gets when it wins a game + ///what the role gets when it wins a game var/winner_award = /datum/award/achievement/mafia/assistant - //so mafia have to also kill them to have a majority - var/solo_counts_as_town = FALSE //(don't set this for town) + ///so mafia have to also kill them to have a majority var/game_status = MAFIA_ALIVE ///icon state in the mafia dmi of the hud of the role, used in the mafia ui @@ -38,8 +40,30 @@ /datum/mafia_role/New(datum/mafia_controller/game) . = ..() -/datum/mafia_role/proc/kill(datum/mafia_controller/game,lynch=FALSE) - if(SEND_SIGNAL(src,COMSIG_MAFIA_ON_KILL,game,lynch) & MAFIA_PREVENT_KILL) +/** + * Tests if a visitor can actually perform an action on this role. Verbose on purpose! + * + * Will return false if: Your visit is roleblocked, they have perished, or your visit was interrupted + */ +/datum/mafia_role/proc/can_action(datum/mafia_controller/game, datum/mafia_role/visitor, action) + if(role_flags & ROLE_ROLEBLOCKED) + to_chat(visitor,span_danger("Your [action] was blocked!")) + return FALSE + if(game_status != MAFIA_ALIVE) //They're already dead + to_chat(visitor,span_danger("[body.real_name] perished before you could visit!")) + return FALSE + if(SEND_SIGNAL(src,COMSIG_MAFIA_ON_VISIT,game,visitor) & MAFIA_VISIT_INTERRUPTED) //visited a warden. something that prevents you by visiting that person + to_chat(visitor,span_danger("Your [action] was interrupted!")) + return FALSE + return TRUE + +/** + * Tests kill immunities, if nothing prevents the kill, kills this role. + * + * Does not count as visiting, see visit proc. + */ +/datum/mafia_role/proc/kill(datum/mafia_controller/game, datum/mafia_role/attacker, lynch=FALSE) + if(SEND_SIGNAL(src,COMSIG_MAFIA_ON_KILL,game,attacker,lynch) & MAFIA_PREVENT_KILL) return FALSE game_status = MAFIA_DEAD body.death() @@ -55,19 +79,19 @@ /datum/mafia_role/proc/greet() SEND_SOUND(body, 'sound/ambience/ambifailure.ogg') - to_chat(body,"You are the [name].") - to_chat(body,"[desc]") + to_chat(body,span_danger("You are the [name].")) + to_chat(body,span_danger("[desc]")) switch(team) if(MAFIA_TEAM_MAFIA) - to_chat(body,"You and your co-conspirators win if you outnumber crewmembers.") + to_chat(body,span_danger("You and your co-conspirators win if you outnumber crewmembers.")) if(MAFIA_TEAM_TOWN) - to_chat(body,"You are a crewmember. Find out and lynch the changelings!") + to_chat(body,span_danger("You are a crewmember. Find out and lynch the changelings!")) if(MAFIA_TEAM_SOLO) - to_chat(body,"You are not aligned to town or mafia. Accomplish your own objectives!") - to_chat(body, "Be sure to read the wiki page to learn more, if you have no idea what's going on.") + to_chat(body,span_danger("You are not aligned to town or mafia. Accomplish your own objectives!")) + to_chat(body, "Be sure to read the wiki page to learn more, if you have no idea what's going on.") /datum/mafia_role/proc/reveal_role(datum/mafia_controller/game, verbose = FALSE) - if(revealed) + if((role_flags & ROLE_REVEALED)) return if(verbose) game.send_message("It is revealed that the true role of [body] [game_status == MAFIA_ALIVE ? "is" : "was"] [name]!") @@ -76,7 +100,7 @@ qdel(thing) special_reveal_equip(game) body.equipOutfit(revealed_outfit) - revealed = TRUE + role_flags |= ROLE_REVEALED /datum/mafia_role/proc/special_reveal_equip(datum/mafia_controller/game) return @@ -85,7 +109,7 @@ return /datum/mafia_role/proc/validate_action_target(datum/mafia_controller/game,action,datum/mafia_role/target) - if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,action,target) & MAFIA_PREVENT_ACTION) + if((role_flags & ROLE_ROLEBLOCKED)) return FALSE return TRUE @@ -114,9 +138,9 @@ team_desc = "Nobody" team_span = "comradio" the = FALSE - result += "The [name] is aligned with [the ? "the " : ""][team_desc]" + result += span_notice("The [span_bold("[name]")] is aligned with [the ? "the " : ""][team_desc]") result += "\"[desc]\"" - result += "[name] wins when they [win_condition]" + result += span_notice("[name] wins when they [win_condition]") to_chat(clueless, result.Join("
")) /datum/mafia_role/detective @@ -145,35 +169,39 @@ /datum/mafia_role/detective/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target) if(!target || target.game_status != MAFIA_ALIVE) - to_chat(body,"You can only investigate alive people.") + to_chat(body,span_warning("You can only investigate alive people.")) return - to_chat(body,"You will investigate [target.body.real_name] tonight.") + to_chat(body,span_warning("You will investigate [target.body.real_name] tonight.")) current_investigation = target /datum/mafia_role/detective/proc/investigate(datum/mafia_controller/game) SIGNAL_HANDLER + if(!current_investigation) + return + var/datum/mafia_role/target = current_investigation - if(target) - if(target.detect_immune) - to_chat(body,"Your investigations reveal that [target.body.real_name] is a true member of the station.") - add_note("N[game.turn] - [target.body.real_name] - Town") - else - var/team_text - var/fluff - switch(target.team) - if(MAFIA_TEAM_TOWN) - team_text = "Town" - fluff = "a true member of the station." - if(MAFIA_TEAM_MAFIA) - team_text = "Mafia" - fluff = "an unfeeling, hideous changeling!" - if(MAFIA_TEAM_SOLO) - team_text = "Solo" - fluff = "a rogue, with their own objectives..." - to_chat(body,"Your investigations reveal that [target.body.real_name] is [fluff]") - add_note("N[game.turn] - [target.body.real_name] - [team_text]") current_investigation = null + if(!target.can_action(game, src, "investigation")) + return + if((target.role_flags & ROLE_UNDETECTABLE)) + to_chat(body,span_warning("Your investigations reveal that [target.body.real_name] is a true member of the station.")) + add_note("N[game.turn] - [target.body.real_name] - Town") + else + var/team_text + var/fluff + switch(target.team) + if(MAFIA_TEAM_TOWN) + team_text = "Town" + fluff = "a true member of the station." + if(MAFIA_TEAM_MAFIA) + team_text = "Mafia" + fluff = "an unfeeling, hideous changeling!" + if(MAFIA_TEAM_SOLO) + team_text = "Solo" + fluff = "a rogue, with their own objectives..." + to_chat(body,span_warning("Your investigations reveal that [target.body.real_name] is [fluff]")) + add_note("N[game.turn] - [target.body.real_name] - [team_text]") /datum/mafia_role/psychologist name = "Psychologist" @@ -195,25 +223,27 @@ /datum/mafia_role/psychologist/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target) . = ..() - if(!. || !can_use || game.phase == MAFIA_PHASE_NIGHT || target.game_status != MAFIA_ALIVE || target.revealed || target == src) + if(!. || !can_use || game.phase == MAFIA_PHASE_NIGHT || target.game_status != MAFIA_ALIVE || (target.role_flags & ROLE_REVEALED) || target == src) return FALSE /datum/mafia_role/psychologist/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target) . = ..() - to_chat(body,"You will reveal [target.body.real_name] tonight.") + to_chat(body,span_warning("You will reveal [target.body.real_name] tonight.")) current_target = target /datum/mafia_role/psychologist/proc/therapy_reveal(datum/mafia_controller/game) SIGNAL_HANDLER - if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"reveal",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by a lawyer. - current_target = null - if(current_target) - add_note("N[game.turn] - [current_target.body.real_name] - Revealed true identity") - to_chat(body,"You have revealed the true nature of the [current_target]!") - current_target.reveal_role(game, verbose = TRUE) - current_target = null - can_use = FALSE + if(!current_target) + return + var/datum/mafia_role/target = current_target + current_target = null + if(!target.can_action(game, src, "role reveal")) + return + add_note("N[game.turn] - [target.body.real_name] - Revealed true identity") + to_chat(body,span_warning("You have revealed the true nature of the [target]!")) + target.reveal_role(game, verbose = TRUE) + can_use = FALSE /datum/mafia_role/chaplain name = "Chaplain" @@ -225,7 +255,7 @@ winner_award = /datum/award/achievement/mafia/chaplain targeted_actions = list("Pray") - var/current_target + var/datum/mafia_role/current_target /datum/mafia_role/chaplain/New(datum/mafia_controller/game) . = ..() @@ -235,25 +265,29 @@ . = ..() if(!.) return - return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_DEAD && target != src && !target.revealed + return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_DEAD && target != src && !(target.role_flags & ROLE_REVEALED) /datum/mafia_role/chaplain/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target) - to_chat(body,"You will commune with the spirit of [target.body.real_name] tonight.") + to_chat(body,span_warning("You will commune with the spirit of [target.body.real_name] tonight.")) current_target = target /datum/mafia_role/chaplain/proc/commune(datum/mafia_controller/game) SIGNAL_HANDLER + if(!current_target) + return var/datum/mafia_role/target = current_target + current_target = null + if(!target.can_action(game, src, "communion")) + return if(target) - to_chat(body,"You invoke spirit of [target.body.real_name] and learn their role was [target.name].") + to_chat(body,span_warning("You invoke spirit of [target.body.real_name] and learn their role was [target.name].")) add_note("N[game.turn] - [target.body.real_name] - [target.name]") - current_target = null /datum/mafia_role/md name = "Medical Doctor" desc = "You can protect a single person each night from killing." - revealed_outfit = /datum/outfit/mafia/md // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these) + revealed_outfit = /datum/outfit/mafia/md role_type = TOWN_PROTECT hud_icon = "hudmedicaldoctor" revealed_icon = "medicaldoctor" @@ -271,29 +305,38 @@ . = ..() if(!.) return - if(target.name == "Head of Personnel" && target.revealed) + if((target.role_flags & ROLE_VULNERABLE) && (target.role_flags & ROLE_REVEALED)) //do not give the option to protect roles that your protection will fail on return FALSE return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src /datum/mafia_role/md/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target) if(!target || target.game_status != MAFIA_ALIVE) - to_chat(body,"You can only protect alive people.") + to_chat(body,span_warning("You can only protect alive people.")) return - to_chat(body,"You will protect [target.body.real_name] tonight.") + to_chat(body,span_warning("You will protect [target.body.real_name] tonight.")) current_protected = target /datum/mafia_role/md/proc/protect(datum/mafia_controller/game) SIGNAL_HANDLER - if(current_protected) - RegisterSignal(current_protected,COMSIG_MAFIA_ON_KILL,.proc/prevent_kill) - add_note("N[game.turn] - Protected [current_protected.body.real_name]") + if(!current_protected) + return + var/datum/mafia_role/target = current_protected + //current protected is unset at the end, as this action ends at a different phase + if(!target.can_action(game, src, "medical assistance")) + return -/datum/mafia_role/md/proc/prevent_kill(datum/source) + RegisterSignal(target,COMSIG_MAFIA_ON_KILL,.proc/prevent_kill) + add_note("N[game.turn] - Protected [target.body.real_name]") + +/datum/mafia_role/md/proc/prevent_kill(datum/source,datum/mafia_controller/game,datum/mafia_role/attacker,lynch) SIGNAL_HANDLER - to_chat(body,"The person you protected tonight was attacked!") - to_chat(current_protected.body,"You were attacked last night, but someone nursed you back to life!") + if((current_protected.role_flags & ROLE_VULNERABLE)) + to_chat(body,span_warning("The person you protected could not be saved.")) + return + to_chat(body,span_warning("The person you protected tonight was attacked!")) + to_chat(current_protected.body,span_greentext("You were attacked last night, but someone nursed you back to life!")) return MAFIA_PREVENT_KILL /datum/mafia_role/md/proc/end_protection(datum/mafia_controller/game) @@ -303,11 +346,77 @@ UnregisterSignal(current_protected,COMSIG_MAFIA_ON_KILL) current_protected = null +/datum/mafia_role/officer + name = "Security Officer" + desc = "You can protect a single person each night. If they are attacked, you will retaliate, killing yourself and the attacker." + revealed_outfit = /datum/outfit/mafia/security + revealed_icon = "securityofficer" + hud_icon = "hudsecurityofficer" + role_type = TOWN_PROTECT + role_flags = ROLE_CAN_KILL + winner_award = /datum/award/achievement/mafia/officer + + targeted_actions = list("Defend") + var/datum/mafia_role/current_defended + +/datum/mafia_role/officer/New(datum/mafia_controller/game) + . = ..() + RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/defend) + RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/end_defense) + +/datum/mafia_role/officer/validate_action_target(datum/mafia_controller/game,action,datum/mafia_role/target) + . = ..() + if(!.) + return + if((role_flags & ROLE_VULNERABLE) && (target.role_flags & ROLE_REVEALED)) //do not give the option to protect roles that your protection will fail on + return FALSE + return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src + +/datum/mafia_role/officer/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target) + if(!target || target.game_status != MAFIA_ALIVE) + to_chat(body,span_warning("You can only defend alive people.")) + return + to_chat(body,span_warning("You will defend [target.body.real_name] tonight.")) + current_defended = target + +/datum/mafia_role/officer/proc/defend(datum/mafia_controller/game) + SIGNAL_HANDLER + + if(!current_defended) + return + var/datum/mafia_role/target = current_defended + //current defended is unset at the end, as this action ends at a different phase + if(!target.can_action(game, src, "security patrol")) + return + if(target) + RegisterSignal(target,COMSIG_MAFIA_ON_KILL,.proc/retaliate) + add_note("N[game.turn] - Defended [target.body.real_name]") + +/datum/mafia_role/officer/proc/retaliate(datum/source,datum/mafia_controller/game,datum/mafia_role/attacker,lynch) + SIGNAL_HANDLER + + if((current_defended.role_flags & ROLE_VULNERABLE)) + to_chat(body,span_warning("The person you defended could not be saved. You could not attack the killer.")) + return + to_chat(body,span_userdanger("The person you defended tonight was attacked!")) + to_chat(current_defended.body,span_userdanger("You were attacked last night, but security fought off the attacker!")) + if(attacker.kill(game,src,FALSE)) //you attack the attacker + to_chat(attacker.body, span_userdanger("You have been ambushed by Security!")) + kill(game,attacker,FALSE) //the attacker attacks you, they were able to attack the target so they can attack you. + return MAFIA_PREVENT_KILL + +/datum/mafia_role/officer/proc/end_defense(datum/mafia_controller/game) + SIGNAL_HANDLER + + if(current_defended) + UnregisterSignal(current_defended,COMSIG_MAFIA_ON_KILL) + current_defended = null + /datum/mafia_role/lawyer name = "Lawyer" desc = "You can choose a person during the day to provide extensive legal advice to during the night, preventing night actions." revealed_outfit = /datum/outfit/mafia/lawyer - role_type = TOWN_PROTECT + role_type = TOWN_SUPPORT hud_icon = "hudlawyer" revealed_icon = "lawyer" winner_award = /datum/award/achievement/mafia/lawyer @@ -317,23 +426,30 @@ /datum/mafia_role/lawyer/New(datum/mafia_controller/game) . = ..() - RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/roleblock_text) - RegisterSignal(game,COMSIG_MAFIA_NIGHT_START,.proc/try_to_roleblock) + RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/roleblock) RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/release) -/datum/mafia_role/lawyer/proc/roleblock_text(datum/mafia_controller/game) +/datum/mafia_role/lawyer/proc/roleblock(datum/mafia_controller/game) SIGNAL_HANDLER - if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"roleblock",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by another lawyer. + if(!current_target) + return + + var/datum/mafia_role/target = current_target + if(!target.can_action(game, src, "roleblock")) //roleblocking a warden moment current_target = null - if(current_target) - to_chat(current_target.body,"YOU HAVE BEEN BLOCKED! YOU CANNOT PERFORM ANY ACTIONS TONIGHT.") - add_note("N[game.turn] - [current_target.body.real_name] - Blocked") + return + + to_chat(target.body,"YOU HAVE BEEN BLOCKED! YOU CANNOT PERFORM ANY ACTIONS TONIGHT.") + add_note("N[game.turn] - [target.body.real_name] - Blocked") + target.role_flags |= ROLE_ROLEBLOCKED /datum/mafia_role/lawyer/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target) . = ..() if(!.) return FALSE + if(target == src) + return FALSE if(game.phase == MAFIA_PHASE_NIGHT) return FALSE if(target.game_status != MAFIA_ALIVE) @@ -343,53 +459,164 @@ . = ..() if(target == current_target) current_target = null - to_chat(body,"You have decided against blocking anyone tonight.") + to_chat(body,span_warning("You have decided against blocking anyone tonight.")) else current_target = target - to_chat(body,"You will block [target.body.real_name] tonight.") - -/datum/mafia_role/lawyer/proc/try_to_roleblock(datum/mafia_controller/game) - SIGNAL_HANDLER - - if(current_target) - RegisterSignal(current_target,COMSIG_MAFIA_CAN_PERFORM_ACTION, .proc/prevent_action) + to_chat(body,span_warning("You will block [target.body.real_name] tonight.")) /datum/mafia_role/lawyer/proc/release(datum/mafia_controller/game) SIGNAL_HANDLER . = ..() if(current_target) - UnregisterSignal(current_target, COMSIG_MAFIA_CAN_PERFORM_ACTION) + current_target.role_flags &= ~ROLE_ROLEBLOCKED current_target = null -/datum/mafia_role/lawyer/proc/prevent_action(datum/source) - SIGNAL_HANDLER - - if(game_status == MAFIA_ALIVE) //in case we got killed while imprisoning sk - bad luck edge - return MAFIA_PREVENT_ACTION - /datum/mafia_role/hop name = "Head of Personnel" desc = "You can reveal yourself once per game, tripling your vote power but becoming unable to be protected!" - revealed_outfit = /datum/outfit/mafia/hop - role_type = TOWN_MISC + role_type = TOWN_SUPPORT + role_flags = ROLE_UNIQUE hud_icon = "hudheadofpersonnel" revealed_icon = "headofpersonnel" + revealed_outfit = /datum/outfit/mafia/hop winner_award = /datum/award/achievement/mafia/hop targeted_actions = list("Reveal") + vote_potential = 3 /datum/mafia_role/hop/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target) . = ..() - if(!. || game.phase == MAFIA_PHASE_NIGHT || game.turn == 1 || target.game_status != MAFIA_ALIVE || target != src || revealed) + if(!. || game.phase == MAFIA_PHASE_NIGHT || game.turn == 1 || target.game_status != MAFIA_ALIVE || target != src || (role_flags & ROLE_REVEALED)) return FALSE /datum/mafia_role/hop/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target) . = ..() reveal_role(game, TRUE) - vote_power = 2 + role_flags |= ROLE_VULNERABLE + vote_power = 3 -///MAFIA ROLES/// only one until i rework this to allow more, they're the "anti-town" working to kill off townies to win +/datum/mafia_role/hos + name = "Head of Security" + desc = "You can decide to execute during the night, visiting someone killing, and revealing them. If they are innocent, you will die at the start of the next night." + role_type = TOWN_KILLING + role_flags = ROLE_CAN_KILL | ROLE_UNIQUE + revealed_outfit = /datum/outfit/mafia/hos + revealed_icon = "headofsecurity" + hud_icon = "hudheadofsecurity" + winner_award = /datum/award/achievement/mafia/hos + + targeted_actions = list("Execute") + var/datum/mafia_role/execute_target + +/datum/mafia_role/hos/New(datum/mafia_controller/game) + . = ..() + RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/execute) + +/datum/mafia_role/hos/validate_action_target(datum/mafia_controller/game,action,datum/mafia_role/target) + . = ..() + if(!.) + return + return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src + +/datum/mafia_role/hos/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target) + if(execute_target == target) + to_chat(body,span_warning("You have decided against executing tonight.")) + to_chat(body,span_warning("You have decided to execute [target.body.real_name] tonight.")) + execute_target = target + +/datum/mafia_role/hos/proc/execute(datum/mafia_controller/game) + SIGNAL_HANDLER + + if(!execute_target) + return + var/datum/mafia_role/target = execute_target + execute_target = null + if(!target.can_action(game, src, "execution")) //roleblocking a warden moment + return + if(!target.kill(game,src,FALSE))//protection + to_chat(body,span_danger("Your attempt at executing [target.body.real_name] was prevented, or [target.body.real_name] is immune!")) + else + to_chat(target.body, span_userdanger("You have been executed by the Head of Security!")) + target.reveal_role(game, verbose = TRUE) + if(target.team == MAFIA_TEAM_TOWN) + to_chat(body,span_userdanger("You have killed an innocent crewmember. You will die tomorrow night.")) + RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/internal_affairs) + role_flags |= ROLE_VULNERABLE + +/datum/mafia_role/hos/proc/internal_affairs(datum/mafia_controller/game) + to_chat(body,span_userdanger("You have been killed by Nanotrasen Internal Affairs!")) + reveal_role(game, verbose = TRUE) + kill(game,src,FALSE) //you technically kill yourself but that shouldn't matter + + +//just helps read better +#define WARDEN_NOT_LOCKDOWN 0//will NOT kill visitors tonight +#define WARDEN_WILL_LOCKDOWN 1 //will kill visitors tonight + +/datum/mafia_role/warden + name = "Warden" + desc = "You can lockdown during the night once, killing any visitors. WARNING: This kills fellow town members, too!" + + role_type = TOWN_KILLING + role_flags = ROLE_CAN_KILL + revealed_outfit = /datum/outfit/mafia/warden + revealed_icon = "warden" + hud_icon = "hudwarden" + winner_award = /datum/award/achievement/mafia/warden + + actions = list("Lockdown") + var/charges = 1 + var/protection_status = WARDEN_NOT_LOCKDOWN + + +/datum/mafia_role/warden/New(datum/mafia_controller/game) + . = ..() + RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/night_start) + RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/night_end) + +/datum/mafia_role/warden/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target) + . = ..() + if(!charges) + to_chat(body,span_danger("You've already locked down this game!")) + return + if(game.phase == MAFIA_PHASE_NIGHT) + to_chat(body,span_danger("You don't have time to lockdown, night has already arrived.")) + return + if(protection_status == WARDEN_WILL_LOCKDOWN) + to_chat(body,span_danger("You decide to not lockdown tonight.")) + else + to_chat(body,span_danger("You decide to lockdown, killing any visitors.")) + protection_status = !protection_status + +/datum/mafia_role/warden/proc/night_start(datum/mafia_controller/game) + SIGNAL_HANDLER + + if(protection_status == WARDEN_WILL_LOCKDOWN) + to_chat(body,span_danger("Any and all visitors are going to eat buckshot tonight.")) + RegisterSignal(src,COMSIG_MAFIA_ON_VISIT,.proc/self_defense) + +/datum/mafia_role/warden/proc/night_end(datum/mafia_controller/game) + SIGNAL_HANDLER + + if(protection_status == WARDEN_WILL_LOCKDOWN) + charges-- + UnregisterSignal(src,COMSIG_MAFIA_ON_KILL) + to_chat(body,span_danger("You are no longer protected. You have used up your power.")) + protection_status = WARDEN_NOT_LOCKDOWN + +/datum/mafia_role/warden/proc/self_defense(datum/source,datum/mafia_controller/game,datum/mafia_role/attacker,lynch) + SIGNAL_HANDLER + + to_chat(body,span_userdanger("You have shot a visitor!")) + to_chat(attacker,span_userdanger("You have visited the warden!")) + attacker.kill(game, src, lynch = FALSE) + return MAFIA_VISIT_INTERRUPTED + +#undef WARDEN_NOT_LOCKDOWN +#undef WARDEN_WILL_LOCKDOWN + +///MAFIA ROLES/// they're the "anti-town" working to kill off townies to win /datum/mafia_role/mafia name = "Changeling" @@ -436,7 +663,7 @@ return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src /datum/mafia_role/mafia/thoughtfeeder/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target) - to_chat(body,"You will feast on the memories of [target.body.real_name] tonight.") + to_chat(body,span_warning("You will feast on the memories of [target.body.real_name] tonight.")) current_investigation = target /datum/mafia_role/mafia/thoughtfeeder/proc/investigate(datum/mafia_controller/game) @@ -444,18 +671,15 @@ var/datum/mafia_role/target = current_investigation current_investigation = null - if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"thoughtfeed",target) & MAFIA_PREVENT_ACTION) - to_chat(body,"You were unable to investigate [target.body.real_name].") + if(!target.can_action(game, src, "thought feeding")) add_note("N[game.turn] - [target.body.real_name] - Unable to investigate") return - if(target) - if(target.detect_immune) - to_chat(body,"[target.body.real_name]'s memories reveal that they are the Assistant.") - add_note("N[game.turn] - [target.body.real_name] - Assistant") - else - to_chat(body,"[target.body.real_name]'s memories reveal that they are the [target.name].") - add_note("N[game.turn] - [target.body.real_name] - [target.name]") - + if((target.role_flags & ROLE_UNDETECTABLE)) + to_chat(body,span_warning("[target.body.real_name]'s memories reveal that they are the Assistant.")) + add_note("N[game.turn] - [target.body.real_name] - Assistant") + else + to_chat(body,span_warning("[target.body.real_name]'s memories reveal that they are the [target.name].")) + add_note("N[game.turn] - [target.body.real_name] - [target.name]") ///SOLO ROLES/// they range from anomalous factors to deranged killers that try to win alone. @@ -465,15 +689,14 @@ win_condition = "kill everyone." team = MAFIA_TEAM_SOLO role_type = NEUTRAL_KILL + role_flags = ROLE_CAN_KILL winner_award = /datum/award/achievement/mafia/traitor - - targeted_actions = list("Night Kill") revealed_outfit = /datum/outfit/mafia/traitor - - hud_icon = "hudtraitor" revealed_icon = "traitor" + hud_icon = "hudtraitor" special_theme = "neutral" + targeted_actions = list("Night Kill") var/datum/mafia_role/current_victim /datum/mafia_role/traitor/New(datum/mafia_controller/game) @@ -481,17 +704,17 @@ RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/nightkill_immunity) RegisterSignal(game,COMSIG_MAFIA_NIGHT_KILL_PHASE,.proc/try_to_kill) -/datum/mafia_role/traitor/check_total_victory(alive_town, alive_mafia) //serial killers just want teams dead +/datum/mafia_role/traitor/check_total_victory(alive_town, alive_mafia) //serial killers just want teams dead, they cannot be stopped by killing roles anyways return alive_town + alive_mafia <= 1 /datum/mafia_role/traitor/block_team_victory(alive_town, alive_mafia) //no team can win until they're dead return TRUE //while alive, town AND mafia cannot win (though since mafia know who is who it's pretty easy to win from that point) -/datum/mafia_role/traitor/proc/nightkill_immunity(datum/source,datum/mafia_controller/game,lynch) +/datum/mafia_role/traitor/proc/nightkill_immunity(datum/source,datum/mafia_controller/game,datum/mafia_role/attacker,lynch) SIGNAL_HANDLER if(game.phase == MAFIA_PHASE_NIGHT && !lynch) - to_chat(body,"You were attacked, but they'll have to try harder than that to put you down.") + to_chat(body,span_userdanger("You were attacked, but they'll have to try harder than that to put you down.")) return MAFIA_PREVENT_KILL /datum/mafia_role/traitor/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target) @@ -504,25 +727,29 @@ /datum/mafia_role/traitor/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target) . = ..() current_victim = target - to_chat(body,"You will attempt to kill [target.body.real_name] tonight.") + to_chat(body,span_warning("You will attempt to kill [target.body.real_name] tonight.")) -/datum/mafia_role/traitor/proc/try_to_kill(datum/mafia_controller/source) - // SIGNAL_HANDLER +/datum/mafia_role/traitor/proc/try_to_kill(datum/mafia_controller/game) + SIGNAL_HANDLER + if(!current_victim) + return var/datum/mafia_role/target = current_victim current_victim = null - if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"traitor kill",target) & MAFIA_PREVENT_ACTION) + if(!target.can_action(game, src, "flickering")) //flickering a warden return - if(game_status == MAFIA_ALIVE && target && target.game_status == MAFIA_ALIVE) - if(!target.kill(source)) - to_chat(body,"Your attempt at killing [target.body] was prevented!") + if(game_status == MAFIA_ALIVE) + if(!target.kill(game,src,FALSE)) + to_chat(body,span_danger("Your attempt at killing [target.body.real_name] was prevented!")) + else + to_chat(target.body, span_userdanger("You have been killed by a Traitor!")) /datum/mafia_role/nightmare name = "Nightmare" - desc = "You're a solo monster that cannot be detected by detective roles. You can flicker lights of another room each night. You can instead decide to hunt, killing everyone in a flickering room. Kill everyone to win." + desc = "You're a solo monster that cannot be detected by detective roles. You can flicker lights of another room each night, becoming immune to attacks from those roles. You can instead decide to hunt, killing everyone in a flickering room. Kill everyone to win." win_condition = "kill everyone." revealed_outfit = /datum/outfit/mafia/nightmare - detect_immune = TRUE + role_flags = ROLE_UNDETECTABLE | ROLE_CAN_KILL team = MAFIA_TEAM_SOLO role_type = NEUTRAL_KILL special_theme = "neutral" @@ -536,6 +763,7 @@ /datum/mafia_role/nightmare/New(datum/mafia_controller/game) . = ..() + RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/flickering_immunity) RegisterSignal(game,COMSIG_MAFIA_NIGHT_KILL_PHASE,.proc/flicker_or_hunt) /datum/mafia_role/nightmare/check_total_victory(alive_town, alive_mafia) //nightmares just want teams dead @@ -562,33 +790,42 @@ /datum/mafia_role/nightmare/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target) . = ..() if(target == flicker_target) - to_chat(body,"You will do nothing tonight.") + to_chat(body,span_warning("You will do nothing tonight.")) flicker_target = null flicker_target = target if(action == "Flicker") - to_chat(body,"You will attempt to flicker [target.body.real_name]'s room tonight.") + to_chat(body,span_warning("You will attempt to flicker [target.body.real_name]'s room tonight.")) else - to_chat(body,"You will hunt everyone in a flickering room down tonight.") + to_chat(body,span_danger("You will hunt everyone in a flickering room down tonight.")) -/datum/mafia_role/nightmare/proc/flicker_or_hunt(datum/mafia_controller/source) - // SIGNAL_HANDLER +/datum/mafia_role/nightmare/proc/flickering_immunity(datum/source,datum/mafia_controller/game,datum/mafia_role/attacker,lynch) + SIGNAL_HANDLER + if(!attacker) + return //no chance man, that's a town lynch - if(game_status != MAFIA_ALIVE || !flicker_target) - return - if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"nightmare actions",flicker_target) & MAFIA_PREVENT_ACTION) - to_chat(flicker_target.body, "Your actions were prevented!") + if(attacker in flickering) + to_chat(body,span_userdanger("You were attacked by someone in a flickering room. You have danced in the shadows, evading them.")) + return MAFIA_PREVENT_KILL + +/datum/mafia_role/nightmare/proc/flicker_or_hunt(datum/mafia_controller/game) + SIGNAL_HANDLER + + if(!flicker_target) return var/datum/mafia_role/target = flicker_target flicker_target = null + if(!target.can_action(game, src, "flickering")) //flickering a warden + return + if(target != src) //flicker instead of hunt - to_chat(target.body, "The lights begin to flicker and dim. You're in danger.") + to_chat(target.body, span_userdanger("The lights begin to flicker and dim. You're in danger.")) flickering += target return for(var/r in flickering) var/datum/mafia_role/role = r if(role && role.game_status == MAFIA_ALIVE) - to_chat(role.body, "A shadowy monster appears out of the darkness!") - role.kill(source) + to_chat(role.body, span_userdanger("A shadowy figure appears out of the darkness!")) + role.kill(game,src,FALSE) flickering -= role //just helps read better @@ -599,7 +836,6 @@ name = "Fugitive" desc = "You're on the run. You can become immune to night kills exactly twice, and you win by surviving to the end of the game with anyone." win_condition = "survive to the end of the game, with anyone" - solo_counts_as_town = TRUE //should not count towards mafia victory, they should have the option to work with town revealed_outfit = /datum/outfit/mafia/fugitive team = MAFIA_TEAM_SOLO role_type = NEUTRAL_DISRUPT @@ -622,22 +858,22 @@ /datum/mafia_role/fugitive/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target) . = ..() if(!charges) - to_chat(body,"You're out of supplies and cannot protect yourself anymore.") + to_chat(body,span_danger("You're out of supplies and cannot protect yourself anymore.")) return if(game.phase == MAFIA_PHASE_NIGHT) - to_chat(body,"You don't have time to prepare, night has already arrived.") + to_chat(body,span_danger("You don't have time to prepare, night has already arrived.")) return if(protection_status == FUGITIVE_WILL_PRESERVE) - to_chat(body,"You decide to not prepare tonight.") + to_chat(body,span_danger("You decide to not prepare tonight.")) else - to_chat(body,"You decide to prepare for a horrible night.") + to_chat(body,span_danger("You decide to prepare for a horrible night.")) protection_status = !protection_status /datum/mafia_role/fugitive/proc/night_start(datum/mafia_controller/game) SIGNAL_HANDLER if(protection_status == FUGITIVE_WILL_PRESERVE) - to_chat(body,"Your preparations are complete. Nothing could kill you tonight!") + to_chat(body,span_danger("Your preparations are complete. Nothing could kill you tonight!")) RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/prevent_death) /datum/mafia_role/fugitive/proc/night_end(datum/mafia_controller/game) @@ -646,13 +882,13 @@ if(protection_status == FUGITIVE_WILL_PRESERVE) charges-- UnregisterSignal(src,COMSIG_MAFIA_ON_KILL) - to_chat(body,"You are no longer protected. You have [charges] use[charges == 1 ? "" : "s"] left of your power.") + to_chat(body,span_danger("You are no longer protected. You have [charges] use[charges == 1 ? "" : "s"] left of your power.")) protection_status = FUGITIVE_NOT_PRESERVING -/datum/mafia_role/fugitive/proc/prevent_death(datum/mafia_controller/game) +/datum/mafia_role/fugitive/proc/prevent_death(datum/source,datum/mafia_controller/game,datum/mafia_role/attacker,lynch) SIGNAL_HANDLER - to_chat(body,"You were attacked! Luckily, you were ready for this!") + to_chat(body,span_userdanger("You were attacked! Luckily, you were ready for this!")) return MAFIA_PREVENT_KILL /datum/mafia_role/fugitive/proc/survived(datum/mafia_controller/game) @@ -669,8 +905,7 @@ name = "Obsessed" desc = "You're completely lost in your own mind. You win by lynching your obsession before you get killed in this mess. Obsession assigned on the first night!" win_condition = "lynch their obsession." - revealed_outfit = /datum/outfit/mafia/obsessed // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these) - solo_counts_as_town = TRUE //after winning or whatever, can side with whoever. they've already done their objective! + revealed_outfit = /datum/outfit/mafia/obsessed team = MAFIA_TEAM_SOLO role_type = NEUTRAL_DISRUPT special_theme = "neutral" @@ -678,9 +913,6 @@ revealed_icon = "obsessed" winner_award = /datum/award/achievement/mafia/obsessed - - revealed_outfit = /datum/outfit/mafia/obsessed // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these) - solo_counts_as_town = TRUE //after winning or whatever, can side with whoever. they've already done their objective! var/datum/mafia_role/obsession var/lynched_target = FALSE @@ -700,12 +932,12 @@ if(!obsession) obsession = pick(all_roles_shuffle) //okay no town just pick anyone here //if you still don't have an obsession you're playing a single player game like i can't help your dumb ass - to_chat(body, "Your obsession is [obsession.body.real_name]! Get them lynched to win!") + to_chat(body, span_userdanger("Your obsession is [obsession.body.real_name]! Get them lynched to win!")) add_note("N[game.turn] - I vowed to watch my obsession, [obsession.body.real_name], hang!") //it'll always be N1 but whatever RegisterSignal(obsession,COMSIG_MAFIA_ON_KILL,.proc/check_victory) UnregisterSignal(game,COMSIG_MAFIA_SUNDOWN) -/datum/mafia_role/obsessed/proc/check_victory(datum/source,datum/mafia_controller/game,lynch) +/datum/mafia_role/obsessed/proc/check_victory(datum/source,datum/mafia_controller/game,datum/mafia_role/attacker,lynch) SIGNAL_HANDLER UnregisterSignal(source,COMSIG_MAFIA_ON_KILL) @@ -716,14 +948,13 @@ game.award_role(winner_award, src) reveal_role(game, FALSE) else - to_chat(body, "You have failed your objective to lynch [obsession.body]!") + to_chat(body, span_userdanger("You have failed your objective to lynch [obsession.body.real_name]!")) /datum/mafia_role/clown name = "Clown" desc = "If you are lynched you take down one of your voters (guilty or abstain) with you and win. HONK!" win_condition = "get themselves lynched!" revealed_outfit = /datum/outfit/mafia/clown - solo_counts_as_town = TRUE team = MAFIA_TEAM_SOLO role_type = NEUTRAL_DISRUPT special_theme = "neutral" @@ -735,8 +966,8 @@ . = ..() RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/prank) -/datum/mafia_role/clown/proc/prank(datum/source,datum/mafia_controller/game,lynch) - // SIGNAL_HANDLER +/datum/mafia_role/clown/proc/prank(datum/source,datum/mafia_controller/game,datum/mafia_role/attacker,lynch) + SIGNAL_HANDLER if(lynch) var/datum/mafia_role/victim = pick(game.judgement_guilty_votes + game.judgement_abstain_votes)