hewwo have you tried to work?
This commit is contained in:
@@ -388,3 +388,9 @@
|
||||
Insert("polycrystal", 'icons/obj/telescience.dmi', "polycrystal")
|
||||
..()
|
||||
|
||||
/datum/asset/spritesheet/mafia
|
||||
name = "mafia"
|
||||
|
||||
/datum/asset/spritesheet/mafia/register()
|
||||
InsertAll("", 'icons/obj/mafia.dmi')
|
||||
..()
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
GLOBAL_LIST_EMPTY(mafia_signup)
|
||||
//the current global mafia game running.
|
||||
GLOBAL_VAR(mafia_game)
|
||||
/// list of ghosts who want to play mafia that have since disconnected. They are kept in the lobby, but not counted for starting a game.
|
||||
GLOBAL_LIST_EMPTY(mafia_bad_signup)
|
||||
|
||||
GLOBAL_LIST_INIT(mafia_setups,generate_mafia_setups())
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* It is first created when the first ghost signs up to play.
|
||||
*/
|
||||
/datum/mafia_controller
|
||||
///list of observers that should get game updates.
|
||||
var/list/spectators = list()
|
||||
///all roles in the game, dead or alive. check their game status if you only want living or dead.
|
||||
var/list/all_roles = list()
|
||||
///exists to speed up role retrieval, it's a dict. player_role_lookup[player ckey] will give you the role they play
|
||||
@@ -48,7 +50,8 @@
|
||||
|
||||
///group voting on one person, like putting people to trial or choosing who to kill as mafia
|
||||
var/list/votes = list()
|
||||
///and these (judgement_innocent_votes and judgement_guilty_votes) are the judgement phase votes, aka people sorting themselves into guilty and innocent lists. whichever has more wins!
|
||||
///and these (judgement_innocent_votes, judgement_abstain_votes and judgement_guilty_votes) are the judgement phase votes, aka people sorting themselves into guilty and innocent, and "eh, i don't really care" lists. whichever has more inno or guilty wins!
|
||||
var/list/judgement_abstain_votes = list()
|
||||
var/list/judgement_innocent_votes = list()
|
||||
var/list/judgement_guilty_votes = list()
|
||||
///current role on trial for the judgement phase, will die if guilty is greater than innocent
|
||||
@@ -129,7 +132,7 @@
|
||||
var/team_suffix = team ? "([uppertext(team)] CHAT)" : ""
|
||||
for(var/M in GLOB.dead_mob_list)
|
||||
var/mob/spectator = M
|
||||
if(spectator.ckey in GLOB.mafia_signup || player_role_lookup[spectator.mind.current] != null) //was in current game, or is signed up
|
||||
if(spectator.ckey in spectators) //was in current game, or spectatin' (won't send to living)
|
||||
var/link = FOLLOW_LINK(M, town_center_landmark)
|
||||
to_chat(M, "[link] MAFIA: [msg] [team_suffix]")
|
||||
|
||||
@@ -190,8 +193,19 @@
|
||||
*/
|
||||
/datum/mafia_controller/proc/check_trial(verbose = TRUE)
|
||||
var/datum/mafia_role/loser = get_vote_winner("Day")//, majority_of_town = TRUE)
|
||||
var/loser_votes = get_vote_count(loser,"Day")
|
||||
if(loser)
|
||||
// if(loser_votes > 12)
|
||||
// loser.body.client?.give_award(/datum/award/achievement/mafia/universally_hated, loser.body)
|
||||
send_message("<b>[loser.body.real_name] wins the day vote, Listen to their defense and vote \"INNOCENT\" or \"GUILTY\"!</b>")
|
||||
//refresh the lists
|
||||
judgement_abstain_votes = list()
|
||||
judgement_innocent_votes = list()
|
||||
judgement_guilty_votes = list()
|
||||
for(var/i in all_roles)
|
||||
var/datum/mafia_role/abstainee = i
|
||||
if(abstainee.game_status == MAFIA_ALIVE && abstainee != loser)
|
||||
judgement_abstain_votes += abstainee
|
||||
on_trial = loser
|
||||
on_trial.body.forceMove(get_turf(town_center_landmark))
|
||||
phase = MAFIA_PHASE_JUDGEMENT
|
||||
@@ -215,8 +229,11 @@
|
||||
for(var/i in judgement_innocent_votes)
|
||||
var/datum/mafia_role/role = i
|
||||
send_message("<span class='green'>[role.body.real_name] voted innocent.</span>")
|
||||
for(var/ii in judgement_guilty_votes)
|
||||
for(var/ii in judgement_abstain_votes)
|
||||
var/datum/mafia_role/role = ii
|
||||
send_message("<span class='comradio'>[role.body.real_name] abstained.</span>")
|
||||
for(var/iii in judgement_guilty_votes)
|
||||
var/datum/mafia_role/role = iii
|
||||
send_message("<span class='red'>[role.body.real_name] voted guilty.</span>")
|
||||
if(judgement_guilty_votes.len > judgement_innocent_votes.len) //strictly need majority guilty to lynch
|
||||
send_message("<span class='red'><b>Guilty wins majority, [on_trial.body.real_name] has been lynched.</b></span>")
|
||||
@@ -225,9 +242,6 @@
|
||||
else
|
||||
send_message("<span class='green'><b>Innocent wins majority, [on_trial.body.real_name] has been spared.</b></span>")
|
||||
on_trial.body.forceMove(get_turf(on_trial.assigned_landmark))
|
||||
//by now clowns should have killed someone in guilty list, clear this out
|
||||
judgement_innocent_votes = list()
|
||||
judgement_guilty_votes = list()
|
||||
on_trial = null
|
||||
//day votes are already cleared, so this will skip the trial and check victory/lockdown/whatever else
|
||||
next_phase_timer = addtimer(CALLBACK(src, .proc/check_trial, FALSE),judgement_lynch_period,TIMER_STOPPABLE)// small pause to see the guy dead, no verbosity since we already did this
|
||||
@@ -320,10 +334,8 @@
|
||||
/**
|
||||
* Cleans up the game, resetting variables back to the beginning and removing the map with the generator.
|
||||
*/
|
||||
/datum/mafia_controller/proc/end_game()
|
||||
|
||||
/datum/mafia_controller/proc/end_game(
|
||||
map_deleter.generate() //remove the map, it will be loaded at the start of the next one
|
||||
|
||||
QDEL_LIST(all_roles)
|
||||
turn = 0
|
||||
votes = list()
|
||||
@@ -481,7 +493,7 @@
|
||||
if(phase != MAFIA_PHASE_VOTING)
|
||||
return
|
||||
var/v = get_vote_count(player_role_lookup[source],"Day")
|
||||
var/mutable_appearance/MA = mutable_appearance('icons/obj/mafia.dmi',"vote_[v]")
|
||||
var/mutable_appearance/MA = mutable_appearance('icons/obj/mafia.dmi',"vote_[v > 12 ? "over_12" : v]")
|
||||
overlay_list += MA
|
||||
|
||||
/**
|
||||
@@ -528,13 +540,26 @@
|
||||
.["judgement_phase"] = FALSE
|
||||
var/datum/mafia_role/user_role = player_role_lookup[user]
|
||||
if(user_role)
|
||||
.["roleinfo"] = list("role" = user_role.name,"desc" = user_role.desc, "action_log" = user_role.role_notes)
|
||||
.["roleinfo"] = list("role" = user_role.name,"desc" = user_role.desc, "action_log" = user_role.role_notes, "hud_icon" = user_role.hud_icon, "revealed_icon" = user_role.revealed_icon)
|
||||
var/actions = list()
|
||||
for(var/action in user_role.actions)
|
||||
if(user_role.validate_action_target(src,action,null))
|
||||
actions += action
|
||||
.["actions"] = actions
|
||||
.["role_theme"] = user_role.special_theme
|
||||
else
|
||||
var/list/lobby_data = list()
|
||||
for(var/key in GLOB.mafia_signup + GLOB.mafia_bad_signup)
|
||||
var/list/lobby_member = list()
|
||||
lobby_member["name"] = key
|
||||
lobby_member["status"] = "Ready"
|
||||
if(key in GLOB.mafia_bad_signup)
|
||||
lobby_member["status"] = "Disconnected"
|
||||
lobby_member["spectating"] = "Ghost"
|
||||
if(key in spectators)
|
||||
lobby_member["spectating"] = "Spectator"
|
||||
lobby_data += list(lobby_member)
|
||||
.["lobbydata"] = lobby_data
|
||||
var/list/player_data = list()
|
||||
for(var/datum/mafia_role/R in all_roles)
|
||||
var/list/player_info = list()
|
||||
@@ -561,6 +586,11 @@
|
||||
//Not sure on this, should this info be visible
|
||||
.["all_roles"] = current_setup_text
|
||||
|
||||
/datum/mafia_controller/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/mafia),
|
||||
)
|
||||
|
||||
/datum/mafia_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
@@ -606,7 +636,31 @@
|
||||
helper = role
|
||||
break
|
||||
helper.show_help(usr)
|
||||
if(!user_role || user_role.game_status == MAFIA_DEAD)//ghosts, dead people?
|
||||
if(!user_role)//just the dead
|
||||
var/client/C = ui.user.client
|
||||
switch(action)
|
||||
if("mf_signup")
|
||||
if(!SSticker.HasRoundStarted())
|
||||
to_chat(usr, "<span class='warning'>Wait for the round to start.</span>")
|
||||
return
|
||||
if(GLOB.mafia_signup[C.ckey])
|
||||
GLOB.mafia_signup -= C.ckey
|
||||
to_chat(usr, "<span class='notice'>You unregister from Mafia.</span>")
|
||||
return
|
||||
else
|
||||
GLOB.mafia_signup[C.ckey] = C
|
||||
to_chat(usr, "<span class='notice'>You sign up for Mafia.</span>")
|
||||
if(phase == MAFIA_PHASE_SETUP)
|
||||
check_signups()
|
||||
try_autostart()
|
||||
if("mf_spectate")
|
||||
if(C.ckey in spectators)
|
||||
to_chat(usr, "<span class='notice'>You will no longer get messages from the game.</span>")
|
||||
spectators -= C.ckey
|
||||
else
|
||||
to_chat(usr, "<span class='notice'>You will now get messages from the game.</span>")
|
||||
spectators += C.ckey
|
||||
if(user_role.game_status == MAFIA_DEAD)
|
||||
return
|
||||
var/self_voting = user_role == on_trial ? TRUE : FALSE //used to block people from voting themselves innocent or guilty
|
||||
//User actions
|
||||
@@ -637,20 +691,29 @@
|
||||
return
|
||||
user_role.handle_action(src,params["atype"],target)
|
||||
return TRUE
|
||||
if("vote_innocent")
|
||||
if(phase != MAFIA_PHASE_JUDGEMENT && !self_voting)
|
||||
return
|
||||
to_chat(user_role.body,"Your vote on [on_trial.body.real_name] submitted as INNOCENT!")
|
||||
judgement_innocent_votes -= user_role//no double voting
|
||||
judgement_guilty_votes -= user_role//no radical centrism
|
||||
judgement_innocent_votes += user_role
|
||||
if("vote_guilty")
|
||||
if(phase != MAFIA_PHASE_JUDGEMENT && !self_voting)
|
||||
return
|
||||
to_chat(user_role.body,"Your vote on [on_trial.body.real_name] submitted as GUILTY!")
|
||||
judgement_innocent_votes -= user_role//no radical centrism
|
||||
judgement_guilty_votes -= user_role//no double voting
|
||||
judgement_guilty_votes += user_role
|
||||
if(user_role != on_trial)
|
||||
switch(action)
|
||||
if("vote_abstain")
|
||||
if(phase != MAFIA_PHASE_JUDGEMENT || (user_role in judgement_abstain_votes))
|
||||
return
|
||||
to_chat(user_role.body,"You have decided to abstain.")
|
||||
judgement_innocent_votes -= user_role
|
||||
judgement_guilty_votes -= user_role
|
||||
judgement_abstain_votes += user_role
|
||||
if("vote_innocent")
|
||||
if(phase != MAFIA_PHASE_JUDGEMENT || (user_role in judgement_innocent_votes))
|
||||
return
|
||||
to_chat(user_role.body,"Your vote on [on_trial.body.real_name] submitted as INNOCENT!")
|
||||
judgement_abstain_votes -= user_role//no fakers, and...
|
||||
judgement_guilty_votes -= user_role//no radical centrism
|
||||
judgement_innocent_votes += user_role
|
||||
if("vote_guilty")
|
||||
if(phase != MAFIA_PHASE_JUDGEMENT || (user_role in judgement_guilty_votes))
|
||||
return
|
||||
to_chat(user_role.body,"Your vote on [on_trial.body.real_name] submitted as GUILTY!")
|
||||
judgement_abstain_votes -= user_role//no fakers, and...
|
||||
judgement_innocent_votes -= user_role//no radical centrism
|
||||
judgement_guilty_votes += user_role
|
||||
|
||||
/datum/mafia_controller/ui_state(mob/user)
|
||||
return GLOB.always_state
|
||||
@@ -699,7 +762,7 @@
|
||||
//cuts invalid players from signups (disconnected/not a ghost)
|
||||
var/list/possible_keys = list()
|
||||
for(var/key in GLOB.mafia_signup)
|
||||
if(GLOB.directory[key] && GLOB.directory[key] == GLOB.mafia_signup[key])
|
||||
if(GLOB.directory[key])
|
||||
var/client/C = GLOB.directory[key]
|
||||
if(isobserver(C.mob))
|
||||
possible_keys += key
|
||||
@@ -739,6 +802,25 @@
|
||||
if(GLOB.mafia_signup.len >= min_players)//enough people to try and make something
|
||||
basic_setup()
|
||||
|
||||
/**
|
||||
* Filters inactive player into a different list until they reconnect, and removes players who are no longer ghosts.
|
||||
*
|
||||
* If a disconnected player gets a non-ghost mob and reconnects, they will be first put back into mafia_signup then filtered by that.
|
||||
*/
|
||||
/datum/mafia_controller/proc/check_signups()
|
||||
for(var/bad_key in GLOB.mafia_bad_signup)
|
||||
if(GLOB.directory[bad_key])//they have reconnected if we can search their key and get a client
|
||||
GLOB.mafia_bad_signup -= bad_key
|
||||
GLOB.mafia_signup += bad_key
|
||||
for(var/key in GLOB.mafia_signup)
|
||||
var/client/C = GLOB.directory[key]
|
||||
if(!C)//vice versa but in a variable we use later
|
||||
GLOB.mafia_signup -= key
|
||||
GLOB.mafia_bad_signup += key
|
||||
if(!isobserver(C.mob))
|
||||
//they are back to playing the game, remove them from the signups
|
||||
GLOB.mafia_signup -= key
|
||||
|
||||
/datum/action/innate/mafia_panel
|
||||
name = "Mafia Panel"
|
||||
desc = "Use this to play."
|
||||
|
||||
@@ -19,7 +19,12 @@
|
||||
|
||||
var/game_status = MAFIA_ALIVE
|
||||
|
||||
var/special_theme //set this to something cool for antagonists and their window will look different
|
||||
///icon state in the mafia dmi of the hud of the role, used in the mafia ui
|
||||
var/hud_icon = "hudassistant"
|
||||
///icon state in the mafia dmi of the hud of the role, used in the mafia ui
|
||||
var/revealed_icon = "assistant"
|
||||
///set this to something cool for antagonists and their window will look different
|
||||
var/special_theme
|
||||
|
||||
var/list/role_notes = list()
|
||||
|
||||
@@ -34,6 +39,8 @@
|
||||
body.death()
|
||||
if(lynch)
|
||||
reveal_role(game, verbose = TRUE)
|
||||
if(!(player_key in game.spectators)) //people who played will want to see the end of the game more often than not
|
||||
game.spectators += player_key
|
||||
return TRUE
|
||||
|
||||
/datum/mafia_role/Destroy(force, ...)
|
||||
@@ -108,6 +115,9 @@
|
||||
desc = "You can investigate a single person each night to learn their team."
|
||||
revealed_outfit = /datum/outfit/mafia/detective
|
||||
|
||||
hud_icon = "huddetective"
|
||||
revealed_icon = "detective"
|
||||
|
||||
targeted_actions = list("Investigate")
|
||||
|
||||
var/datum/mafia_role/current_investigation
|
||||
@@ -152,7 +162,8 @@
|
||||
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)
|
||||
|
||||
hud_icon = "hudmedicaldoctor"
|
||||
revealed_icon = "medicaldoctor"
|
||||
targeted_actions = list("Protect")
|
||||
|
||||
var/datum/mafia_role/current_protected
|
||||
@@ -194,7 +205,8 @@
|
||||
name = "Chaplain"
|
||||
desc = "You can communicate with spirits of the dead each night to discover dead crewmember roles."
|
||||
revealed_outfit = /datum/outfit/mafia/chaplain
|
||||
|
||||
hud_icon = "hudchaplain"
|
||||
revealed_icon = "chaplain"
|
||||
targeted_actions = list("Pray")
|
||||
var/current_target
|
||||
|
||||
@@ -222,8 +234,9 @@
|
||||
/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
|
||||
hud_icon = "hudlawyer"
|
||||
revealed_icon = "lawyer"
|
||||
targeted_actions = list("Advise")
|
||||
|
||||
var/datum/mafia_role/current_target
|
||||
@@ -278,6 +291,9 @@
|
||||
desc = "You can visit someone ONCE PER GAME to reveal their true role in the morning!"
|
||||
revealed_outfit = /datum/outfit/mafia/psychologist
|
||||
|
||||
hud_icon = "hudpsychologist"
|
||||
revealed_icon = "psychologist"
|
||||
|
||||
targeted_actions = list("Reveal")
|
||||
var/datum/mafia_role/current_target
|
||||
var/can_use = TRUE
|
||||
@@ -313,6 +329,8 @@
|
||||
desc = "You're a member of the changeling hive. Use ':j' talk prefix to talk to your fellow lings."
|
||||
team = MAFIA_TEAM_MAFIA
|
||||
revealed_outfit = /datum/outfit/mafia/changeling
|
||||
hud_icon = "hudchangeling"
|
||||
revealed_icon = "changeling"
|
||||
special_theme = "syndicate"
|
||||
win_condition = "become majority over the town and no solo killing role can stop them."
|
||||
|
||||
@@ -332,7 +350,10 @@
|
||||
team = MAFIA_TEAM_SOLO
|
||||
targeted_actions = list("Night Kill")
|
||||
revealed_outfit = /datum/outfit/mafia/traitor
|
||||
special_theme = "syndicate"
|
||||
|
||||
hud_icon = "hudtraitor"
|
||||
revealed_icon = "traitor"
|
||||
special_theme = "neutral"
|
||||
|
||||
var/datum/mafia_role/current_victim
|
||||
|
||||
@@ -384,6 +405,9 @@
|
||||
var/protection_status = FUGITIVE_NOT_PRESERVING
|
||||
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
|
||||
special_theme = "neutral"
|
||||
hud_icon = "hudfugitive"
|
||||
revealed_icon = "fugitive"
|
||||
|
||||
/datum/mafia_role/fugitive/New(datum/mafia_controller/game)
|
||||
. = ..()
|
||||
@@ -434,7 +458,9 @@
|
||||
win_condition = "lynch their obsession."
|
||||
team = MAFIA_TEAM_SOLO
|
||||
revealed_outfit = /datum/outfit/mafia/obsessed // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these)
|
||||
|
||||
special_theme = "neutral"
|
||||
hud_icon = "hudobsessed"
|
||||
revealed_icon = "obsessed"
|
||||
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
|
||||
@@ -470,10 +496,14 @@
|
||||
|
||||
/datum/mafia_role/clown
|
||||
name = "Clown"
|
||||
desc = "If you are lynched you take down one of your voters with you and win. HONK!"
|
||||
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
|
||||
special_theme = "neutral"
|
||||
hud_icon = "hudclown"
|
||||
revealed_icon = "clown"
|
||||
|
||||
/datum/mafia_role/clown/New(datum/mafia_controller/game)
|
||||
. = ..()
|
||||
@@ -481,7 +511,7 @@
|
||||
|
||||
/datum/mafia_role/clown/proc/prank(datum/source,datum/mafia_controller/game,lynch)
|
||||
if(lynch)
|
||||
var/datum/mafia_role/victim = pick(game.judgement_guilty_votes)
|
||||
var/datum/mafia_role/victim = pick(game.judgement_guilty_votes + game.judgement_abstain_votes)
|
||||
game.send_message("<span class='big clown'>[body.real_name] WAS A CLOWN! HONK! They take down [victim.body.real_name] with their last prank.</span>")
|
||||
game.send_message("<span class='big clown'>!! CLOWN VICTORY !!</span>")
|
||||
victim.kill(game,FALSE)
|
||||
|
||||
@@ -905,6 +905,22 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
else
|
||||
to_chat(usr, "Can't become a pAI candidate while not dead!")
|
||||
|
||||
/mob/dead/observer/verb/mafia_game_signup()
|
||||
set category = "Ghost"
|
||||
set name = "Signup for Mafia"
|
||||
set desc = "Sign up for a game of Mafia to pass the time while dead."
|
||||
mafia_signup()
|
||||
/mob/dead/observer/proc/mafia_signup()
|
||||
if(!client)
|
||||
return
|
||||
if(!isobserver(src))
|
||||
to_chat(usr, "<span class='warning'>You must be a ghost to join mafia!</span>")
|
||||
return
|
||||
var/datum/mafia_controller/game = GLOB.mafia_game //this needs to change if you want multiple mafia games up at once.
|
||||
if(!game)
|
||||
game = create_mafia_game("mafia")
|
||||
game.ui_interact(usr)
|
||||
|
||||
/mob/dead/observer/CtrlShiftClick(mob/user)
|
||||
if(isobserver(user) && check_rights(R_SPAWN))
|
||||
change_mob_type( /mob/living/carbon/human , null, null, TRUE) //always delmob, ghosts shouldn't be left lingering
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 668 B After Width: | Height: | Size: 13 KiB |
3
tgui/packages/tgui/assets/bg-neutral.svg
Normal file
3
tgui/packages/tgui/assets/bg-neutral.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="user-secret" class="svg-inline--fa fa-user-secret fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" opacity=".33">
|
||||
<path fill="currentColor" d="M383.9 308.3l23.9-62.6c4-10.5-3.7-21.7-15-21.7h-58.5c11-18.9 17.8-40.6 17.8-64v-.3c39.2-7.8 64-19.1 64-31.7 0-13.3-27.3-25.1-70.1-33-9.2-32.8-27-65.8-40.6-82.8-9.5-11.9-25.9-15.6-39.5-8.8l-27.6 13.8c-9 4.5-19.6 4.5-28.6 0L182.1 3.4c-13.6-6.8-30-3.1-39.5 8.8-13.5 17-31.4 50-40.6 82.8-42.7 7.9-70 19.7-70 33 0 12.6 24.8 23.9 64 31.7v.3c0 23.4 6.8 45.1 17.8 64H56.3c-11.5 0-19.2 11.7-14.7 22.3l25.8 60.2C27.3 329.8 0 372.7 0 422.4v44.8C0 491.9 20.1 512 44.8 512h358.4c24.7 0 44.8-20.1 44.8-44.8v-44.8c0-48.4-25.8-90.4-64.1-114.1zM176 480l-41.6-192 49.6 32 24 40-32 120zm96 0l-32-120 24-40 49.6-32L272 480zm41.7-298.5c-3.9 11.9-7 24.6-16.5 33.4-10.1 9.3-48 22.4-64-25-2.8-8.4-15.4-8.4-18.3 0-17 50.2-56 32.4-64 25-9.5-8.8-12.7-21.5-16.5-33.4-.8-2.5-6.3-5.7-6.3-5.8v-10.8c28.3 3.6 61 5.8 96 5.8s67.7-2.1 96-5.8v10.8c-.1.1-5.6 3.2-6.4 5.8z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -22,6 +22,7 @@ import './styles/themes/abductor.scss';
|
||||
import './styles/themes/cardtable.scss';
|
||||
import './styles/themes/hackerman.scss';
|
||||
import './styles/themes/malfunction.scss';
|
||||
import './styles/themes/neutral.scss';
|
||||
import './styles/themes/ntos.scss';
|
||||
import './styles/themes/paper.scss';
|
||||
import './styles/themes/retro.scss';
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { classes } from 'common/react';
|
||||
import { Fragment } from 'inferno';
|
||||
import { multiline } from 'common/string';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, LabeledList, Section, TimeDisplay } from '../components';
|
||||
import { Box, Button, Collapsible, Flex, NoticeBox, Section, TimeDisplay, Tooltip } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const MafiaPanel = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
lobbydata,
|
||||
players,
|
||||
actions,
|
||||
phase,
|
||||
@@ -16,29 +19,124 @@ export const MafiaPanel = (props, context) => {
|
||||
timeleft,
|
||||
all_roles,
|
||||
} = data;
|
||||
const playerAddedHeight = roleinfo ? players.length * 30 : 7;
|
||||
const readyGhosts = lobbydata ? lobbydata.filter(
|
||||
player => player.status === "Ready") : null;
|
||||
return (
|
||||
<Window
|
||||
title="Mafia"
|
||||
theme={role_theme}
|
||||
width={650}
|
||||
height={550}
|
||||
resizable>
|
||||
<Window.Content>
|
||||
<Section title={phase}>
|
||||
{!!roleinfo && (
|
||||
<Fragment>
|
||||
<Box>
|
||||
<TimeDisplay auto="down" value={timeleft} />
|
||||
width={650} // 414 or 415 / 444 or 445
|
||||
height={293 + playerAddedHeight}>
|
||||
<Window.Content scrollable={admin_controls}>
|
||||
{!roleinfo && (
|
||||
<Flex scrollable
|
||||
overflowY="scroll"
|
||||
direction="column"
|
||||
height="100%"
|
||||
grow={1}>
|
||||
<Section
|
||||
title="Lobby"
|
||||
mb={1}
|
||||
buttons={
|
||||
<LobbyDisplay
|
||||
phase={phase}
|
||||
timeleft={timeleft}
|
||||
admin_controls={admin_controls} />
|
||||
}>
|
||||
<Box textAlign="center">
|
||||
<NoticeBox info>
|
||||
The lobby currently has {readyGhosts.length}
|
||||
/12 valid players signed up.
|
||||
</NoticeBox>
|
||||
<Flex
|
||||
direction="column">
|
||||
{!!lobbydata && lobbydata.map(lobbyist => (
|
||||
<Flex.Item
|
||||
key={lobbyist}
|
||||
basis={2}
|
||||
className="Section__title candystripe">
|
||||
<Flex
|
||||
height={2}
|
||||
align="center"
|
||||
justify="space-between">
|
||||
<Flex.Item basis={0}>
|
||||
{lobbyist.name}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
STATUS:
|
||||
</Flex.Item>
|
||||
<Flex.Item width="30%">
|
||||
<Section>
|
||||
<Box
|
||||
color={
|
||||
lobbyist.status === "Ready" ? "green" : "red"
|
||||
}
|
||||
textAlign="center">
|
||||
{lobbyist.status} {lobbyist.spectating}
|
||||
</Box>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Section>
|
||||
</Flex>
|
||||
)}
|
||||
{!!roleinfo && (
|
||||
<Section
|
||||
title={phase}
|
||||
minHeight="100px"
|
||||
maxHeight="50px"
|
||||
buttons={
|
||||
<Box>
|
||||
<b>You are a {roleinfo.role}</b>
|
||||
{!!admin_controls && (
|
||||
<Button
|
||||
color="red"
|
||||
icon="gavel"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltip={multiline`
|
||||
Hello admin! If it is the admin controls you seek,
|
||||
please notice the extra scrollbar you have that players
|
||||
do not!`}
|
||||
/>
|
||||
)} <TimeDisplay auto="down" value={timeleft} />
|
||||
</Box>
|
||||
<Box>
|
||||
}>
|
||||
<Flex
|
||||
justify="space-between">
|
||||
<Flex.Item
|
||||
align="center"
|
||||
textAlign="center"
|
||||
maxWidth="500px">
|
||||
<b>You are the {roleinfo.role}</b><br />
|
||||
<b>{roleinfo.desc}</b>
|
||||
</Box>
|
||||
</Fragment>
|
||||
)}
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Box
|
||||
className={classes([
|
||||
'mafia32x32',
|
||||
roleinfo.revealed_icon,
|
||||
])}
|
||||
style={{
|
||||
'transform': 'scale(2) translate(0px, 10%)',
|
||||
'vertical-align': 'middle',
|
||||
}} />
|
||||
<Box
|
||||
className={classes([
|
||||
'mafia32x32',
|
||||
roleinfo.hud_icon,
|
||||
])}
|
||||
style={{
|
||||
'transform': 'scale(2) translate(-5px, -5px)',
|
||||
'vertical-align': 'middle',
|
||||
}} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
)}
|
||||
<Flex>
|
||||
{!!actions && actions.map(action => (
|
||||
<Flex.Item key={action}>
|
||||
@@ -49,116 +147,323 @@ export const MafiaPanel = (props, context) => {
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
{!!admin_controls && (
|
||||
{!!roleinfo && (
|
||||
<Section
|
||||
title="ADMIN CONTROLS"
|
||||
backgroundColor="red">
|
||||
THESE ARE DEBUG, THEY WILL BREAK THE GAME, DO NOT TOUCH <br />
|
||||
Also because an admin did it: do not gib/delete/etc
|
||||
anyone! It will runtime the game to death! <br />
|
||||
<Button
|
||||
icon="arrow-right"
|
||||
onClick={() => act("next_phase")}>
|
||||
Next Phase
|
||||
</Button>
|
||||
<Button
|
||||
icon="home"
|
||||
onClick={() => act("players_home")}>
|
||||
Send All Players Home
|
||||
</Button>
|
||||
<Button
|
||||
icon="radiation"
|
||||
onClick={() => act("new_game")}>
|
||||
New Game
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
icon="skull"
|
||||
onClick={() => act("nuke")}
|
||||
color="black">
|
||||
Nuke (delete datum + landmarks, hope it fixes everything!)
|
||||
</Button>
|
||||
</Section>
|
||||
)}
|
||||
<Section title="Players">
|
||||
<LabeledList>
|
||||
{!!players && players.map(player => (
|
||||
<LabeledList.Item
|
||||
className="candystripe"
|
||||
key={player.ref}
|
||||
label={player.name}>
|
||||
{!player.alive && (<Box color="red">DEAD</Box>)}
|
||||
{player.votes !== undefined && !!player.alive
|
||||
&& (<Fragment>Votes : {player.votes} </Fragment>)}
|
||||
{
|
||||
!!player.actions && player.actions.map(action => {
|
||||
return (
|
||||
<Button
|
||||
key={action}
|
||||
onClick={() => act('mf_targ_action', {
|
||||
atype: action,
|
||||
target: player.ref,
|
||||
})}>
|
||||
{action}
|
||||
</Button>); })
|
||||
}
|
||||
</LabeledList.Item>)
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
{!!judgement_phase && (
|
||||
<Section title="JUDGEMENT">
|
||||
title="Judgement"
|
||||
buttons={
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="info"
|
||||
tooltipPosition="left"
|
||||
tooltip={multiline`
|
||||
When someone is on trial, you are in charge of their fate.
|
||||
Innocent winning means the person on trial can live to see
|
||||
another day... and in losing they do not. You can go back
|
||||
to abstaining with the middle button if you reconsider.`}
|
||||
/>
|
||||
}>
|
||||
<Flex justify="space-around">
|
||||
<Button
|
||||
icon="smile-beam"
|
||||
content="INNOCENT!"
|
||||
color="good"
|
||||
onClick={() => act("vote_innocent")}>
|
||||
INNOCENT!
|
||||
</Button>
|
||||
Use these buttons to vote the accused innocent or guilty!
|
||||
disabled={!judgement_phase}
|
||||
onClick={() => act("vote_innocent")} />
|
||||
{!judgement_phase && (
|
||||
<Box>
|
||||
There is nobody on trial at the moment.
|
||||
</Box>
|
||||
)}
|
||||
{!!judgement_phase && (
|
||||
<Box>
|
||||
It is now time to vote, vote the accused innocent or guilty!
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
icon="angry"
|
||||
content="GUILTY!"
|
||||
color="bad"
|
||||
onClick={() => act("vote_guilty")}>
|
||||
GUILTY!
|
||||
</Button>
|
||||
disabled={!judgement_phase}
|
||||
onClick={() => act("vote_guilty")} />
|
||||
</Flex>
|
||||
<Flex justify="center">
|
||||
<Button
|
||||
icon="meh"
|
||||
content="Abstain"
|
||||
color="white"
|
||||
disabled={!judgement_phase}
|
||||
onClick={() => act("vote_abstain")} />
|
||||
</Flex>
|
||||
</Section>
|
||||
)}
|
||||
<Flex mt={1} spacing={1}>
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
<Section
|
||||
title="Roles"
|
||||
minHeight={10}>
|
||||
{!!all_roles && all_roles.map(r => (
|
||||
<Box key={r}>
|
||||
<Flex justify="space-between">
|
||||
{r}
|
||||
<Button
|
||||
content="?"
|
||||
onClick={() => act("mf_lookup", {
|
||||
atype: r.slice(0, -3),
|
||||
})}
|
||||
/>
|
||||
{phase !== "No Game" &&(
|
||||
<Flex spacing={1}>
|
||||
<Flex.Item grow={2}>
|
||||
<Section title="Players"
|
||||
buttons={
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="info"
|
||||
tooltip={multiline`
|
||||
This is the list of all the players in
|
||||
the game, during the day phase you may vote on them and,
|
||||
depending on your role, select players
|
||||
at certain phases to use your ability.`}
|
||||
/>
|
||||
}>
|
||||
<Flex
|
||||
direction="column">
|
||||
{!!players && players.map(player => (
|
||||
<Flex.Item
|
||||
height="30px"
|
||||
className="Section__title candystripe"
|
||||
key={player.ref}>
|
||||
<Flex
|
||||
height="18px"
|
||||
justify="space-between"
|
||||
align="center">
|
||||
<Flex.Item basis={16} >
|
||||
{!!player.alive && (<Box>{player.name}</Box>)}
|
||||
{!player.alive && (
|
||||
<Box color="red">{player.name}</Box>)}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
{!player.alive && (<Box color="red">DEAD</Box>)}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
{player.votes !== undefined && !!player.alive
|
||||
&& (<Fragment>Votes : {player.votes} </Fragment>)}
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} />
|
||||
<Flex.Item>
|
||||
{
|
||||
!!player.actions && player.actions.map(action => {
|
||||
return (
|
||||
<Button
|
||||
key={action}
|
||||
onClick={() => act('mf_targ_action', {
|
||||
atype: action,
|
||||
target: player.ref,
|
||||
})}>
|
||||
{action}
|
||||
</Button>); })
|
||||
}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>)
|
||||
)}
|
||||
</Flex>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={2}>
|
||||
<Flex
|
||||
direction="column"
|
||||
height="100%">
|
||||
<Section
|
||||
title="Roles and Notes"
|
||||
buttons={
|
||||
<Fragment>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="address-book"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltip={multiline`
|
||||
The top section is the roles in the game. You can
|
||||
press the question mark to get a quick blurb
|
||||
about the role itself.`}
|
||||
/>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="edit"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltip={multiline`
|
||||
The bottom section are your notes. on some roles this
|
||||
will just be an empty box, but on others it records the
|
||||
actions of your abilities (so for example, your
|
||||
detective work revealing a changeling).`}
|
||||
/>
|
||||
</Fragment>
|
||||
}>
|
||||
<Flex
|
||||
direction="column">
|
||||
{!!all_roles && all_roles.map(r => (
|
||||
<Flex.Item
|
||||
key={r}
|
||||
height="30px"
|
||||
className="Section__title candystripe">
|
||||
<Flex
|
||||
height="18px"
|
||||
align="center"
|
||||
justify="space-between">
|
||||
<Flex.Item>
|
||||
{r}
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
textAlign="right">
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="question"
|
||||
onClick={() => act("mf_lookup", {
|
||||
atype: r.slice(0, -3),
|
||||
})}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={2} basis={0}>
|
||||
<Section
|
||||
title="Notes"
|
||||
minHeight={10}>
|
||||
{roleinfo !== undefined && !!roleinfo.action_log
|
||||
&& roleinfo.action_log.map(log_line => (
|
||||
<Box key={log_line}>
|
||||
{log_line}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
</Section>
|
||||
{!!roleinfo && (
|
||||
<Flex.Item height={0} grow={1}>
|
||||
<Section scrollable
|
||||
fill
|
||||
overflowY="scroll">
|
||||
{roleinfo !== undefined && !!roleinfo.action_log
|
||||
&& roleinfo.action_log.map(log_line => (
|
||||
<Box key={log_line}>
|
||||
{log_line}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mt={1} direction="column">
|
||||
<Flex.Item>
|
||||
{!!admin_controls && (
|
||||
<Section textAlign="center">
|
||||
<Collapsible
|
||||
title="ADMIN CONTROLS"
|
||||
color="red">
|
||||
<Button
|
||||
icon="exclamation-triangle"
|
||||
color="black"
|
||||
tooltipPosition="top"
|
||||
tooltip={multiline`
|
||||
Almost all of these are all built to help me debug
|
||||
the game (ow, debugging a 12 player game!) So they are
|
||||
rudamentary and prone to breaking at the drop of a hat.
|
||||
Make sure you know what you're doing when you press one.
|
||||
Also because an admin did it: do not gib/delete/dust
|
||||
anyone! It will runtime the game to death!`}
|
||||
content="A Kind, Coder Warning"
|
||||
onClick={() => act("next_phase")} /><br />
|
||||
<Button
|
||||
icon="arrow-right"
|
||||
tooltipPosition="top"
|
||||
tooltip={multiline`
|
||||
This will advance the game to the next phase
|
||||
(day talk > day voting, day voting > night/trial)
|
||||
pretty fun to just spam this and freak people out,
|
||||
try that roundend!`}
|
||||
content="Next Phase"
|
||||
onClick={() => act("next_phase")} />
|
||||
<Button
|
||||
icon="home"
|
||||
tooltipPosition="top"
|
||||
tooltip={multiline`
|
||||
Hopefully you won't use this button
|
||||
often, it's a safety net just in case
|
||||
mafia players somehow escape (nullspace
|
||||
redirects to the error room then station)
|
||||
Either way, VERY BAD IF THAT HAPPENS as
|
||||
godmoded assistants will run free. Use
|
||||
this to recollect them then make a bug report.`}
|
||||
content="Send All Players Home"
|
||||
onClick={() => act("players_home")} />
|
||||
<Button
|
||||
icon="sync-alt"
|
||||
tooltipPosition="top"
|
||||
tooltip={multiline`
|
||||
This immediately ends the game, and attempts to start
|
||||
another. Nothing will happen if another
|
||||
game fails to start!`}
|
||||
content="New Game"
|
||||
onClick={() => act("new_game")} />
|
||||
<Button
|
||||
icon="skull"
|
||||
tooltipPosition="top"
|
||||
tooltip={multiline`
|
||||
Deletes the datum, clears all landmarks, makes mafia
|
||||
as it was roundstart: nonexistant. Use this if you
|
||||
really mess things up. You did mess things up, didn't you.`}
|
||||
content="Nuke"
|
||||
onClick={() => act("nuke")} />
|
||||
<br />
|
||||
<Button
|
||||
icon="paint-brush"
|
||||
tooltipPosition="top"
|
||||
tooltip={multiline`
|
||||
This is the custom game creator, it is... simple.
|
||||
You put in roles and until you press CANCEL or FINISH
|
||||
it will keep letting you add more roles. Assitants
|
||||
on the bottom because of pathing stuff. Resets after
|
||||
the round finishes back to 12 player random setups.`}
|
||||
content="Create Custom Setup"
|
||||
onClick={() => act("debug_setup")} />
|
||||
<Button
|
||||
icon="paint-roller"
|
||||
tooltipPosition="top"
|
||||
tooltip={multiline`
|
||||
If you messed up and accidently didn't make it how
|
||||
you wanted, simply just press this to reset it. The game
|
||||
will auto reset after each game as well.`}
|
||||
content="Reset Custom Setup"
|
||||
onClick={() => act("cancel_setup")} />
|
||||
</Collapsible>
|
||||
</Section>
|
||||
)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const LobbyDisplay = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
phase,
|
||||
timeleft,
|
||||
admin_controls,
|
||||
} = data;
|
||||
return (
|
||||
<Box>
|
||||
[Phase = {phase} | <TimeDisplay auto="down" value={timeleft} />]{' '}
|
||||
<Button
|
||||
icon="clipboard-check"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltip={multiline`
|
||||
Signs you up for the next game. If there
|
||||
is an ongoing one, you will be signed up
|
||||
for the next.`}
|
||||
content="Sign Up"
|
||||
onClick={() => act("mf_signup")} />
|
||||
<Button
|
||||
icon="eye"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltip={multiline`
|
||||
Spectates games until you turn it off.
|
||||
Automatically enabled when you die in game,
|
||||
because I assumed you would want to see the
|
||||
conclusion. You won't get messages if you
|
||||
rejoin SS13.`}
|
||||
content="Spectate"
|
||||
onClick={() => act("mf_spectate")} />
|
||||
{!!admin_controls && (
|
||||
<Button
|
||||
color="red"
|
||||
icon="gavel"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltip={multiline`
|
||||
Hello admin! If it is the admin controls you seek,
|
||||
please notice the scrollbar you have that players
|
||||
do not!`}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
43
tgui/packages/tgui/styles/themes/neutral.scss
Normal file
43
tgui/packages/tgui/styles/themes/neutral.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
@use 'sass:meta';
|
||||
|
||||
$neutral: #ffb300;
|
||||
|
||||
@use '../colors.scss' with (
|
||||
$primary: $neutral,
|
||||
$fg-map-keys: (),
|
||||
$bg-map-keys: (),
|
||||
);
|
||||
@use '../base.scss' with (
|
||||
$color-bg: color.scale($neutral, $lightness: -40%),
|
||||
$color-bg-grad-spread: 6%,
|
||||
);
|
||||
|
||||
.theme-neutral {
|
||||
// Components
|
||||
@include meta.load-css('../components/Button.scss', $with: (
|
||||
'color-default': color.scale($neutral, $lightness: -30%),
|
||||
'color-transparent-text': color.scale($neutral, $lightness: 30%),
|
||||
));
|
||||
@include meta.load-css('../components/ProgressBar.scss', $with: (
|
||||
'color-default': $neutral,
|
||||
'color-background': rgba(0, 0, 0, 0.5),
|
||||
));
|
||||
@include meta.load-css('../components/Section.scss');
|
||||
|
||||
// Layouts
|
||||
@include meta.load-css('../layouts/Layout.scss');
|
||||
@include meta.load-css('../layouts/Window.scss');
|
||||
@include meta.load-css('../layouts/TitleBar.scss', $with: (
|
||||
'color-background': color.scale($neutral, $lightness: -25%),
|
||||
));
|
||||
|
||||
.Layout__content {
|
||||
background-image: url('../../assets/bg-neutral.svg');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user