mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-20 20:37:34 +01:00
af6c74d917
Currently, only Preferences Verbs use subcategories. As a result, all other Verb tabs are only organized alphabetically, meaning that a single mechanic (ie. languages) has its governing Verbs scattered rather than grouped together. Verb organization by Tab handles broad organization - IC, OOC, Objects, etc.- highly effectively but requires the user to click-navigate to other Tabs to access any other verbs. Verb organization within a Tab can be handled by A.) subcategories and B.) Verb name prefixes (such as Earphones - *****). The first option is the clearest and cleanest, but consumes additional vertical space. The second option conserves vertical space at the cost of visual clarity. This PR attempts to reorganize Verbs by their Tabs, Subcategories, and prefixes with the following priorities in mind: - Minimal disruption; any radical change must be justified (don't move shit around just for the sake of moving shit around.) - Verbs which share common functions (such as Languages) should be grouped together. - Maintainability. Contributors should not need to memorize an excessively convoluted Subcategories list; Subcategories must be intuitive and simple. - Subcategories should be used judiciously when grouping Verbs to conserve vertical space. Example: Instead of creating a Subcategory for Emotes, just rename Audible Emote and Visual Emote to Emote (Audible) and Emote (Visual). - Subcategories should highlight uncommon or unusual verbs when reasonable. Example: There are several animal husbandry-related Verbs, and because most characters will not have access to those verbs regularly, we can be less conservative with space (because they'll be gone most of the time) for the sake of emphasizing their availability. This PR covers the IC, OOC, and Object Tabs. Subcategories: OOC.Chat (Chat functions) - AOOC - LOOC - Devsay (moved from Special Verbs) - Dsay (moved from Special Verbs) - OOC OOC.Debug (Fix shit) - Fit viewport - Fix chat - Refresh TGUI OOC.Round (Round information) - Check Gamemode Probability - Check Round Info - Custom Event Info - Vote IC.Antag (Antag verbs w/o their own tab) - Invite to the Loyalists - Invite to the Revolutionaries - Set Ambition IC.Critters (More critter stuff than you'd expect there to be) - Befriend Carp (Previous name 'Become Friends') - Befriend Cat (Previous name 'Become Friends') - Befriend Dog - Befriend Ives - Name Alien Species - Name Animal IC.Language (Language-related stuff) - Check Default Language - Check Known Languages - Set Default Language IC.Maneuver (Movement, positioning, etc.) - Adjust walk speed - Do Pushup - Face Direction - Look Down - Look Up - Move Downwards - Move Upwards - Rest Object.Equipped (Equipped object stuff, mostly for drip) - Adjust Badge - Adjust Bag Straps (Backpacks) - Adjust Goggles - Adjust Mask - Adjust Welding Goggles - Adjust Welding Mask - Change Glasses Layer - Change Pants Layer - Change Wrist Radio Layer - Change Wristwear Layer - Check Time - Flip Badge - Flip Belt - Flip Eyepatch - Flip Hat - Flip ID card (ID card) - Flip Radio (Clip-on radio) - Flip Wristwear - Fold Collar - Lock Antenna(e) - Point At Watch - Raise Shroud - Roll Up Cape Mantle - Roll Up/Down Jumpsuit - Roll Up/Down Sleeves - Switch Belt Layer - Switch ID Layer - Switch Lanyard Layer - Switch Shoe Layer - Toggle Aviators (Like a dozen variants) - Toggle Ceremonial Garment Lights - Toggle Coat Buttons - Toggle Coat Zipper - Toggle Hair Coverage (For hats helmets etc) - Toggle Hair Coverage (FOR BABY CARP!!!!!) - Toggle Helmet Camera - Toggle Hood - Toggle Lyodsuit Mask - Toggle Poncho Tail Coverage - Toggle Shirt Buttons - Toggle Suit Sensors - Toggle Visor (Pilot helmet) - Toggle Waistcoat Buttons - Transform Holoclothing - Voidsuit - Eject Suit Cooler - Voidsuit - Eject Tank - Voidsuit - Toggle Helmet Object.Held (If you're HOLDING it, its probably relevant to you rn) - Activate Held Object (Silicons; moved from IC) - Alter Beacon's Signal (Radio beacon) - Change Bite Size (Utensils) - Change Pen Colour - Choose Colour (Paint sprayer) - Choose Decal (Paint sprayer) - Choose Direction (Paint sprayer) - Choose Preset Colour (Paint sprayer) - Describe Prototype - Draw Boot Knife - Empty Bee Net - Empty Spray Bottle - Holster - Name Gun - Name Prototype - Plant Flag (Flags) - Print Plant Report - Remove Chopsticks - Remove Top - Set Detector High-Bound (Light meter) - Set Detector Low-Bound (Light meter) - Set Hailer Message (Hailer) - Set Timer (Timer igniter) - Set Valve Pressure (Pneumatic cannon) - Show Held Item - Spin Cylinder (Revolvers) - Switch Verbosity (Health analyzer) - Toggle Chainsaw Power (Chainsaw) - Toggle Flashlight Brightness (Flashlights) - Toggle Gun Safety - Toggle Hazard Vest - Toggle Pinpointer Mode (Pinpointer) - Toggle Mister (Backpack water tank. Goes into/out of hands) - Twist Cap - Use Scope - Wield Pick/Drill - Wield Two-Handed Weapon (Two-handed weapons) Object.Cards (I fucking hate cards!!!) - Deck - Deal - Deck - Draw - Deck - Pick - Hand - Pick - Turn Hand Into Deck Object.Earphones (Enough here to separate out) - Eject Music Cartridge - Change Volume - Next Song - Pause/Unpause - Play/Stop - Previous Song Object.Jetpack (This is probably really important to see quickly) - Toggle Jetpack - Toggle Jetpack Stabilization Object.Tape Recorder (Enough here to separate out) - Start Recording - Stop Recording - Clear Memory - Playback Memory - Print Transcript - Eject Portable Storage **IC tab example:** <img width="1061" height="277" alt="Screenshot 2025-08-09 101813" src="https://github.com/user-attachments/assets/a2afe92d-34fd-4160-bf68-b459a4195e26" /> **Objects tab example:** <img width="1100" height="472" alt="Screenshot 2025-08-09 101807" src="https://github.com/user-attachments/assets/a36e4c76-594e-4569-93ba-ffcda57e8760" />
639 lines
25 KiB
Plaintext
639 lines
25 KiB
Plaintext
GLOBAL_LIST_EMPTY(additional_antag_types)
|
|
|
|
/datum/game_mode
|
|
var/name = "invalid"
|
|
var/round_description = "How did you even vote this in?"
|
|
var/extended_round_description = "This roundtype should not be spawned, let alone votable. Someone contact a developer and tell them the game's broken again."
|
|
var/config_tag = null
|
|
var/votable = 1
|
|
var/probability = 0
|
|
|
|
var/required_players = 0 // Minimum players for round to start if voted in.
|
|
var/max_players = 0 // Maximum players for round to start for secret voting. 0 means "doesn't matter"
|
|
var/required_enemies = 0 // Minimum antagonists for round to start.
|
|
var/newscaster_announcements = null
|
|
var/ert_disabled = 0 // ERT cannot be called.
|
|
var/deny_respawn = 0 // Disable respawn during this round.
|
|
|
|
var/list/disabled_jobs = list() // Mostly used for Malf. This check is performed in job_controller so it doesn't spawn a regular AI.
|
|
|
|
var/shuttle_delay = 1 // Shuttle transit time is multiplied by this.
|
|
var/auto_recall_shuttle = 0 // Will the shuttle automatically be recalled?
|
|
|
|
var/list/antag_tags = list() // Core antag templates to spawn.
|
|
var/list/antag_templates // Extra antagonist types to include.
|
|
var/round_autoantag = 0 // Will this round attempt to periodically spawn more antagonists?
|
|
var/antag_scaling_coeff = 5 // Coefficient for scaling max antagonists to player count.
|
|
var/require_all_templates = 0 // Will only start if all templates are checked and can spawn.
|
|
|
|
var/station_was_nuked = 0 // See nuclearbomb.dm and malfunction.dm.
|
|
var/explosion_in_progress = 0 // Sit back and relax
|
|
var/waittime_l = 60 SECONDS // Lower bound on time before intercept arrives (in tenths of seconds)
|
|
var/waittime_h = 180 SECONDS // Upper bound on time before intercept arrives (in tenths of seconds)
|
|
|
|
var/event_delay_mod_moderate // Modifies the timing of random events.
|
|
var/event_delay_mod_major // As above.
|
|
|
|
/datum/game_mode/New()
|
|
..()
|
|
// Enforce some formatting.
|
|
// This will probably break something.
|
|
name = capitalize(lowertext(name))
|
|
config_tag = lowertext(config_tag)
|
|
|
|
/datum/game_mode/Topic(href, href_list[])
|
|
if(..())
|
|
return
|
|
if(href_list["toggle"])
|
|
switch(href_list["toggle"])
|
|
if("respawn")
|
|
deny_respawn = !deny_respawn
|
|
if("ert")
|
|
ert_disabled = !ert_disabled
|
|
announce_ert_disabled()
|
|
if("shuttle_recall")
|
|
auto_recall_shuttle = !auto_recall_shuttle
|
|
if("autotraitor")
|
|
round_autoantag = !round_autoantag
|
|
message_admins("Admin [key_name_admin(usr)] toggled game mode option '[href_list["toggle"]]'.")
|
|
else if(href_list["set"])
|
|
var/choice = ""
|
|
switch(href_list["set"])
|
|
if("shuttle_delay")
|
|
choice = input("Enter a new shuttle delay multiplier") as num
|
|
if(!choice || choice < 1 || choice > 20)
|
|
return
|
|
shuttle_delay = choice
|
|
if("antag_scaling")
|
|
choice = input("Enter a new antagonist cap scaling coefficient.") as num
|
|
if(isnull(choice) || choice < 0 || choice > 100)
|
|
return
|
|
antag_scaling_coeff = choice
|
|
if("event_modifier_moderate")
|
|
choice = input("Enter a new moderate event time modifier.") as num
|
|
if(isnull(choice) || choice < 0 || choice > 100)
|
|
return
|
|
event_delay_mod_moderate = choice
|
|
refresh_event_modifiers()
|
|
if("event_modifier_severe")
|
|
choice = input("Enter a new moderate event time modifier.") as num
|
|
if(isnull(choice) || choice < 0 || choice > 100)
|
|
return
|
|
event_delay_mod_major = choice
|
|
refresh_event_modifiers()
|
|
message_admins("Admin [key_name_admin(usr)] set game mode option '[href_list["set"]]' to [choice].")
|
|
else if(href_list["debug_antag"])
|
|
if(href_list["debug_antag"] == "self")
|
|
usr.client.debug_variables(src)
|
|
return
|
|
var/datum/antagonist/antag = GLOB.all_antag_types[href_list["debug_antag"]]
|
|
if(antag)
|
|
usr.client.debug_variables(antag)
|
|
message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.")
|
|
else if(href_list["remove_antag_type"])
|
|
if(antag_tags && (href_list["remove_antag_type"] in antag_tags))
|
|
to_chat(usr, "Cannot remove core mode antag type.")
|
|
return
|
|
var/datum/antagonist/antag = GLOB.all_antag_types[href_list["remove_antag_type"]]
|
|
if(antag_templates && antag_templates.len && antag && (antag in antag_templates) && (antag.id in GLOB.additional_antag_types))
|
|
antag_templates -= antag
|
|
GLOB.additional_antag_types -= antag.id
|
|
message_admins("Admin [key_name_admin(usr)] removed [antag.role_text] template from game mode.")
|
|
else if(href_list["add_antag_type"])
|
|
var/choice = input("Which type do you wish to add?") as null|anything in GLOB.all_antag_types
|
|
if(!choice)
|
|
return
|
|
var/datum/antagonist/antag = GLOB.all_antag_types[choice]
|
|
if(antag)
|
|
if(!islist(SSticker.mode.antag_templates))
|
|
SSticker.mode.antag_templates = list()
|
|
SSticker.mode.antag_templates |= antag
|
|
message_admins("Admin [key_name_admin(usr)] added [antag.role_text] template to game mode.")
|
|
|
|
// I am very sure there's a better way to do this, but I'm not sure what it might be. ~Z
|
|
// yes there is. but let me first immortalize this code.
|
|
/* spawn(1)
|
|
for(var/datum/admins/admin in world)
|
|
if(usr.client == admin.owner)
|
|
admin.show_game_mode(usr)
|
|
return */
|
|
|
|
if (usr.client && usr.client.holder)
|
|
usr.client.holder.show_game_mode(usr)
|
|
|
|
/datum/game_mode/proc/announce() //to be called when round starts
|
|
to_world("<B>The current game mode is [capitalize(name)]!</B>")
|
|
if(round_description)
|
|
to_world("[round_description]")
|
|
if(round_autoantag)
|
|
to_world("Antagonists will be added to the round automagically as needed.")
|
|
if(antag_templates && antag_templates.len)
|
|
var/antag_summary = "<b>Possible antagonist types:</b> "
|
|
var/i = 1
|
|
for(var/datum/antagonist/antag in antag_templates)
|
|
if(i > 1)
|
|
if(i == antag_templates.len)
|
|
antag_summary += " and "
|
|
else
|
|
antag_summary += ", "
|
|
antag_summary += "[antag.role_text_plural]"
|
|
i++
|
|
antag_summary += "."
|
|
if(antag_templates.len > 1 && !SSticker.hide_mode)
|
|
to_world("[antag_summary]")
|
|
else
|
|
message_admins("[antag_summary]")
|
|
|
|
///can_start()
|
|
///Checks to see if the game can be setup and ran with the current number of players or whatnot.
|
|
/datum/game_mode/proc/can_start()
|
|
|
|
log_game_mode("Checking gamemode possibility selection for: [name]...")
|
|
|
|
var/returning = GAME_FAILURE_NONE
|
|
|
|
var/playerC = 0
|
|
for(var/mob/abstract/new_player/player in GLOB.player_list)
|
|
if(player.client && player.ready)
|
|
playerC++
|
|
|
|
log_traitor("[playerC] players checked and readied.")
|
|
|
|
if(required_players && playerC < required_players)
|
|
log_game_mode("There aren't enough players ([playerC]/[required_players]) to start [name]!")
|
|
returning |= GAME_FAILURE_NO_PLAYERS
|
|
|
|
if(max_players && playerC > max_players)
|
|
log_game_mode("There are too many players ([playerC]/[max_players]) to start [name]!")
|
|
returning |= GAME_FAILURE_TOO_MANY_PLAYERS
|
|
|
|
if(antag_templates && antag_templates.len)
|
|
log_game_mode("Checking antag templates...")
|
|
if(antag_tags && antag_tags.len)
|
|
log_game_mode("Checking antag tags...")
|
|
for(var/antag_tag in antag_tags)
|
|
var/datum/antagonist/antag = GLOB.all_antag_types[antag_tag]
|
|
if(!antag)
|
|
continue
|
|
log_game_mode("Checking antag tag: [antag.role_text]...")
|
|
var/list/potential = list() //List of potential players to spawn as antagonists
|
|
if(antag.flags & ANTAG_OVERRIDE_JOB)
|
|
potential = antag.pending_antagonists
|
|
else
|
|
potential = antag.candidates
|
|
if(islist(potential))
|
|
for(var/potential_antag in potential)
|
|
var/datum/mind/player = potential_antag
|
|
if(!(antag.flags & ANTAG_OVERRIDE_JOB) && (player.assigned_role in antag.restricted_jobs))
|
|
potential -= potential_antag
|
|
antag.candidates -= player
|
|
log_traitor("GAMEMODE: Player [player.name] ([player.key]) was removed from the potential antags list due to being given the role [player.assigned_role] which is a restricted job!")
|
|
|
|
// Split the for loop here so that we can have a complete set of potential lists for each antag_tag before continuing
|
|
var/list/total_enemies = list()
|
|
for(var/antag_tag in antag_tags)
|
|
var/datum/antagonist/antag = GLOB.all_antag_types[antag_tag]
|
|
if(!antag)
|
|
continue
|
|
var/list/potential = list()
|
|
if(antag.flags & ANTAG_OVERRIDE_JOB)
|
|
potential = antag.pending_antagonists
|
|
else
|
|
potential = antag.candidates
|
|
if(potential.len)
|
|
log_traitor("GAMEMODE: Found [potential.len] potential antagonists for [antag.role_text].")
|
|
total_enemies |= potential //Only count candidates once for our total enemy pool
|
|
if(antag.initial_spawn_req && require_all_templates && potential.len < antag.initial_spawn_req)
|
|
log_game_mode("There are not enough antagonists ([potential.len]/[antag.initial_spawn_req]) for the role [antag.role_text]!")
|
|
returning |= GAME_FAILURE_NO_ANTAGS
|
|
|
|
log_traitor("GAMEMODE: Found [total_enemies.len] total enemies for [name].")
|
|
|
|
if(required_enemies && total_enemies.len < required_enemies)
|
|
log_traitor("GAMEMODE: There are not enough total antagonists ([total_enemies.len]/[required_enemies]) to start [name]!")
|
|
returning |= GAME_FAILURE_NO_ANTAGS
|
|
|
|
log_game_mode("Finished gamemode checking. [name] returned [returning].")
|
|
|
|
return returning
|
|
|
|
/datum/game_mode/proc/refresh_event_modifiers()
|
|
if(event_delay_mod_moderate || event_delay_mod_major)
|
|
SSevents.report_at_round_end = 1
|
|
if(event_delay_mod_moderate)
|
|
var/datum/event_container/EModerate = SSevents.event_containers[EVENT_LEVEL_MODERATE]
|
|
EModerate.delay_modifier = event_delay_mod_moderate
|
|
if(event_delay_mod_moderate)
|
|
var/datum/event_container/EMajor = SSevents.event_containers[EVENT_LEVEL_MAJOR]
|
|
EMajor.delay_modifier = event_delay_mod_major
|
|
|
|
/datum/game_mode/proc/pre_setup()
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
for(var/datum/antagonist/antag in antag_templates)
|
|
antag.update_current_antag_max()
|
|
antag.update_initial_spawn_target()
|
|
antag.build_candidate_list() //compile a list of all eligible candidates
|
|
|
|
if(length(antag_templates) > 1) // If we have multiple templates to satisfy, we must pick candidates who satisfy fewer templates first, and fill the template with fewest candidates first
|
|
var/list/template_candidates = list()
|
|
var/list/all_candidates = list() // All candidates for every template, may contain duplicates
|
|
var/list/antag_templates_by_initial_spawn_req = list()
|
|
|
|
for(var/datum/antagonist/antag in antag_templates)
|
|
template_candidates[antag.id] = length(antag.candidates)
|
|
all_candidates += antag.candidates
|
|
antag_templates_by_initial_spawn_req[antag] = antag.initial_spawn_req
|
|
|
|
sortTim(antag_templates_by_initial_spawn_req, GLOBAL_PROC_REF(cmp_numeric_asc), TRUE)
|
|
antag_templates = list_keys(antag_templates_by_initial_spawn_req)
|
|
|
|
var/list/valid_templates_per_candidate = list() // number of roles each candidate can satisfy
|
|
for(var/candidate in all_candidates)
|
|
valid_templates_per_candidate[candidate]++
|
|
|
|
valid_templates_per_candidate = shuffle(valid_templates_per_candidate) // shuffle before sorting so that candidates with the same number of templates will be in random order
|
|
sortTim(valid_templates_per_candidate, GLOBAL_PROC_REF(cmp_numeric_asc), TRUE)
|
|
|
|
for(var/datum/antagonist/antag in antag_templates)
|
|
antag.candidates = list_keys(valid_templates_per_candidate) & antag.candidates // orders antag.candidates by valid_templates_per_candidate
|
|
|
|
var/datum/antagonist/last_template = antag_templates[antag_templates.len]
|
|
last_template.candidates = shuffle(last_template.candidates)
|
|
|
|
for(var/datum/antagonist/antag in antag_templates)
|
|
//antag roles that replace jobs need to be assigned before the job controller hands out jobs.
|
|
if(antag.flags & ANTAG_OVERRIDE_JOB)
|
|
antag.attempt_spawn() //select antags to be spawned
|
|
antag.candidates = shuffle(antag.candidates) // makes selection past initial_spawn_req fairer
|
|
|
|
///post_setup()
|
|
/datum/game_mode/proc/post_setup()
|
|
|
|
next_spawn = world.time + rand(min_autotraitor_delay, max_autotraitor_delay)
|
|
|
|
refresh_event_modifiers()
|
|
|
|
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(display_logout_report)), ROUNDSTART_LOGOUT_REPORT_TIME)
|
|
|
|
var/welcome_delay = rand(waittime_l, waittime_h)
|
|
addtimer(CALLBACK(SSatlas.current_map, TYPE_PROC_REF(/datum/map, send_welcome)), welcome_delay)
|
|
|
|
addtimer(CALLBACK(SSatlas.current_map, TYPE_PROC_REF(/datum/map, load_holodeck_programs)), 5 MINUTES)
|
|
|
|
//Assign all antag types for this game mode. Any players spawned as antags earlier should have been removed from the pending list, so no need to worry about those.
|
|
for(var/datum/antagonist/antag in antag_templates)
|
|
if(!(antag.flags & ANTAG_OVERRIDE_JOB))
|
|
antag.attempt_spawn() //select antags to be spawned
|
|
if(!(antag.flags & ANTAG_NO_ROUNDSTART_SPAWN))
|
|
antag.finalize_spawn() //actually spawn antags
|
|
|
|
if(GLOB.evacuation_controller && auto_recall_shuttle)
|
|
GLOB.evacuation_controller.auto_recall(1)
|
|
|
|
feedback_set_details("round_start","[time2text(world.realtime)]")
|
|
if(SSticker.mode)
|
|
feedback_set_details("GLOB.master_mode","[GLOB.master_mode]")
|
|
feedback_set_details("game_mode","[SSticker.mode]")
|
|
feedback_set_details("server_ip","[world.internet_address]:[world.port]")
|
|
return 1
|
|
|
|
/datum/game_mode/proc/fail_setup()
|
|
for(var/datum/antagonist/antag in antag_templates)
|
|
antag.reset_antag_selection()
|
|
|
|
/datum/game_mode/proc/announce_ert_disabled()
|
|
if(!ert_disabled)
|
|
return
|
|
|
|
var/list/reasons = list(
|
|
"political instability",
|
|
"quantum fluctuations",
|
|
"hostile raiders",
|
|
"derelict station debris",
|
|
"REDACTED",
|
|
"ancient alien artillery",
|
|
"solar magnetic storms",
|
|
"sentient time-travelling killbots",
|
|
"gravitational anomalies",
|
|
"wormholes to another dimension",
|
|
"a telescience mishap",
|
|
"radiation flares",
|
|
"supermatter dust",
|
|
"leaks into a negative reality",
|
|
"antiparticle clouds",
|
|
"residual bluespace energy",
|
|
"suspected criminal operatives",
|
|
"malfunctioning von Neumann probe swarms",
|
|
"shadowy interlopers",
|
|
"a stranded Vaurcan hiveship",
|
|
"haywire IPC constructs",
|
|
"rogue Unathi exiles",
|
|
"artifacts of eldritch horror",
|
|
"a brain slug infestation",
|
|
"killer bugs that lay eggs in the husks of the living",
|
|
"a deserted transport carrying xenomorph specimens",
|
|
"an emissary for the gestalt requesting a security detail",
|
|
"classified security operations"
|
|
)
|
|
command_announcement.Announce("The presence of [pick(reasons)] in the region is tying up all available local emergency resources; emergency response teams cannot be called at this time, and post-evacuation recovery efforts will be substantially delayed.","Emergency Transmission")
|
|
|
|
/datum/game_mode/proc/check_finished()
|
|
return GLOB.evacuation_controller.round_over() || station_was_nuked
|
|
|
|
/datum/game_mode/proc/cleanup() //This is called when the round has ended but not the game, if any cleanup would be necessary in that case.
|
|
return
|
|
|
|
/datum/game_mode/proc/declare_completion()
|
|
|
|
var/is_antag_mode = (antag_templates && antag_templates.len)
|
|
var/discord_text = "A round of **[name]** has ended! \[Game ID: [GLOB.round_id]\]\n\n"
|
|
var/antag_text = ""
|
|
check_victory()
|
|
if(is_antag_mode)
|
|
sleep(10)
|
|
for (var/datum/antagonist/antag in antag_templates)
|
|
sleep(10)
|
|
antag.check_victory()
|
|
antag.print_player_summary()
|
|
// Avoid the longest loop if we aren't actively using the bot.
|
|
if (SSdiscord.active)
|
|
antag_text += antag.print_player_summary_discord()
|
|
|
|
sleep(10)
|
|
print_ownerless_uplinks()
|
|
|
|
discord_text += antag_text
|
|
SSdiscord.send_to_announce(discord_text, 1)
|
|
discord_text = ""
|
|
|
|
var/clients = 0
|
|
var/surviving_total = 0
|
|
var/escaped_total = 0
|
|
var/ghosts = 0
|
|
|
|
for(var/mob/M in GLOB.player_list)
|
|
if(M.client)
|
|
clients++
|
|
if(M.stat != DEAD && isipc(M))
|
|
var/mob/living/carbon/human/H = M
|
|
var/datum/species/machine/machine = H.species
|
|
machine.update_tag(H, H.client)
|
|
if(M.stat != DEAD)
|
|
var/turf/playerTurf = get_turf(M)
|
|
var/area/playerArea = get_area(playerTurf)
|
|
surviving_total++
|
|
if(is_station_level(playerTurf.z) && is_station_area(playerArea))
|
|
escaped_total++
|
|
if(isobserver(M))
|
|
ghosts++
|
|
|
|
var/text = ""
|
|
var/escape_text
|
|
if(GLOB.evacuation_controller.evacuation_type == TRANSFER_EMERGENCY)
|
|
escape_text = "escaped"
|
|
else
|
|
escape_text = "transferred"
|
|
if(surviving_total > 0)
|
|
text += "<br>There [surviving_total>1 ? "were <b>[surviving_total] survivors</b>" : "was <b>one survivor</b>"]"
|
|
text += " (<b>[escaped_total>0 ? escaped_total : "none"] [escape_text]</b>) and <b>[ghosts] ghosts</b>.<br>"
|
|
|
|
discord_text += "There [surviving_total>1 ? "were **[surviving_total] survivors**" : "was **one survivor**"]"
|
|
discord_text += " ([escaped_total>0 ? escaped_total : "none"] [escape_text]) and **[ghosts] ghosts**."
|
|
else
|
|
text += "There were <b>no survivors</b> (<b>[ghosts] ghosts</b>)."
|
|
|
|
discord_text += "There were **no survivors** ([ghosts] ghosts)."
|
|
to_world(text)
|
|
|
|
SSdiscord.send_to_announce(discord_text)
|
|
SSdiscord.post_webhook_event(WEBHOOK_ROUNDEND, list("survivours"=surviving_total, "escaped"=escaped_total, "ghosts"=ghosts, "gamemode"=name, "gameid"=GLOB.round_id, "antags"=antag_text))
|
|
|
|
if(clients > 0)
|
|
feedback_set("round_end_clients", clients)
|
|
if(ghosts > 0)
|
|
feedback_set("round_end_ghosts", ghosts)
|
|
if(surviving_total > 0)
|
|
feedback_set("survived_total", surviving_total)
|
|
if(escaped_total > 0)
|
|
feedback_set("escaped_total", escaped_total)
|
|
|
|
return 0
|
|
|
|
/datum/game_mode/proc/check_win() //universal trigger to be called at mob death, nuke explosion, etc. To be called from everywhere.
|
|
return 0
|
|
|
|
/datum/game_mode/proc/get_players_for_role(var/role, var/antag_id)
|
|
var/list/players = list()
|
|
var/list/candidates = list()
|
|
|
|
var/datum/antagonist/antag_template = GLOB.all_antag_types[antag_id]
|
|
if(!antag_template)
|
|
return candidates
|
|
|
|
// If this is being called post-roundstart then it doesn't care about ready status.
|
|
if(SSticker.current_state == GAME_STATE_PLAYING)
|
|
for(var/mob/player in GLOB.player_list)
|
|
if(!player.client)
|
|
continue
|
|
if(isnewplayer(player))
|
|
continue
|
|
if(!role || (role in player.client.prefs.be_special_role))
|
|
log_traitor("[player.key] had [antag_id] enabled, so we are drafting them.")
|
|
candidates |= player.mind
|
|
else
|
|
// Assemble a list of active players without jobbans.
|
|
for(var/mob/abstract/new_player/player in GLOB.player_list)
|
|
if( player.client && player.ready )
|
|
players += player
|
|
|
|
// Get a list of all the people who want to be the antagonist for this round
|
|
for(var/mob/abstract/new_player/player in players)
|
|
if(!role || (role in player.client.prefs.be_special_role))
|
|
log_traitor("[player.key] had [antag_id] enabled, so we are drafting them.")
|
|
candidates += player.mind
|
|
players -= player
|
|
|
|
return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than required_enemies
|
|
// required_enemies if the number of people with that role set to yes is less than recomended_enemies,
|
|
// Less if there are not enough valid players in the game entirely to make required_enemies.
|
|
|
|
/datum/game_mode/proc/num_players()
|
|
. = 0
|
|
for(var/mob/abstract/new_player/P in GLOB.player_list)
|
|
if(P.client && P.ready)
|
|
. ++
|
|
|
|
/datum/game_mode/proc/check_antagonists_topic(href, href_list[])
|
|
return 0
|
|
|
|
/datum/game_mode/proc/create_antagonists()
|
|
|
|
if(!GLOB.config.traitor_scaling)
|
|
antag_scaling_coeff = 0
|
|
|
|
if(antag_tags && antag_tags.len)
|
|
antag_templates = list()
|
|
for(var/antag_tag in antag_tags)
|
|
var/datum/antagonist/antag = GLOB.all_antag_types[antag_tag]
|
|
if(antag)
|
|
antag_templates |= antag
|
|
|
|
if(GLOB.additional_antag_types && GLOB.additional_antag_types.len)
|
|
if(!antag_templates)
|
|
antag_templates = list()
|
|
for(var/antag_type in GLOB.additional_antag_types)
|
|
var/datum/antagonist/antag = GLOB.all_antag_types[antag_type]
|
|
if(antag)
|
|
antag_templates |= antag
|
|
|
|
shuffle(antag_templates) //In the case of multiple antag types
|
|
newscaster_announcements = pick(GLOB.newscaster_standard_feeds)
|
|
|
|
/datum/game_mode/proc/check_victory()
|
|
return
|
|
|
|
/**
|
|
* This proc runs immediately after the gamemode datum is created, right before the jobs and antag roles are divvied out.
|
|
* Essentially, in the lobby right before game start.
|
|
* This proc should always return TRUE on a success and FALSE if something went wrong, so that if the initialization failed, the game can reset to lobby state.
|
|
*/
|
|
/datum/game_mode/proc/pre_game_setup()
|
|
return TRUE
|
|
|
|
//////////////////////////
|
|
//Reports player logouts//
|
|
//////////////////////////
|
|
/proc/get_logout_report()
|
|
var/msg = "<span class='notice'><b>Logout report</b>\n\n"
|
|
for(var/mob/living/L in GLOB.mob_list)
|
|
|
|
if(L.ckey)
|
|
var/found = 0
|
|
for(var/client/C in GLOB.clients)
|
|
if(C.ckey == L.ckey)
|
|
found = 1
|
|
break
|
|
if(!found)
|
|
msg += "<b>[L.name]</b> ([L.ckey]), the [L.job] (<font color='#ffcc00'><b>Disconnected</b></font>)\n"
|
|
|
|
if(L.ckey && L.client)
|
|
if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something)
|
|
msg += "<b>[L.name]</b> ([L.ckey]), the [L.job] (<font color='#ffcc00'><b>Connected, Inactive</b></font>)\n"
|
|
continue //AFK client
|
|
if(L.stat)
|
|
if(L.stat == UNCONSCIOUS)
|
|
msg += "<b>[L.name]</b> ([L.ckey]), the [L.job] (Unconscious)\n"
|
|
continue //Unconscious
|
|
if(L.stat == DEAD)
|
|
msg += "<b>[L.name]</b> ([L.ckey]), the [L.job] (Dead)\n"
|
|
continue //Dead
|
|
|
|
continue //Happy connected client
|
|
for(var/mob/abstract/ghost/observer/D in GLOB.mob_list)
|
|
if(D.mind && (D.mind.original == L || D.mind.current == L))
|
|
if(L.stat == DEAD)
|
|
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] (Dead)\n"
|
|
continue //Dead mob, ghost abandoned
|
|
else
|
|
if(D.can_reenter_corpse)
|
|
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] (<span class='warning'><b>Adminghosted</b></span>)\n"
|
|
continue //Lolwhat
|
|
else
|
|
msg += "<b>[L.name]</b> ([ckey(D.mind.key)]), the [L.job] (<span class='warning'><b>Ghosted</b></span>)\n"
|
|
continue //Ghosted while alive
|
|
|
|
msg += "</span>" // close the span from right at the top
|
|
return msg
|
|
|
|
/proc/display_logout_report()
|
|
var/logout_report = get_logout_report()
|
|
for(var/s in GLOB.staff)
|
|
var/client/C = s
|
|
if(check_rights(R_MOD|R_ADMIN,0,C.mob))
|
|
to_chat(C.mob, logout_report)
|
|
|
|
/client/proc/print_logout_report()
|
|
set category = "Admin"
|
|
set name = "Print Logout Report"
|
|
|
|
if(!check_rights(R_ADMIN|R_MOD))
|
|
return
|
|
to_chat(src,get_logout_report())
|
|
|
|
/proc/get_poor()
|
|
var/list/characters = list()
|
|
|
|
for(var/mob/living/carbon/human/character in GLOB.player_list)
|
|
if(character.client)
|
|
if((character.client.prefs.economic_status == ECONOMICALLY_DESTITUTE) || (character.client.prefs.economic_status == ECONOMICALLY_RUINED)) // Discrimination.
|
|
characters += character
|
|
else if(character.client.prefs.economic_status == ECONOMICALLY_POOR && prob(50)) // 50% discrimination.
|
|
characters += character
|
|
|
|
if(!length(characters))
|
|
return
|
|
|
|
return pick(characters)
|
|
|
|
//Announces objectives/generic antag text.
|
|
/proc/show_generic_antag_text(var/datum/mind/player)
|
|
if(player.current)
|
|
to_chat(player.current, "You are an antagonist! <span class='notice'><b>Within the rules</b></span>, try to act as an opposing force to the crew. Further RP and try to make sure other players have <i>fun</i>! If you are confused or at a loss, always adminhelp, and before taking extreme actions, please try to also contact the administration! Think through your actions and make the roleplay immersive! <b>Please remember all rules aside from those without explicit exceptions apply to antagonists.</b>")
|
|
|
|
/proc/show_objectives(var/datum/mind/player)
|
|
|
|
if(!player || !player.current) return
|
|
|
|
if(GLOB.config.objectives_disabled)
|
|
show_generic_antag_text(player)
|
|
return
|
|
|
|
var/obj_count = 1
|
|
to_chat(player.current, SPAN_NOTICE("Your current objectives:"))
|
|
for(var/datum/objective/objective in player.objectives)
|
|
to_chat(player.current, "<B>Objective #[obj_count]</B>: [objective.explanation_text]")
|
|
obj_count++
|
|
|
|
/mob/verb/check_round_info()
|
|
set name = "Check Round Info"
|
|
set category = "OOC.Round"
|
|
|
|
if(!SSticker.mode)
|
|
to_chat(usr, "Something is terribly wrong; there is no gametype.")
|
|
return
|
|
|
|
if(!SSticker.hide_mode)
|
|
to_chat(usr, "<b>The roundtype is [capitalize(SSticker.mode.name)]</b>")
|
|
if(SSticker.mode.round_description)
|
|
to_chat(usr, "<i>[SSticker.mode.round_description]</i>")
|
|
if(SSticker.mode.extended_round_description)
|
|
to_chat(usr, "[SSticker.mode.extended_round_description]")
|
|
else
|
|
to_chat(usr, "<i>Shhhh</i>. It's a secret.")
|
|
return
|
|
|
|
/mob/verb/check_gamemode_probability()
|
|
set name = "Check Gamemode Probability"
|
|
set category = "OOC.Round"
|
|
|
|
if(GLOB.config.show_game_type_odd)
|
|
to_chat(src, "<b>Secret Mode Odds:</b>")
|
|
var/sum = 0
|
|
for(var/config_tag in GLOB.config.probabilities_secret)
|
|
sum += GLOB.config.probabilities_secret[config_tag]
|
|
for(var/config_tag in GLOB.config.probabilities_secret)
|
|
if(GLOB.config.probabilities_secret[config_tag] > 0)
|
|
var/percentage = round(GLOB.config.probabilities_secret[config_tag] / sum * 100, 0.1)
|
|
to_chat(src, "[config_tag] [percentage]%")
|
|
|
|
to_chat(src, "<b>Mixed Secret Mode Odds:</b>")
|
|
sum = 0
|
|
for(var/config_tag in GLOB.config.probabilities_mixed_secret)
|
|
sum += GLOB.config.probabilities_mixed_secret[config_tag]
|
|
for(var/config_tag in GLOB.config.probabilities_mixed_secret)
|
|
if(GLOB.config.probabilities_mixed_secret[config_tag] > 0)
|
|
var/percentage = round(GLOB.config.probabilities_mixed_secret[config_tag] / sum * 100, 0.1)
|
|
to_chat(src, "[config_tag] [percentage]%")
|
|
else
|
|
to_chat(src, "Displaying gamemode odds is disabled in the config.")
|