diff --git a/.vscode/settings.json b/.vscode/settings.json
index bfce9bda7c..8edb013566 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -13,7 +13,7 @@
"source.fixAll.eslint": true
},
"workbench.editorAssociations": {
- "*.dmi": "imagePreview.previewEditor"
+ "*.dmi": "dmiEditor.dmiEditor"
},
"files.eol": "\n",
"gitlens.advanced.blame.customArguments": ["-w"],
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 83995812a4..65194b26f8 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -21,6 +21,12 @@
#define COMSIG_GLOB_PLAY_CINEMATIC "!play_cinematic"
#define COMPONENT_GLOB_BLOCK_CINEMATIC 1
+/// job subsystem has spawned and equipped a new mob
+#define COMSIG_GLOB_JOB_AFTER_SPAWN "!job_after_spawn"
+
+/// job datum has been called to deal with the aftermath of a latejoin spawn
+#define COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN "!job_after_latejoin_spawn"
+
#define COMSIG_GLOB_PRE_RANDOM_EVENT "!pre_random_event"
/// Do not allow this random event to continue.
#define CANCEL_PRE_RANDOM_EVENT (1<<0)
@@ -670,3 +676,6 @@
// /datum/component/identification signals
#define COMSIG_IDENTIFICATION_KNOWLEDGE_CHECK "id_knowledge_check" // (mob/user) - returns a value from ID_COMPONENT_KNOWLEDGE_NONE to ID_COMPONENT_KNOWLEDGE_FULL
+
+///from base of [/datum/component/multiple_lives/proc/respawn]: (mob/respawned_mob, gibbed, lives_left)
+#define COMSIG_ON_MULTIPLE_LIVES_RESPAWN "on_multiple_lives_respawn"
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index e315152252..33a3aa65c4 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -151,3 +151,43 @@
#define SOUND_AREA_LAVALAND SOUND_ENVIRONMENT_MOUNTAINS
#define SOUND_AREA_ICEMOON SOUND_ENVIRONMENT_CAVE
#define SOUND_AREA_WOODFLOOR SOUND_ENVIRONMENT_CITY
+
+///Announcer audio keys
+#define ANNOUNCER_AIMALF "aimalf"
+#define ANNOUNCER_ALIENS "aliens"
+#define ANNOUNCER_ANIMES "animes"
+#define ANNOUNCER_GRANOMALIES "granomalies"
+#define ANNOUNCER_INTERCEPT "intercept"
+#define ANNOUNCER_IONSTORM "ionstorm"
+#define ANNOUNCER_METEORS "meteors"
+#define ANNOUNCER_NEWAI "newAI"
+#define ANNOUNCER_OUTBREAK5 "outbreak5"
+#define ANNOUNCER_OUTBREAK7 "outbreak7"
+#define ANNOUNCER_POWEROFF "poweroff"
+#define ANNOUNCER_POWERON "poweron"
+#define ANNOUNCER_RADIATION "radiation"
+#define ANNOUNCER_SHUTTLECALLED "shuttlecalled"
+#define ANNOUNCER_SHUTTLEDOCK "shuttledock"
+#define ANNOUNCER_SHUTTLERECALLED "shuttlerecalled"
+#define ANNOUNCER_SPANOMALIES "spanomalies"
+
+/// Global list of all of our announcer keys.
+GLOBAL_LIST_INIT(announcer_keys, list(
+ ANNOUNCER_AIMALF,
+ ANNOUNCER_ALIENS,
+ ANNOUNCER_ANIMES,
+ ANNOUNCER_GRANOMALIES,
+ ANNOUNCER_INTERCEPT,
+ ANNOUNCER_IONSTORM,
+ ANNOUNCER_METEORS,
+ ANNOUNCER_NEWAI,
+ ANNOUNCER_OUTBREAK5,
+ ANNOUNCER_OUTBREAK7,
+ ANNOUNCER_POWEROFF,
+ ANNOUNCER_POWERON,
+ ANNOUNCER_RADIATION,
+ ANNOUNCER_SHUTTLECALLED,
+ ANNOUNCER_SHUTTLEDOCK,
+ ANNOUNCER_SHUTTLERECALLED,
+ ANNOUNCER_SPANOMALIES,
+))
diff --git a/code/__DEFINES/station.dm b/code/__DEFINES/station.dm
new file mode 100644
index 0000000000..0f6c4db003
--- /dev/null
+++ b/code/__DEFINES/station.dm
@@ -0,0 +1,9 @@
+#define STATION_TRAIT_POSITIVE 1
+#define STATION_TRAIT_NEUTRAL 2
+#define STATION_TRAIT_NEGATIVE 3
+
+
+#define STATION_TRAIT_ABSTRACT (1<<0)
+
+/// The data file that future station traits are stored in
+#define FUTURE_STATION_TRAITS_FILE "data/future_station_traits.json"
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 91b7c2ac7b..560bc2daf3 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -113,6 +113,7 @@
#define INIT_ORDER_VIS 80
#define INIT_ORDER_ACHIEVEMENTS 77
#define INIT_ORDER_RESEARCH 75
+#define INIT_ORDER_STATION 74 //This is high priority because it manipulates a lot of the subsystems that will initialize after it.
#define INIT_ORDER_EVENTS 70
#define INIT_ORDER_JOBS 65
#define INIT_ORDER_QUIRKS 60
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index d562755083..4e97723676 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -302,6 +302,7 @@
#define GLOVE_TRAIT "glove" //inherited by your cool gloves
#define BOOK_TRAIT "granter (book)" // knowledge is power
#define TURF_TRAIT "turf"
+#define STATION_TRAIT "station-trait"
// unique trait sources, still defines
#define STATUE_TRAIT "statue"
@@ -365,3 +366,16 @@
#define MAPPING_HELPER_TRAIT "mapping-helper"
/// Trait associated with mafia
#define MAFIA_TRAIT "mafia"
+
+///Traits given by station traits
+#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"
+#define STATION_TRAIT_UNNATURAL_ATMOSPHERE "station_trait_unnatural_atmosphere"
+#define STATION_TRAIT_UNIQUE_AI "station_trait_unique_ai"
+#define STATION_TRAIT_CARP_INFESTATION "station_trait_carp_infestation"
+#define STATION_TRAIT_PREMIUM_INTERNALS "station_trait_premium_internals"
+#define STATION_TRAIT_LATE_ARRIVALS "station_trait_late_arrivals"
+#define STATION_TRAIT_RANDOM_ARRIVALS "station_trait_random_arrivals"
+#define STATION_TRAIT_HANGOVER "station_trait_hangover"
+#define STATION_TRAIT_FILLED_MAINT "station_trait_filled_maint"
+#define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint"
+#define STATION_TRAIT_PDA_GLITCHED "station_trait_pda_glitched"
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 5578b1f22a..4e260244c8 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -5,6 +5,9 @@
locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \
)
+/// Returns either the error landmark or the location of the room. Needless to say, if this is used, it means things have gone awry.
+#define GET_ERROR_ROOM ((locate(/obj/effect/landmark/error) in GLOB.landmarks_list) || locate(4,4,1))
+
#define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL))
#define CULT_POLL_WAIT 2400
diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm
index 1609c5f208..cfb52408aa 100644
--- a/code/__HELPERS/priority_announce.dm
+++ b/code/__HELPERS/priority_announce.dm
@@ -4,7 +4,9 @@
var/announcement
if(!sound)
- sound = "attention"
+ sound = SSstation.announcer.get_rand_alert_sound()
+ else if(SSstation.announcer.event_sounds[sound])
+ sound = SSstation.announcer.event_sounds[sound]
if(type == "Priority")
announcement += "
Priority Announcement
"
@@ -29,110 +31,19 @@
GLOB.news_network.SubmitArticle(title + "
" + text, "Central Command", "Station Announcements", null)
///If the announcer overrides alert messages, use that message.
- // if(SSstation.announcer.custom_alert_message && !has_important_message)
- // announcement += SSstation.announcer.custom_alert_message
- // else
- announcement += "
[span_alert("[html_encode(text)]")]
"
+ if(SSstation.announcer.custom_alert_message && !has_important_message)
+ announcement += SSstation.announcer.custom_alert_message
+ else
+ announcement += "
[span_alert("[html_encode(text)]")]
"
announcement += "
"
- var/s = sound(get_announcer_sound(sound))
+ var/s = sound(sound)
for(var/mob/M in GLOB.player_list)
if(!isnewplayer(M) && M.can_hear())
to_chat(M, announcement)
if(M.client.prefs.toggles & SOUND_ANNOUNCEMENTS)
SEND_SOUND(M, s)
-/proc/get_announcer_sound(soundid)
- if(isfile(soundid))
- return soundid
- else if(!istext(soundid))
- CRASH("Invalid type passed to get_announcer_sound()")
- switch(GLOB.announcertype) //These are all individually hardcoded to allow the announcer sounds to be included in the rsc, reducing lag from sending resources midgame.
- if("classic")
- switch(soundid)
- if("aimalf")
- . = 'sound/announcer/classic/aimalf.ogg'
- if("aliens")
- . = 'sound/announcer/classic/aliens.ogg'
- if("animes")
- . = 'sound/announcer/classic/animes.ogg'
- if("attention")
- . = 'sound/announcer/classic/attention.ogg'
- if("commandreport")
- . = 'sound/announcer/classic/commandreport.ogg'
- if("granomalies")
- . = 'sound/announcer/classic/granomalies.ogg'
- if("intercept")
- . = 'sound/announcer/classic/intercept.ogg'
- if("ionstorm")
- . = 'sound/announcer/classic/ionstorm.ogg'
- if("meteors")
- . = 'sound/announcer/classic/meteors.ogg'
- if("newAI")
- . = 'sound/announcer/classic/newAI.ogg'
- if("outbreak5")
- . = 'sound/announcer/classic/outbreak5.ogg'
- if("outbreak7")
- . = 'sound/announcer/classic/outbreak7.ogg'
- if("poweroff")
- . = 'sound/announcer/classic/poweroff.ogg'
- if("poweron")
- . = 'sound/announcer/classic/poweron.ogg'
- if("radiation")
- . = 'sound/announcer/classic/radiation.ogg'
- if("shuttlecalled")
- . = 'sound/announcer/classic/shuttlecalled.ogg'
- if("shuttledock")
- . = 'sound/announcer/classic/shuttledock.ogg'
- if("shuttlerecalled")
- . = 'sound/announcer/classic/shuttlerecalled.ogg'
- if("spanomalies")
- . = 'sound/announcer/classic/spanomalies.ogg'
- if("welcome")
- . = 'sound/announcer/classic/welcome.ogg'
- if("medibot")
- switch(soundid)
- if("aimalf")
- . = 'sound/announcer/classic/aimalf.ogg'
- if("aliens")
- . = 'sound/announcer/medibot/aliens.ogg'
- if("animes")
- . = 'sound/announcer/medibot/animes.ogg'
- if("attention")
- . = 'sound/announcer/medibot/attention.ogg'
- if("commandreport")
- . = 'sound/announcer/medibot/commandreport.ogg'
- if("granomalies")
- . = 'sound/announcer/medibot/granomalies.ogg'
- if("intercept")
- . = 'sound/announcer/medibot/intercept.ogg'
- if("ionstorm")
- . = 'sound/announcer/medibot/ionstorm.ogg'
- if("meteors")
- . = 'sound/announcer/medibot/meteors.ogg'
- if("newAI")
- . = 'sound/announcer/medibot/newAI.ogg'
- if("outbreak5")
- . = 'sound/announcer/medibot/outbreak5.ogg'
- if("outbreak7")
- . = 'sound/announcer/medibot/outbreak7.ogg'
- if("poweroff")
- . = 'sound/announcer/medibot/poweroff.ogg'
- if("poweron")
- . = 'sound/announcer/medibot/poweron.ogg'
- if("radiation")
- . = 'sound/announcer/medibot/radiation.ogg'
- if("shuttlecalled")
- . = 'sound/announcer/medibot/shuttlecalled.ogg'
- if("shuttledock")
- . = 'sound/announcer/medibot/shuttledocked.ogg'
- if("shuttlerecalled")
- . = 'sound/announcer/medibot/shuttlerecalled.ogg'
- if("spanomalies")
- . = 'sound/announcer/medibot/spanomalies.ogg'
- if("welcome")
- . = 'sound/announcer/medibot/welcome.ogg'
-
/**
* Summon the crew for an emergency meeting
*
@@ -170,7 +81,7 @@
title = "Classified [command_name()] Update"
if(announce)
- priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", "commandreport", has_important_message = TRUE)
+ priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", SSstation.announcer.get_rand_report_sound(), has_important_message = TRUE)
var/datum/comm_message/M = new
M.title = title
diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm
index 1e3f8d25eb..f10e3b7655 100644
--- a/code/_globalvars/lists/mapping.dm
+++ b/code/_globalvars/lists/mapping.dm
@@ -37,6 +37,7 @@ GLOBAL_LIST_EMPTY(emergencyresponseteamspawn)
GLOBAL_LIST_EMPTY(servant_spawns) //Servants of Ratvar spawn here
GLOBAL_LIST_EMPTY(city_of_cogs_spawns) //Anyone entering the City of Cogs spawns here
GLOBAL_LIST_EMPTY(ruin_landmarks)
+GLOBAL_LIST_EMPTY(bar_areas)
//away missions
GLOBAL_LIST_EMPTY(vr_spawnpoints)
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index b039e6d114..49c22bce01 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -6,8 +6,6 @@ GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY"))
GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013???
-GLOBAL_VAR_INIT(announcertype, "standard")
-
// For FTP requests. (i.e. downloading runtime logs.)
// However it'd be ok to use for accessing attack logs and such too, which are even laggier.
GLOBAL_VAR_INIT(fileaccess_timer, 0)
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index db780ca05b..311f8bab8d 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -246,7 +246,7 @@
//used to initialize the subsystem AFTER the map has loaded
/datum/controller/subsystem/Initialize(start_timeofday)
initialized = TRUE
- // SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday)
+ SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday)
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!"
to_chat(world, span_boldannounce("[msg]"))
diff --git a/code/controllers/subsystem/economy.dm b/code/controllers/subsystem/economy.dm
index 47b00d1125..3f594e170e 100644
--- a/code/controllers/subsystem/economy.dm
+++ b/code/controllers/subsystem/economy.dm
@@ -47,6 +47,10 @@ SUBSYSTEM_DEF(economy)
"rainbow" = 1000)
var/list/bank_accounts = list() //List of normal accounts (not department accounts)
var/list/dep_cards = list()
+ ///The modifier multiplied to the value of bounties paid out.
+ var/bounty_modifier = 1
+ ///The modifier multiplied to the value of cargo pack prices.
+ var/pack_price_modifier = 1
/datum/controller/subsystem/economy/Initialize(timeofday)
var/budget_to_hand_out = round(budget_pool / department_accounts.len)
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 98ebcf4606..643811368c 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -434,20 +434,7 @@ SUBSYSTEM_DEF(job)
//If we joined at roundstart we should be positioned at our workstation
if(!joined_late)
- var/obj/S = null
- for(var/obj/effect/landmark/start/sloc in GLOB.start_landmarks_list)
- if(!sloc.job_spawnpoint)
- continue
- if(sloc.name != rank)
- S = sloc //so we can revert to spawning them on top of eachother if something goes wrong
- continue
- if(locate(/mob/living) in sloc.loc)
- continue
- S = sloc
- sloc.used = TRUE
- break
- if(length(GLOB.jobspawn_overrides[rank]))
- S = pick(GLOB.jobspawn_overrides[rank])
+ var/atom/S = job.get_roundstart_spawn_point(H)
if(S)
S.JoinPlayerHere(H, FALSE)
if(!S) //if there isn't a spawnpoint send them to latejoin, if there's no latejoin go yell at your mapper
@@ -494,7 +481,7 @@ SUBSYSTEM_DEF(job)
if(job && H)
if(job.dresscodecompliant)// CIT CHANGE - dress code compliance
equip_loadout(N, H) // CIT CHANGE - allows players to spawn with loadout items
- job.after_spawn(H, M, joined_late) // note: this happens before the mob has a key! M will always have a client, H might not.
+ job.after_spawn(H, M.client, joined_late) // note: this happens before the mob has a key! M will always have a client, H might not.
equip_loadout(N, H, TRUE)//CIT CHANGE - makes players spawn with in-backpack loadout items properly. A little hacky but it works
var/list/tcg_cards
@@ -768,6 +755,35 @@ SUBSYSTEM_DEF(job)
return FALSE
job.current_positions = max(0, job.current_positions - 1)
+/datum/controller/subsystem/job/proc/get_last_resort_spawn_points()
+ //bad mojo
+ var/area/shuttle/arrival/arrivals_area = GLOB.areas_by_type[/area/shuttle/arrival]
+ if(arrivals_area)
+ //first check if we can find a chair
+ var/obj/structure/chair/shuttle_chair = locate() in arrivals_area
+ if(shuttle_chair)
+ return shuttle_chair
+
+ //last hurrah
+ var/list/turf/available_turfs = list()
+ for(var/turf/arrivals_turf in arrivals_area)
+ if(!is_blocked_turf(arrivals_turf, TRUE))
+ available_turfs += arrivals_turf
+ if(length(available_turfs))
+ return pick(available_turfs)
+
+ //pick an open spot on arrivals and dump em
+ var/list/arrivals_turfs = shuffle(get_area_turfs(/area/shuttle/arrival))
+ if(length(arrivals_turfs))
+ for(var/turf/arrivals_turf in arrivals_turfs)
+ if(!is_blocked_turf(arrivals_turf, TRUE))
+ return arrivals_turf
+ //last chance, pick ANY spot on arrivals and dump em
+ return pick(arrivals_turfs)
+
+ stack_trace("Unable to find last resort spawn point.")
+ return GET_ERROR_ROOM
+
///////////////////////////////////
//Keeps track of all living heads//
///////////////////////////////////
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index d0a6336223..5abe772c18 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -81,7 +81,6 @@ SUBSYSTEM_DEF(mapping)
to_chat(world, "Unable to load next or default map config, defaulting to Box Station")
config = old_config
GLOB.year_integer += config.year_offset
- GLOB.announcertype = (config.announcertype == "standard" ? (prob(1) ? "medibot" : "classic") : config.announcertype)
initialize_biomes()
loadWorld()
repopulate_sorted_areas()
diff --git a/code/controllers/subsystem/processing/station.dm b/code/controllers/subsystem/processing/station.dm
new file mode 100644
index 0000000000..f812ea8c4a
--- /dev/null
+++ b/code/controllers/subsystem/processing/station.dm
@@ -0,0 +1,84 @@
+PROCESSING_SUBSYSTEM_DEF(station)
+ name = "Station"
+ init_order = INIT_ORDER_STATION
+ flags = SS_BACKGROUND
+ runlevels = RUNLEVEL_GAME
+ wait = 5 SECONDS
+
+ ///A list of currently active station traits
+ var/list/station_traits = list()
+ ///Assoc list of trait type || assoc list of traits with weighted value. Used for picking traits from a specific category.
+ var/list/selectable_traits_by_types = list(STATION_TRAIT_POSITIVE = list(), STATION_TRAIT_NEUTRAL = list(), STATION_TRAIT_NEGATIVE = list())
+ ///Currently active announcer. Starts as a type but gets initialized after traits are selected
+ var/datum/centcom_announcer/announcer = /datum/centcom_announcer/default
+
+/datum/controller/subsystem/processing/station/Initialize(timeofday)
+
+ //If doing unit tests we don't do none of that trait shit ya know?
+ #ifndef UNIT_TESTS
+ SetupTraits()
+ #endif
+
+ announcer = new announcer() //Initialize the station's announcer datum
+
+ return ..()
+
+///Rolls for the amount of traits and adds them to the traits list
+/datum/controller/subsystem/processing/station/proc/SetupTraits()
+ if (fexists(FUTURE_STATION_TRAITS_FILE))
+ var/forced_traits_contents = file2text(FUTURE_STATION_TRAITS_FILE)
+ fdel(FUTURE_STATION_TRAITS_FILE)
+
+ var/list/forced_traits_text_paths = json_decode(forced_traits_contents)
+ forced_traits_text_paths = SANITIZE_LIST(forced_traits_text_paths)
+
+ for (var/trait_text_path in forced_traits_text_paths)
+ var/station_trait_path = text2path(trait_text_path)
+ if (!ispath(station_trait_path, /datum/station_trait) || station_trait_path == /datum/station_trait)
+ var/message = "Invalid station trait path [station_trait_path] was requested in the future station traits!"
+ log_game(message)
+ message_admins(message)
+ continue
+
+ setup_trait(station_trait_path)
+
+ return
+
+ for(var/i in subtypesof(/datum/station_trait))
+ var/datum/station_trait/trait_typepath = i
+
+ // If forced, (probably debugging), just set it up now, keep it out of the pool.
+ if(initial(trait_typepath.force))
+ setup_trait(trait_typepath)
+ continue
+
+ if(initial(trait_typepath.trait_flags) & STATION_TRAIT_ABSTRACT || initial(trait_typepath.weight) == 0)
+ continue //Dont add abstract ones to it
+ selectable_traits_by_types[initial(trait_typepath.trait_type)][trait_typepath] = initial(trait_typepath.weight)
+
+ var/positive_trait_count = pick(20;0, 5;1, 1;2)
+ var/neutral_trait_count = pick(10;0, 10;1, 3;2)
+ var/negative_trait_count = pick(20;0, 5;1, 1;2)
+ can_fire = FALSE
+ pick_traits(STATION_TRAIT_POSITIVE, positive_trait_count)
+ pick_traits(STATION_TRAIT_NEUTRAL, neutral_trait_count)
+ pick_traits(STATION_TRAIT_NEGATIVE, negative_trait_count)
+
+///Picks traits of a specific category (e.g. bad or good) and a specified amount, then initializes them and adds them to the list of traits.
+/datum/controller/subsystem/processing/station/proc/pick_traits(trait_sign, amount)
+ if(!amount)
+ return
+ for(var/iterator in 1 to amount)
+ var/datum/station_trait/trait_type = pickweight(selectable_traits_by_types[trait_sign]) //Rolls from the table for the specific trait type
+ setup_trait(trait_type)
+
+///Creates a given trait of a specific type, while also removing any blacklisted ones from the future pool.
+/datum/controller/subsystem/processing/station/proc/setup_trait(datum/station_trait/trait_type)
+ var/datum/station_trait/trait_instance = new trait_type()
+ can_fire = can_fire || trait_instance.trait_processes
+ station_traits += trait_instance
+ if(!trait_instance.blacklist)
+ return
+ for(var/i in trait_instance.blacklist)
+ var/datum/station_trait/trait_to_remove = i
+ selectable_traits_by_types[initial(trait_to_remove.trait_type)] -= trait_to_remove
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index d171366760..d755003f3b 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -301,7 +301,7 @@ SUBSYSTEM_DEF(ticker)
SSdbcore.SetRoundStart()
to_chat(world, "Welcome to [station_name()], enjoy your stay!")
- SEND_SOUND(world, sound(get_announcer_sound("welcome")))
+ SEND_SOUND(world, sound(SSstation.announcer.get_rand_welcome_sound()))
current_state = GAME_STATE_PLAYING
Master.SetRunLevel(RUNLEVEL_GAME)
diff --git a/code/controllers/subsystem/traumas.dm b/code/controllers/subsystem/traumas.dm
index edf10f89c4..7f1ece6937 100644
--- a/code/controllers/subsystem/traumas.dm
+++ b/code/controllers/subsystem/traumas.dm
@@ -169,7 +169,7 @@ SUBSYSTEM_DEF(traumas)
/obj/structure/fluff/empty_sleeper/syndicate, /obj/item/implant/radio/syndicate, /obj/item/clothing/head/helmet/space/syndicate, /obj/machinery/nuclearbomb/syndicate, /obj/item/grenade/syndieminibomb, /obj/item/storage/backpack/duffelbag/syndie, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver,
/obj/item/gun/ballistic/automatic/shotgun/bulldog, /obj/item/gun/ballistic/automatic/c20r, /obj/item/gun/ballistic/automatic/m90, /obj/item/gun/ballistic/automatic/l6_saw, /obj/item/storage/belt/grenade/full, /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate, /obj/item/gun/energy/kinetic_accelerator/crossbow,
/obj/item/melee/transforming/energy/sword/saber, /obj/item/dualsaber, /obj/item/melee/powerfist, /obj/item/storage/box/syndie_kit, /obj/item/grenade/spawnergrenade/manhacks, /obj/item/grenade/chem_grenade/bioterrorfoam, /obj/item/reagent_containers/spray/chemsprayer/bioterror, /obj/item/ammo_box/magazine/m10mm,
- /obj/item/ammo_box/magazine/pistolm9mm, /obj/item/ammo_box/a357, /obj/item/ammo_box/magazine/m12g, /obj/item/ammo_box/magazine/mm195x129, /obj/item/antag_spawner/nuke_ops, /obj/vehicle/sealed/mecha/combat/gygax/dark, /obj/vehicle/sealed/mecha/combat/marauder/mauler, /obj/item/soap/syndie, /obj/item/gun/syringe/syndicate, /obj/item/cartridge/virus/syndicate,
+ /obj/item/ammo_box/magazine/pistolm9mm, /obj/item/ammo_box/a357, /obj/item/ammo_box/magazine/m12g, /obj/item/ammo_box/magazine/mm712x82, /obj/item/antag_spawner/nuke_ops, /obj/vehicle/sealed/mecha/combat/gygax/dark, /obj/vehicle/sealed/mecha/combat/marauder/mauler, /obj/item/soap/syndie, /obj/item/gun/syringe/syndicate, /obj/item/cartridge/virus/syndicate,
/obj/item/cartridge/virus/frame, /obj/item/chameleon, /obj/item/storage/box/syndie_kit/cutouts, /obj/item/clothing/suit/space/hardsuit/syndi, /obj/item/card/emag, /obj/item/storage/toolbox/syndicate, /obj/item/storage/book/bible/syndicate, /obj/item/encryptionkey/binary, /obj/item/encryptionkey/syndicate, /obj/item/aiModule/syndicate,
/obj/item/clothing/shoes/magboots/syndie, /obj/item/powersink, /obj/item/sbeacondrop, /obj/item/sbeacondrop/bomb, /obj/item/syndicatedetonator, /obj/item/shield/energy, /obj/item/assault_pod, /obj/item/slimepotion/slime/sentience/nuclear, /obj/item/stack/telecrystal, /obj/item/jammer, /obj/item/codespeak_manual/unlimited,
/obj/item/toy/cards/deck/syndicate, /obj/item/storage/secure/briefcase/syndie, /obj/item/storage/fancy/cigarettes/cigpack_syndicate, /obj/item/toy/syndicateballoon, /obj/item/clothing/gloves/fingerless/pugilist/rapid, /obj/item/paper/fluff/ruins/thederelict/syndie_mission, /obj/item/organ/cyberimp/eyes/hud/security/syndicate, /obj/item/clothing/head/HoS/syndicate,
diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm
index 15115b62c0..b2bffb9653 100644
--- a/code/datums/ai_laws.dm
+++ b/code/datums/ai_laws.dm
@@ -223,6 +223,11 @@
/datum/ai_laws/proc/set_laws_config()
var/list/law_ids = CONFIG_GET(keyed_list/random_laws)
+
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_UNIQUE_AI))
+ pick_weighted_lawset()
+ return
+
switch(CONFIG_GET(number/default_laws))
if(0)
add_inherent_law("You may not injure a human being or cause one to come to harm.")
diff --git a/code/datums/announcers/_announcer.dm b/code/datums/announcers/_announcer.dm
new file mode 100644
index 0000000000..dab790438c
--- /dev/null
+++ b/code/datums/announcers/_announcer.dm
@@ -0,0 +1,23 @@
+///Data holder for the announcers that can be used in a game, this can be used to have alternative announcements outside of the default e.g.the intern
+/datum/centcom_announcer
+ ///Roundshift start audio
+ var/welcome_sounds = list()
+ ///Sounds made when announcement is receivedc
+ var/alert_sounds = list()
+ ///Sounds made when command report is received
+ var/command_report_sounds = list()
+ ///Event audio, can be used for specific event announcements and is assoc key - sound. If no sound is found the default is used.area
+ var/event_sounds = list()
+ ///Override this to have a custom message to show instead of the normal priority announcement
+ var/custom_alert_message
+
+
+/datum/centcom_announcer/proc/get_rand_welcome_sound()
+ return pick(welcome_sounds)
+
+
+/datum/centcom_announcer/proc/get_rand_alert_sound()
+ return pick(alert_sounds)
+
+/datum/centcom_announcer/proc/get_rand_report_sound()
+ return pick(command_report_sounds)
diff --git a/code/datums/announcers/default_announcer.dm b/code/datums/announcers/default_announcer.dm
new file mode 100644
index 0000000000..4ab045e175
--- /dev/null
+++ b/code/datums/announcers/default_announcer.dm
@@ -0,0 +1,21 @@
+/datum/centcom_announcer/default
+ welcome_sounds = list('sound/announcer/classic/welcome.ogg')
+ alert_sounds = list('sound/announcer/classic/attention.ogg')
+ command_report_sounds = list('sound/announcer/classic/commandreport.ogg')
+ event_sounds = list(ANNOUNCER_AIMALF = 'sound/announcer/classic/aimalf.ogg',
+ ANNOUNCER_ALIENS = 'sound/announcer/classic/aliens.ogg',
+ ANNOUNCER_ANIMES = 'sound/announcer/classic/animes.ogg',
+ ANNOUNCER_GRANOMALIES = 'sound/announcer/classic/granomalies.ogg',
+ ANNOUNCER_INTERCEPT = 'sound/announcer/classic/intercept.ogg',
+ ANNOUNCER_IONSTORM = 'sound/announcer/classic/ionstorm.ogg',
+ ANNOUNCER_METEORS = 'sound/announcer/classic/meteors.ogg',
+ ANNOUNCER_NEWAI = 'sound/announcer/classic/newai.ogg',
+ ANNOUNCER_OUTBREAK5 = 'sound/announcer/classic/outbreak5.ogg',
+ ANNOUNCER_OUTBREAK7 = 'sound/announcer/classic/outbreak7.ogg',
+ ANNOUNCER_POWEROFF = 'sound/announcer/classic/poweroff.ogg',
+ ANNOUNCER_POWERON = 'sound/announcer/classic/poweron.ogg',
+ ANNOUNCER_RADIATION = 'sound/announcer/classic/radiation.ogg',
+ ANNOUNCER_SHUTTLECALLED = 'sound/announcer/classic/shuttlecalled.ogg',
+ ANNOUNCER_SHUTTLEDOCK = 'sound/announcer/classic/shuttledock.ogg',
+ ANNOUNCER_SHUTTLERECALLED = 'sound/announcer/classic/shuttlerecalled.ogg',
+ ANNOUNCER_SPANOMALIES = 'sound/announcer/classic/spanomalies.ogg')
diff --git a/code/datums/announcers/intern_announcer.dm b/code/datums/announcers/intern_announcer.dm
new file mode 100644
index 0000000000..25065f38b0
--- /dev/null
+++ b/code/datums/announcers/intern_announcer.dm
@@ -0,0 +1,47 @@
+/datum/centcom_announcer/intern
+ welcome_sounds = list('sound/announcer/intern/welcome/1.ogg',
+ 'sound/announcer/intern/welcome/2.ogg',
+ 'sound/announcer/intern/welcome/3.ogg',
+ 'sound/announcer/intern/welcome/4.ogg',
+ 'sound/announcer/intern/welcome/5.ogg',
+ 'sound/announcer/intern/welcome/6.ogg')
+
+ alert_sounds = list('sound/announcer/intern/alerts/1.ogg',
+ 'sound/announcer/intern/alerts/2.ogg',
+ 'sound/announcer/intern/alerts/3.ogg',
+ 'sound/announcer/intern/alerts/4.ogg',
+ 'sound/announcer/intern/alerts/5.ogg',
+ 'sound/announcer/intern/alerts/6.ogg',
+ 'sound/announcer/intern/alerts/7.ogg',
+ 'sound/announcer/intern/alerts/8.ogg',
+ 'sound/announcer/intern/alerts/9.ogg',
+ 'sound/announcer/intern/alerts/10.ogg',
+ 'sound/announcer/intern/alerts/11.ogg',
+ 'sound/announcer/intern/alerts/12.ogg',
+ 'sound/announcer/intern/alerts/13.ogg',
+ 'sound/announcer/intern/alerts/14.ogg')
+
+
+ command_report_sounds = list('sound/announcer/intern/commandreport/1.ogg',
+ 'sound/announcer/intern/commandreport/2.ogg',
+ 'sound/announcer/intern/commandreport/3.ogg')
+
+ event_sounds = list(ANNOUNCER_AIMALF = 'sound/announcer/classic/aimalf.ogg',
+ ANNOUNCER_ALIENS = 'sound/announcer/intern/aliens.ogg',
+ ANNOUNCER_ANIMES = 'sound/announcer/intern/animes.ogg',
+ ANNOUNCER_GRANOMALIES = 'sound/announcer/intern/granomalies.ogg',
+ ANNOUNCER_INTERCEPT = 'sound/announcer/intern/intercept.ogg',
+ ANNOUNCER_IONSTORM = 'sound/announcer/intern/ionstorm.ogg',
+ ANNOUNCER_METEORS = 'sound/announcer/intern/meteors.ogg',
+ ANNOUNCER_NEWAI = 'sound/announcer/classic/newai.ogg',
+ ANNOUNCER_OUTBREAK5 = 'sound/announcer/intern/outbreak5.ogg',
+ ANNOUNCER_OUTBREAK7 = 'sound/announcer/intern/outbreak7.ogg',
+ ANNOUNCER_POWEROFF = 'sound/announcer/intern/poweroff.ogg',
+ ANNOUNCER_POWERON = 'sound/announcer/intern/poweron.ogg',
+ ANNOUNCER_RADIATION = 'sound/announcer/intern/radiation.ogg',
+ ANNOUNCER_SHUTTLECALLED = 'sound/announcer/intern/shuttlecalled.ogg',
+ ANNOUNCER_SHUTTLEDOCK = 'sound/announcer/intern/shuttledock.ogg',
+ ANNOUNCER_SHUTTLERECALLED = 'sound/announcer/intern/shuttlerecalled.ogg',
+ ANNOUNCER_SPANOMALIES = 'sound/announcer/intern/spanomalies.ogg')
+
+ custom_alert_message = "
Please stand by for an important message from our new intern.
"
diff --git a/code/datums/announcers/medbot_announcer.dm b/code/datums/announcers/medbot_announcer.dm
new file mode 100644
index 0000000000..ed03ac3fc9
--- /dev/null
+++ b/code/datums/announcers/medbot_announcer.dm
@@ -0,0 +1,22 @@
+/datum/centcom_announcer/medbot
+ welcome_sounds = list('sound/announcer/medibot/welcome.ogg',
+ 'sound/announcer/medibot/newAI.ogg')
+ alert_sounds = list('sound/announcer/medibot/attention.ogg')
+ command_report_sounds = list('sound/announcer/medibot/commandreport.ogg')
+ event_sounds = list(ANNOUNCER_AIMALF = 'sound/announcer/classic/aimalf.ogg',
+ ANNOUNCER_ALIENS = 'sound/announcer/medibot/aliens.ogg',
+ ANNOUNCER_ANIMES = 'sound/announcer/medibot/animes.ogg',
+ ANNOUNCER_GRANOMALIES = 'sound/announcer/medibot/granomalies.ogg',
+ ANNOUNCER_INTERCEPT = 'sound/announcer/medibot/intercept.ogg',
+ ANNOUNCER_IONSTORM = 'sound/announcer/medibot/ionstorm.ogg',
+ ANNOUNCER_METEORS = 'sound/announcer/medibot/meteors.ogg',
+ ANNOUNCER_NEWAI = 'sound/announcer/medibot/newai.ogg',
+ ANNOUNCER_OUTBREAK5 = 'sound/announcer/medibot/outbreak5.ogg',
+ ANNOUNCER_OUTBREAK7 = 'sound/announcer/medibot/outbreak7.ogg',
+ ANNOUNCER_POWEROFF = 'sound/announcer/medibot/poweroff.ogg',
+ ANNOUNCER_POWERON = 'sound/announcer/medibot/poweron.ogg',
+ ANNOUNCER_RADIATION = 'sound/announcer/medibot/radiation.ogg',
+ ANNOUNCER_SHUTTLECALLED = 'sound/announcer/medibot/shuttlecalled.ogg',
+ ANNOUNCER_SHUTTLEDOCK = 'sound/announcer/medibot/shuttledocked.ogg',
+ ANNOUNCER_SHUTTLERECALLED = 'sound/announcer/medibot/shuttlerecalled.ogg',
+ ANNOUNCER_SPANOMALIES = 'sound/announcer/medibot/spanomalies.ogg')
diff --git a/code/datums/atmosphere/_atmosphere.dm b/code/datums/atmosphere/_atmosphere.dm
index 7fe8e44d75..a516bd46bd 100644
--- a/code/datums/atmosphere/_atmosphere.dm
+++ b/code/datums/atmosphere/_atmosphere.dm
@@ -24,6 +24,9 @@
var/target_pressure = rand(minimum_pressure, maximum_pressure)
var/pressure_scale = target_pressure / maximum_pressure
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_UNNATURAL_ATMOSPHERE))
+ restricted_chance = restricted_chance + 40
+
// First let's set up the gasmix and base gases for this template
// We make the string from a gasmix in this proc because gases need to calculate their pressure
var/datum/gas_mixture/gasmix = new
diff --git a/code/datums/atmosphere/planetary.dm b/code/datums/atmosphere/planetary.dm
index efa57b4e5f..2324c4780a 100644
--- a/code/datums/atmosphere/planetary.dm
+++ b/code/datums/atmosphere/planetary.dm
@@ -15,7 +15,6 @@
GAS_BZ=0.1,
GAS_BROMINE=0.1
)
- restricted_chance = 30
minimum_pressure = HAZARD_LOW_PRESSURE + 10
maximum_pressure = LAVALAND_EQUIPMENT_EFFECT_PRESSURE - 1
@@ -62,7 +61,7 @@
GAS_METHYL_BROMIDE=0.1,
GAS_HYDROGEN=0.1
)
- restricted_chance = 10
+ restricted_chance = 5
minimum_pressure = HAZARD_LOW_PRESSURE + 10
maximum_pressure = LAVALAND_EQUIPMENT_EFFECT_PRESSURE - 1
diff --git a/code/datums/components/multiple_lives.dm b/code/datums/components/multiple_lives.dm
new file mode 100644
index 0000000000..3f4418a9d1
--- /dev/null
+++ b/code/datums/components/multiple_lives.dm
@@ -0,0 +1,47 @@
+/**
+ * A simple component that spawns a mob of the same type and transfers itself to it when parent dies.
+ * For more complex behaviors, use the COMSIG_ON_MULTIPLE_LIVES_RESPAWN comsig.
+ */
+/datum/component/multiple_lives
+ can_transfer = TRUE
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ /// The number of respawns the living mob has left.
+ var/lives_left
+
+/datum/component/multiple_lives/Initialize(lives_left)
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.lives_left = lives_left
+
+/datum/component/multiple_lives/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/respawn)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+
+/datum/component/multiple_lives/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_MOB_DEATH, COMSIG_PARENT_EXAMINE))
+
+/datum/component/multiple_lives/proc/respawn(mob/living/source, gibbed)
+ SIGNAL_HANDLER
+ if(source.suiciding) //Freed from this mortail coil.
+ qdel(src)
+ return
+ var/mob/living/respawned_mob = new source.type (source.drop_location())
+ source.mind?.transfer_to(respawned_mob)
+ lives_left--
+ if(lives_left <= 0)
+ qdel(src)
+ source.TransferComponents(respawned_mob)
+ SEND_SIGNAL(source, COMSIG_ON_MULTIPLE_LIVES_RESPAWN, respawned_mob, gibbed, lives_left)
+
+/datum/component/multiple_lives/proc/on_examine(mob/living/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ if(isobserver(user) || source == user)
+ examine_list += "[source.p_theyve(TRUE)] [lives_left] extra lives left."
+
+/datum/component/multiple_lives/InheritComponent(datum/component/multiple_lives/new_comp , lives_left)
+ src.lives_left += new_comp ? new_comp.lives_left : lives_left
+
+/datum/component/multiple_lives/PostTransfer()
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
diff --git a/code/datums/components/twitch_plays.dm b/code/datums/components/twitch_plays.dm
index aadbd58b4b..a04592e1af 100644
--- a/code/datums/components/twitch_plays.dm
+++ b/code/datums/components/twitch_plays.dm
@@ -82,12 +82,14 @@
var/move_delay = 2
var/last_move = 0
-/datum/component/twitch_plays/simple_movement/auto/Initialize(...)
+/datum/component/twitch_plays/simple_movement/auto/Initialize(move_delay)
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
. = ..()
if(. & COMPONENT_INCOMPATIBLE)
return
+ if(!isnull(move_delay))
+ src.move_delay = move_delay
START_PROCESSING(SSfastprocess, src)
/datum/component/twitch_plays/simple_movement/auto/Destroy(force, silent)
@@ -95,10 +97,10 @@
return ..()
/datum/component/twitch_plays/simple_movement/auto/process()
+ if(world.time < (last_move + move_delay))
+ return
var/dir = fetch_data(null, TRUE)
if(!dir)
return
- if(world.time < (last_move + move_delay))
- return
last_move = world.time
step(parent, dir)
diff --git a/code/datums/station_traits/_station_trait.dm b/code/datums/station_traits/_station_trait.dm
new file mode 100644
index 0000000000..5fbf8611d5
--- /dev/null
+++ b/code/datums/station_traits/_station_trait.dm
@@ -0,0 +1,57 @@
+///Base class of station traits. These are used to influence rounds in one way or the other by influencing the levers of the station.
+/datum/station_trait
+ ///Name of the trait
+ var/name = "unnamed station trait"
+ ///The type of this trait. Used to classify how this trait influences the station
+ var/trait_type = STATION_TRAIT_NEUTRAL
+ ///Whether or not this trait uses process()
+ var/trait_processes = FALSE
+ ///Chance relative to other traits of its type to be picked
+ var/weight = 10
+ ///Whether this trait is always enabled; generally used for debugging
+ var/force = FALSE
+ ///Does this trait show in the centcom report?
+ var/show_in_report = FALSE
+ ///What message to show in the centcom report?
+ var/report_message
+ ///What code-trait does this station trait give? gives none if null
+ var/trait_to_give
+ ///What traits are incompatible with this one?
+ var/blacklist
+ ///Extra flags for station traits such as it being abstract
+ var/trait_flags
+ /// Whether or not this trait can be reverted by an admin
+ var/can_revert = TRUE
+
+/datum/station_trait/New()
+ . = ..()
+
+ RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, .proc/on_round_start)
+
+ if(trait_processes)
+ START_PROCESSING(SSstation, src)
+ if(trait_to_give)
+ ADD_TRAIT(SSstation, trait_to_give, STATION_TRAIT)
+
+/datum/station_trait/Destroy()
+ SSstation.station_traits -= src
+ return ..()
+
+/// Proc ran when round starts. Use this for roundstart effects.
+/datum/station_trait/proc/on_round_start()
+ SIGNAL_HANDLER
+ return
+
+///type of info the centcom report has on this trait, if any.
+/datum/station_trait/proc/get_report()
+ return "[name] - [report_message]"
+
+/// Will attempt to revert the station trait, used by admins.
+/datum/station_trait/proc/revert()
+ if (!can_revert)
+ CRASH("revert() was called on [type], which can't be reverted!")
+
+ if (trait_to_give)
+ REMOVE_TRAIT(SSstation, trait_to_give, STATION_TRAIT)
+
+ qdel(src)
diff --git a/code/datums/station_traits/admin_panel.dm b/code/datums/station_traits/admin_panel.dm
new file mode 100644
index 0000000000..02eca48b54
--- /dev/null
+++ b/code/datums/station_traits/admin_panel.dm
@@ -0,0 +1,133 @@
+/// Opens the station traits admin panel
+/datum/admins/proc/station_traits_panel()
+ set name = "Modify Station Traits"
+ set category = "Admin.Events"
+
+ var/static/datum/station_traits_panel/station_traits_panel = new
+ station_traits_panel.ui_interact(usr)
+
+/datum/station_traits_panel
+ var/static/list/future_traits
+
+/datum/station_traits_panel/ui_data(mob/user)
+ var/list/data = list()
+
+ data["too_late_to_revert"] = too_late_to_revert()
+
+ var/list/current_station_traits = list()
+ for (var/datum/station_trait/station_trait as anything in SSstation.station_traits)
+ current_station_traits += list(list(
+ "name" = station_trait.name,
+ "can_revert" = station_trait.can_revert,
+ "ref" = REF(station_trait),
+ ))
+
+ data["current_traits"] = current_station_traits
+ data["future_station_traits"] = future_traits
+
+ return data
+
+/datum/station_traits_panel/ui_static_data(mob/user)
+ var/list/data = list()
+
+ var/list/valid_station_traits = list()
+
+ for (var/datum/station_trait/station_trait_path as anything in subtypesof(/datum/station_trait))
+ valid_station_traits += list(list(
+ "name" = initial(station_trait_path.name),
+ "path" = station_trait_path,
+ ))
+
+ data["valid_station_traits"] = valid_station_traits
+
+ return data
+
+/datum/station_traits_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if (.)
+ return
+
+ switch (action)
+ if ("revert")
+ var/ref = params["ref"]
+ if (!ref)
+ return TRUE
+
+ var/datum/station_trait/station_trait = locate(ref)
+
+ if (!istype(station_trait))
+ return TRUE
+
+ if (too_late_to_revert())
+ to_chat(usr, span_warning("It's too late to revert station traits, the round has already started!"))
+ return TRUE
+
+ if (!station_trait.can_revert)
+ stack_trace("[station_trait.type] can't be reverted, but was requested anyway.")
+ return TRUE
+
+ var/message = "[key_name(usr)] reverted the station trait [station_trait.name] ([station_trait.type])"
+ log_admin(message)
+ message_admins(message)
+
+ station_trait.revert()
+
+ return TRUE
+ if ("setup_future_traits")
+ if (too_late_for_future_traits())
+ to_chat(usr, span_warning("It's too late to add future station traits, the round is already over!"))
+ return TRUE
+
+ var/list/new_future_traits = list()
+ var/list/station_trait_names = list()
+
+ for (var/station_trait_text in params["station_traits"])
+ var/datum/station_trait/station_trait_path = text2path(station_trait_text)
+ if (!ispath(station_trait_path, /datum/station_trait) || station_trait_path == /datum/station_trait)
+ log_admin("[key_name(usr)] tried to set an invalid future station trait: [station_trait_text]")
+ to_chat(usr, span_warning("Invalid future station trait: [station_trait_text]"))
+ return TRUE
+
+ station_trait_names += initial(station_trait_path.name)
+
+ new_future_traits += list(list(
+ "name" = initial(station_trait_path.name),
+ "path" = station_trait_path,
+ ))
+
+ var/message = "[key_name(usr)] has prepared the following station traits for next round: [station_trait_names.Join(", ") || "None"]"
+ log_admin(message)
+ message_admins(message)
+
+ future_traits = new_future_traits
+ rustg_file_write(json_encode(params["station_traits"]), FUTURE_STATION_TRAITS_FILE)
+
+ return TRUE
+ if ("clear_future_traits")
+ if (!future_traits)
+ to_chat(usr, span_warning("There are no future station traits."))
+ return TRUE
+
+ var/message = "[key_name(usr)] has cleared the station traits for next round."
+ log_admin(message)
+ message_admins(message)
+
+ fdel(FUTURE_STATION_TRAITS_FILE)
+ future_traits = null
+
+ return TRUE
+
+/datum/station_traits_panel/proc/too_late_for_future_traits()
+ return SSticker.current_state >= GAME_STATE_FINISHED
+
+/datum/station_traits_panel/proc/too_late_to_revert()
+ return SSticker.current_state >= GAME_STATE_PLAYING
+
+/datum/station_traits_panel/ui_status(mob/user, datum/ui_state/state)
+ return check_rights_for(user.client, R_FUN) ? UI_INTERACTIVE : UI_CLOSE
+
+/datum/station_traits_panel/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "StationTraitsPanel")
+ ui.open()
diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm
new file mode 100644
index 0000000000..c24d93865c
--- /dev/null
+++ b/code/datums/station_traits/negative_traits.dm
@@ -0,0 +1,287 @@
+/datum/station_trait/carp_infestation
+ name = "Carp infestation"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Dangerous fauna is present in the area of this station."
+ trait_to_give = STATION_TRAIT_CARP_INFESTATION
+
+/datum/station_trait/distant_supply_lines
+ name = "Distant supply lines"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 3
+ show_in_report = TRUE
+ report_message = "Due to the distance to our normal supply lines, cargo orders are more expensive."
+ blacklist = list(/datum/station_trait/strong_supply_lines)
+
+/datum/station_trait/distant_supply_lines/on_round_start()
+ SSeconomy.pack_price_modifier *= 1.2
+
+/datum/station_trait/late_arrivals
+ name = "Late Arrivals"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 2
+ show_in_report = TRUE
+ report_message = "Sorry for that, we didn't expect to fly into that vomiting goose while bringing you to your new station."
+ trait_to_give = STATION_TRAIT_LATE_ARRIVALS
+ blacklist = list(/datum/station_trait/random_spawns, /datum/station_trait/hangover)
+
+/datum/station_trait/random_spawns
+ name = "Drive-by landing"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 2
+ show_in_report = TRUE
+ report_message = "Sorry for that, we missed your station by a few miles, so we just launched you towards your station in pods. Hope you don't mind!"
+ trait_to_give = STATION_TRAIT_RANDOM_ARRIVALS
+ blacklist = list(/datum/station_trait/late_arrivals, /datum/station_trait/hangover)
+
+/datum/station_trait/hangover
+ name = "Hangover"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 0
+ show_in_report = TRUE
+ report_message = "Ohh....Man....That mandatory office party from last shift...God that was awesome..I woke up in some random toilet 3 sectors away..."
+ trait_to_give = STATION_TRAIT_HANGOVER
+ blacklist = list(/datum/station_trait/late_arrivals, /datum/station_trait/random_spawns)
+
+/datum/station_trait/hangover/New()
+ . = ..()
+ RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN, .proc/on_job_after_spawn)
+
+/datum/station_trait/hangover/revert()
+ for (var/obj/effect/landmark/start/hangover/hangover_spot in GLOB.start_landmarks_list)
+ QDEL_LIST(hangover_spot.debris)
+
+ return ..()
+
+/datum/station_trait/hangover/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned_mob)
+ SIGNAL_HANDLER
+
+ if(prob(65)) // most aren't hungover
+ return
+ if(!iscarbon(spawned_mob)) // don't want silicons or similar to be counted here
+ return
+ if(HAS_TRAIT(spawned_mob, TRAIT_ROBOTIC_ORGANISM)) // robots can't get hungover
+ return
+ if(HAS_TRAIT(spawned_mob, TRAIT_TOXIC_ALCOHOL)) // people with alcohol intolerance also can't
+ return
+ var/obj/item/hat = pick(
+ /obj/item/clothing/head/sombrero,
+ /obj/item/clothing/head/fedora,
+ /obj/item/clothing/mask/balaclava,
+ /obj/item/clothing/head/ushanka,
+ /obj/item/clothing/head/cardborg,
+ /obj/item/clothing/head/pirate,
+ /obj/item/clothing/head/cone,
+ )
+ hat = new hat(spawned_mob)
+ spawned_mob.equip_to_slot_or_del(hat, ITEM_SLOT_HEAD)
+
+
+/datum/station_trait/blackout
+ name = "Blackout"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 3
+ show_in_report = TRUE
+ report_message = "Station lights seem to be damaged, be safe when starting your shift today."
+
+/datum/station_trait/blackout/on_round_start()
+ . = ..()
+ for(var/obj/machinery/power/apc/apc as anything in GLOB.apcs_list)
+ if(is_station_level(apc.z) && prob(60))
+ apc.overload_lighting()
+
+/datum/station_trait/empty_maint
+ name = "Cleaned out maintenance"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Our workers cleaned out most of the junk in the maintenace areas."
+ blacklist = list(/datum/station_trait/filled_maint)
+ trait_to_give = STATION_TRAIT_EMPTY_MAINT
+
+ // This station trait is checked when loot drops initialize, so it's too late
+ can_revert = FALSE
+
+/datum/station_trait/overflow_job_bureaucracy
+ name = "Overflow bureaucracy mistake"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 4
+ show_in_report = TRUE
+ var/chosen_job_name
+
+/datum/station_trait/overflow_job_bureaucracy/New()
+ . = ..()
+ RegisterSignal(SSjob, COMSIG_SUBSYSTEM_POST_INITIALIZE, .proc/set_overflow_job_override)
+
+/datum/station_trait/overflow_job_bureaucracy/get_report()
+ return "[name] - It seems for some reason we put out the wrong job-listing for the overflow role this shift...I hope you like [chosen_job_name]s."
+
+/datum/station_trait/overflow_job_bureaucracy/proc/set_overflow_job_override(datum/source)
+ SIGNAL_HANDLER
+ var/datum/job/picked_job = pick(get_all_jobs())
+ chosen_job_name = lowertext(picked_job.title) // like Chief Engineers vs like chief engineers
+ SSjob.set_overflow_role(picked_job.type)
+
+/datum/station_trait/slow_shuttle
+ name = "Slow Shuttle"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Due to distance to our supply station, the cargo shuttle will have a slower flight time to your cargo department."
+ blacklist = list(/datum/station_trait/quick_shuttle)
+
+/datum/station_trait/slow_shuttle/on_round_start()
+ . = ..()
+ SSshuttle.supply.callTime *= 1.5
+
+/datum/station_trait/bot_languages
+ name = "Bot Language Matrix Malfunction"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 3
+ show_in_report = TRUE
+ report_message = "Your station's friendly bots have had their language matrix fried due to an event, resulting in some strange and unfamiliar speech patterns."
+
+/datum/station_trait/bot_languages/New()
+ . = ..()
+ /// What "caused" our robots to go haywire (fluff)
+ var/event_source = pick(list("an ion storm", "a syndicate hacking attempt", "a malfunction", "issues with your onboard AI", "an intern's mistakes", "budget cuts"))
+ report_message = "Your station's friendly bots have had their language matrix fried due to [event_source], resulting in some strange and unfamiliar speech patterns."
+
+/datum/station_trait/bot_languages/on_round_start()
+ . = ..()
+ //All bots that exist round start have their set language randomized.
+ for(var/mob/living/simple_animal/bot/found_bot in GLOB.alive_mob_list)
+ /// The bot's language holder - so we can randomize and change their language
+ var/datum/language_holder/bot_languages = found_bot.get_language_holder()
+ bot_languages.selected_language = bot_languages.get_random_spoken_language()
+
+/datum/station_trait/revenge_of_pun_pun
+ name = "Revenge of Pun Pun"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 2
+
+ // Way too much is done on atoms SS to be reverted, and it'd look
+ // kinda clunky on round start. It's not impossible to make this work,
+ // but it's a project for...someone else.
+ can_revert = FALSE
+
+ var/static/list/weapon_types
+
+/datum/station_trait/revenge_of_pun_pun/New()
+ if(!weapon_types)
+ weapon_types = list(
+ /obj/item/chair = 25,
+ /obj/item/tailclub = 15,
+ /obj/item/melee/baseball_bat = 10,
+ /obj/item/melee/chainofcommand/tailwhip = 15,
+ /obj/item/melee/chainofcommand/tailwhip/kitty = 15,
+ /obj/item/reagent_containers/food/drinks/bottle = 25,
+ /obj/item/gun/ballistic/automatic/pistol = 1,
+ )
+
+ RegisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE, .proc/arm_monke)
+
+/datum/station_trait/revenge_of_pun_pun/proc/arm_monke()
+ SIGNAL_HANDLER
+ var/mob/living/carbon/monkey/punpun = locate()
+ if(!punpun)
+ return
+ var/weapon_type = pickweight(weapon_types)
+ var/obj/item/weapon = new weapon_type
+ if(!punpun.put_in_l_hand(weapon) && !punpun.put_in_r_hand(weapon))
+ // Guess they did all this with whatever they have in their hands already
+ qdel(weapon)
+ weapon = punpun.get_active_held_item() || punpun.get_inactive_held_item()
+
+ weapon?.add_mob_blood(punpun)
+ punpun.add_mob_blood(punpun)
+
+ punpun.aggressive = TRUE
+
+ var/area/place = get_area(punpun)
+
+ var/list/area_open_turfs = list()
+ for(var/turf/location in place)
+ if(location.density)
+ continue
+ area_open_turfs += location
+
+ punpun.forceMove(pick(area_open_turfs))
+
+ for(var/i in 1 to rand(10, 40))
+ new /obj/effect/decal/cleanable/blood(pick(area_open_turfs))
+
+ var/list/blood_path = list()
+ for(var/i in 1 to 10) // Only 10 attempts
+ var/turf/destination = pick(area_open_turfs)
+ var/turf/next_step = get_step_to(punpun, destination)
+ for(var/k in 1 to 30) // Max 30 steps
+ if(!next_step)
+ break
+ blood_path += next_step
+ next_step = get_step_to(next_step, destination)
+ if(length(blood_path))
+ break
+ if(!length(blood_path))
+ CRASH("Unable to make a path from punpun")
+
+ var/turf/last_location
+ for(var/turf/location as anything in blood_path)
+ last_location = location
+
+ if(prob(80))
+ new /obj/effect/decal/cleanable/blood(location)
+
+ if(prob(50))
+ var/static/blood_types = list(
+ /obj/effect/decal/cleanable/blood/splatter,
+ /obj/effect/decal/cleanable/blood/gibs,
+ )
+ var/blood_type = pick(blood_types)
+ new blood_type(get_turf(pick(orange(location, 2))))
+
+ new /obj/effect/decal/cleanable/blood/gibs/torso(last_location)
+
+// Abstract station trait used for traits that modify a random event in some way (their weight or max occurrences).
+/datum/station_trait/random_event_weight_modifier
+ name = "Random Event Modifier"
+ report_message = "A random event has been modified this shift! Someone forgot to set this!"
+ show_in_report = TRUE
+ trait_flags = STATION_TRAIT_ABSTRACT
+ weight = 0
+
+ /// The path to the round_event_control that we modify.
+ var/event_control_path
+ /// Multiplier applied to the weight of the event.
+ var/weight_multiplier = 1
+ /// Flat modifier added to the amount of max occurances the random event can have.
+ var/max_occurrences_modifier = 0
+
+/datum/station_trait/random_event_weight_modifier/on_round_start()
+ . = ..()
+ var/datum/round_event_control/modified_event = locate(event_control_path) in SSevents.control
+ if(!modified_event)
+ CRASH("[type] could not find a round event controller to modify on round start (likely has an invalid event_control_path set)!")
+
+ modified_event.weight *= weight_multiplier
+ modified_event.max_occurrences += max_occurrences_modifier
+
+/datum/station_trait/random_event_weight_modifier/ion_storms
+ name = "Ionic Stormfront"
+ report_message = "An ionic stormfront is passing over your station's system. Expect an increased likelihood of ion storms afflicting your station's silicon units."
+ trait_type = STATION_TRAIT_NEGATIVE
+ trait_flags = NONE
+ weight = 3
+ event_control_path = /datum/round_event_control/ion_storm
+ weight_multiplier = 2
+
+/datum/station_trait/random_event_weight_modifier/rad_storms
+ name = "Radiation Stormfront"
+ report_message = "A radioactive stormfront is passing through your station's system. Expect an increased likelihood of radiation storms passing over your station, as well the potential for multiple radiation storms to occur during your shift."
+ trait_type = STATION_TRAIT_NEGATIVE
+ trait_flags = NONE
+ weight = 2
+ event_control_path = /datum/round_event_control/radiation_storm
+ weight_multiplier = 1.5
+ max_occurrences_modifier = 2
diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm
new file mode 100644
index 0000000000..19480f6672
--- /dev/null
+++ b/code/datums/station_traits/neutral_traits.dm
@@ -0,0 +1,116 @@
+/datum/station_trait/bananium_shipment
+ name = "Bananium Shipment"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 5
+ report_message = "Rumors has it that the clown planet has been sending support packages to clowns in this system"
+ trait_to_give = STATION_TRAIT_BANANIUM_SHIPMENTS
+
+/datum/station_trait/unnatural_atmosphere
+ name = "Unnatural atmospherical properties"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 5
+ show_in_report = TRUE
+ report_message = "System's local planet has irregular atmospherical properties"
+ trait_to_give = STATION_TRAIT_UNNATURAL_ATMOSPHERE
+
+ // This station trait modifies the atmosphere, which is too far past the time admins are able to revert it
+ can_revert = FALSE
+
+/datum/station_trait/unique_ai
+ name = "Unique AI"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 5
+ show_in_report = TRUE
+ report_message = "For experimental purposes, this station AI might show divergence from default lawset. Do not meddle with this experiment."
+ trait_to_give = STATION_TRAIT_UNIQUE_AI
+
+/datum/station_trait/ian_adventure
+ name = "Ian's Adventure"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 5
+ show_in_report = FALSE
+ report_message = "Ian has gone exploring somewhere in the station."
+
+/datum/station_trait/ian_adventure/on_round_start()
+ for(var/mob/living/simple_animal/pet/dog/corgi/dog in GLOB.mob_list)
+ if(!(istype(dog, /mob/living/simple_animal/pet/dog/corgi/Ian) || istype(dog, /mob/living/simple_animal/pet/dog/corgi/puppy)))
+ continue
+
+ // Makes this station trait more interesting. Ian probably won't go anywhere without a little external help.
+ // Also gives him a couple extra lives to survive eventual tiders.
+ dog.AddComponent(/datum/component/twitch_plays/simple_movement/auto, 3 SECONDS)
+ dog.AddComponent(/datum/component/multiple_lives, 2)
+ RegisterSignal(dog, COMSIG_ON_MULTIPLE_LIVES_RESPAWN, .proc/do_corgi_respawn)
+
+ // The extended safety checks at time of writing are about chasms and lava
+ // if there are any chasms and lava on stations in the future, woah
+ var/turf/current_turf = get_turf(dog)
+ var/turf/adventure_turf = find_safe_turf(extended_safety_checks = TRUE, dense_atoms = FALSE)
+
+ // Poof!
+ do_smoke(location=current_turf)
+ dog.forceMove(adventure_turf)
+ do_smoke(location=adventure_turf)
+
+/// Moves the new dog somewhere safe, equips it with the old one's inventory and makes it deadchat_playable.
+/datum/station_trait/ian_adventure/proc/do_corgi_respawn(mob/living/simple_animal/pet/dog/corgi/old_dog, mob/living/simple_animal/pet/dog/corgi/new_dog, gibbed, lives_left)
+ SIGNAL_HANDLER
+
+ var/turf/current_turf = get_turf(new_dog)
+ var/turf/adventure_turf = find_safe_turf(extended_safety_checks = TRUE, dense_atoms = FALSE)
+
+ do_smoke(location=current_turf)
+ new_dog.forceMove(adventure_turf)
+ do_smoke(location=adventure_turf)
+
+ if(old_dog.inventory_back)
+ var/obj/item/old_dog_back = old_dog.inventory_back
+ old_dog.inventory_back = null
+ old_dog_back.forceMove(new_dog)
+ new_dog.inventory_back = old_dog_back
+
+ if(old_dog.inventory_head)
+ var/obj/item/old_dog_hat = old_dog.inventory_head
+ old_dog.inventory_head = null
+ new_dog.place_on_head(old_dog_hat)
+
+ new_dog.update_corgi_fluff()
+ new_dog.regenerate_icons()
+ new_dog.AddComponent(/datum/component/twitch_plays/simple_movement/auto, 3 SECONDS)
+ if(lives_left)
+ RegisterSignal(new_dog, COMSIG_ON_MULTIPLE_LIVES_RESPAWN, .proc/do_corgi_respawn)
+
+ if(!gibbed) //The old dog will now disappear so we won't have more than one Ian at a time.
+ qdel(old_dog)
+
+/datum/station_trait/glitched_pdas
+ name = "PDA glitch"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 15
+ show_in_report = TRUE
+ report_message = "Something seems to be wrong with the PDAs issued to you all this shift. Nothing too bad though."
+ trait_to_give = STATION_TRAIT_PDA_GLITCHED
+
+/datum/station_trait/announcement_intern
+ name = "Announcement Intern"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 1
+ show_in_report = TRUE
+ report_message = "Please be nice to him."
+ blacklist = list(/datum/station_trait/announcement_medbot)
+
+/datum/station_trait/announcement_intern/New()
+ . = ..()
+ SSstation.announcer = /datum/centcom_announcer/intern
+
+/datum/station_trait/announcement_medbot
+ name = "Announcement \"System\""
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 1
+ show_in_report = TRUE
+ report_message = "Our announcement system is under scheduled maintanance at the moment. Thankfully, we have a backup."
+ blacklist = list(/datum/station_trait/announcement_intern)
+
+/datum/station_trait/announcement_medbot/New()
+ . = ..()
+ SSstation.announcer = /datum/centcom_announcer/medbot
diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm
new file mode 100644
index 0000000000..abe5a3a0e9
--- /dev/null
+++ b/code/datums/station_traits/positive_traits.dm
@@ -0,0 +1,279 @@
+#define PARTY_COOLDOWN_LENGTH_MIN 6 MINUTES
+#define PARTY_COOLDOWN_LENGTH_MAX 12 MINUTES
+
+
+/datum/station_trait/lucky_winner
+ name = "Lucky winner"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 1
+ show_in_report = TRUE
+ report_message = "Your station has won the grand prize of the annual station charity event. Free snacks will be delivered to the bar every now and then."
+ trait_processes = TRUE
+ COOLDOWN_DECLARE(party_cooldown)
+
+/datum/station_trait/lucky_winner/on_round_start()
+ . = ..()
+ COOLDOWN_START(src, party_cooldown, rand(PARTY_COOLDOWN_LENGTH_MIN, PARTY_COOLDOWN_LENGTH_MAX))
+
+/datum/station_trait/lucky_winner/process(delta_time)
+ if(!COOLDOWN_FINISHED(src, party_cooldown))
+ return
+
+ COOLDOWN_START(src, party_cooldown, rand(PARTY_COOLDOWN_LENGTH_MIN, PARTY_COOLDOWN_LENGTH_MAX))
+
+ var/area/area_to_spawn_in = pick(GLOB.bar_areas)
+ var/turf/T = pick(area_to_spawn_in.contents)
+
+ var/obj/structure/closet/supplypod/centcompod/toLaunch = new()
+ var/obj/item/pizzabox/pizza_to_spawn = pick(list(/obj/item/pizzabox/margherita, /obj/item/pizzabox/mushroom, /obj/item/pizzabox/meat, /obj/item/pizzabox/vegetable, /obj/item/pizzabox/pineapple))
+ new pizza_to_spawn(toLaunch)
+ for(var/i in 1 to 6)
+ if(prob(50))
+ new /obj/item/reagent_containers/food/drinks/beer(toLaunch)
+ else
+ for(var/m in 1 to 2)
+ var/obj/item/soda_to_spawn = pick(list(/obj/item/reagent_containers/food/drinks/soda_cans/sol_dry,
+ /obj/item/reagent_containers/food/drinks/soda_cans/space_up,
+ /obj/item/reagent_containers/food/drinks/soda_cans/starkist,
+ /obj/item/reagent_containers/food/drinks/soda_cans/cola,
+ /obj/item/reagent_containers/food/drinks/soda_cans/space_mountain_wind,
+ /obj/item/reagent_containers/food/drinks/soda_cans/dr_gibb,
+ /obj/item/reagent_containers/food/drinks/soda_cans/pwr_game))
+ new soda_to_spawn(toLaunch)
+ new /obj/effect/pod_landingzone(T, toLaunch)
+
+/datum/station_trait/galactic_grant
+ name = "Galactic grant"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 6
+ show_in_report = TRUE
+ report_message = "Your station has been selected for a special grant. Some extra funds has been made available to your cargo department."
+
+/datum/station_trait/galactic_grant/on_round_start()
+ var/datum/bank_account/cargo_bank = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ cargo_bank.adjust_money(rand(2000, 5000))
+
+/datum/station_trait/premium_internals_box
+ name = "Premium internals boxes"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 10
+ show_in_report = TRUE
+ report_message = "The internals boxes for your crew have been filled with bonus equipment."
+ trait_to_give = STATION_TRAIT_PREMIUM_INTERNALS
+
+/datum/station_trait/bountiful_bounties
+ name = "Bountiful bounties"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "It seems collectors in this system are extra keen to on bounties, and will pay more to see their completion."
+
+/datum/station_trait/bountiful_bounties/on_round_start()
+ SSeconomy.bounty_modifier *= 1.2
+
+/datum/station_trait/strong_supply_lines
+ name = "Strong supply lines"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Prices are low in this system, BUY BUY BUY!"
+ blacklist = list(/datum/station_trait/distant_supply_lines)
+
+
+/datum/station_trait/strong_supply_lines/on_round_start()
+ SSeconomy.pack_price_modifier *= 0.8
+
+/datum/station_trait/scarves
+ name = "Scarves"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ var/list/scarves
+
+/datum/station_trait/scarves/New()
+ . = ..()
+ report_message = pick(
+ "Nanotrasen is experimenting with seeing if neck warmth improves employee morale.",
+ "After Space Fashion Week, scarves are the hot new accessory.",
+ "Everyone was simultaneously a little bit cold when they packed to go to the station.",
+ "The station is definitely not under attack by neck grappling aliens masquerading as wool. Definitely not.",
+ "You all get free scarves. Don't ask why.",
+ "A shipment of scarves was delivered to the station.",
+ )
+ scarves = typesof(/obj/item/clothing/neck/scarf) + list(
+ /obj/item/clothing/neck/stripedredscarf,
+ /obj/item/clothing/neck/stripedgreenscarf,
+ /obj/item/clothing/neck/stripedbluescarf,
+ )
+
+ scarves -= /obj/item/clothing/neck/scarf/zomb // donator snowflake code--mayhaps we should make a glob for this or similar
+
+ RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, .proc/on_job_after_spawn)
+
+
+/datum/station_trait/scarves/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client)
+ SIGNAL_HANDLER
+ var/scarf_type = pick(scarves)
+
+ spawned.equip_to_slot_or_del(new scarf_type(spawned), ITEM_SLOT_NECK)
+
+
+/datum/station_trait/filled_maint
+ name = "Filled up maintenance"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Our workers accidentaly forgot more of their personal belongings in the maintenace areas."
+ blacklist = list(/datum/station_trait/empty_maint)
+ trait_to_give = STATION_TRAIT_FILLED_MAINT
+
+ // This station trait is checked when loot drops initialize, so it's too late
+ can_revert = FALSE
+
+/datum/station_trait/quick_shuttle
+ name = "Quick Shuttle"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 6
+ show_in_report = TRUE
+ report_message = "Due to proximity to our supply station, the cargo shuttle will have a quicker flight time to your cargo department."
+ blacklist = list(/datum/station_trait/slow_shuttle)
+
+/datum/station_trait/quick_shuttle/on_round_start()
+ . = ..()
+ SSshuttle.supply.callTime *= 0.5
+
+/datum/station_trait/deathrattle_department
+ name = "deathrattled department"
+ trait_type = STATION_TRAIT_POSITIVE
+ show_in_report = TRUE
+ trait_flags = STATION_TRAIT_ABSTRACT
+ blacklist = list(/datum/station_trait/deathrattle_all)
+
+ var/department_head
+ var/department_name = "department"
+ var/datum/deathrattle_group/deathrattle_group
+
+/datum/station_trait/deathrattle_department/New()
+ . = ..()
+ deathrattle_group = new("[department_name] group")
+ blacklist += subtypesof(/datum/station_trait/deathrattle_department) - type //All but ourselves
+ report_message = "All members of [department_name] have received an implant to notify each other if one of them dies. This should help improve job-safety!"
+ RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, .proc/on_job_after_spawn)
+
+
+/datum/station_trait/deathrattle_department/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client)
+ SIGNAL_HANDLER
+
+ if(department_head != job.title && !(src.department_head in job.department_head))
+ return
+
+ var/obj/item/implant/deathrattle/implant_to_give = new()
+ deathrattle_group.register(implant_to_give)
+ implant_to_give.implant(spawned, spawned, TRUE, TRUE)
+
+
+/datum/station_trait/deathrattle_department/service
+ name = "Deathrattled Service"
+ trait_flags = NONE
+ weight = 1
+ department_head = "Head of Personnel"
+ department_name = "Service"
+
+/datum/station_trait/deathrattle_department/cargo
+ name = "Deathrattled Cargo"
+ trait_flags = NONE
+ weight = 2
+ department_head = "Quartermaster"
+ department_name = "Cargo"
+
+/datum/station_trait/deathrattle_department/engineering
+ name = "Deathrattled Engineering"
+ trait_flags = NONE
+ weight = 2
+ department_head = "Chief Engineer"
+ department_name = "Engineering"
+
+/datum/station_trait/deathrattle_department/command
+ name = "Deathrattled Command"
+ trait_flags = NONE
+ weight = 1
+ department_head = "Captain"
+ department_name = "Command"
+
+/datum/station_trait/deathrattle_department/science
+ name = "Deathrattled Science"
+ trait_flags = NONE
+ weight = 1
+ department_head = "Research Director"
+ department_name = "Science"
+
+/datum/station_trait/deathrattle_department/security
+ name = "Deathrattled Security"
+ trait_flags = NONE
+ weight = 1
+ department_head = "Head of Security"
+ department_name = "Security"
+
+/datum/station_trait/deathrattle_department/medical
+ name = "Deathrattled Medical"
+ trait_flags = NONE
+ weight = 1
+ department_head = "Chief Medical Officer"
+ department_name = "Medical"
+
+/datum/station_trait/deathrattle_all
+ name = "Deathrattled Station"
+ trait_type = STATION_TRAIT_POSITIVE
+ show_in_report = TRUE
+ weight = 1
+ report_message = "All members of the station have received an implant to notify each other if one of them dies. This should help improve job-safety!"
+ var/datum/deathrattle_group/deathrattle_group
+
+
+/datum/station_trait/deathrattle_all/New()
+ . = ..()
+ deathrattle_group = new("station group")
+ blacklist = subtypesof(/datum/station_trait/deathrattle_department)
+ RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, .proc/on_job_after_spawn)
+
+
+/datum/station_trait/deathrattle_all/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client)
+ SIGNAL_HANDLER
+
+ var/obj/item/implant/deathrattle/implant_to_give = new()
+ deathrattle_group.register(implant_to_give)
+ implant_to_give.implant(spawned, spawned, TRUE, TRUE)
+
+
+/datum/station_trait/wallets
+ name = "Wallets!"
+ trait_type = STATION_TRAIT_POSITIVE
+ show_in_report = TRUE
+ weight = 10
+ report_message = "It has become temporarily fashionable to use a wallet, so everyone on the station has been issued one."
+
+/datum/station_trait/wallets/New()
+ . = ..()
+ RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, .proc/on_job_after_spawn)
+
+/datum/station_trait/wallets/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/living_mob, mob/M, joined_late)
+ SIGNAL_HANDLER
+
+ var/obj/item/card/id/id_card = living_mob.get_item_by_slot(ITEM_SLOT_ID)
+ if(!istype(id_card))
+ return
+
+ living_mob.temporarilyRemoveItemFromInventory(id_card, force=TRUE)
+
+ // "Doc, what's wrong with me?"
+ var/obj/item/storage/wallet/wallet = new(src)
+ // "You've got a wallet embedded in your chest."
+ wallet.add_fingerprint(living_mob, ignoregloves = TRUE)
+
+ living_mob.equip_to_slot_if_possible(wallet, ITEM_SLOT_ID)
+
+ id_card.forceMove(wallet)
+
+ // Put our filthy fingerprints all over the contents
+ for(var/obj/item/item in wallet)
+ item.add_fingerprint(living_mob, ignoregloves = TRUE)
diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm
index 361c518073..b09731ab62 100644
--- a/code/game/area/Space_Station_13_areas.dm
+++ b/code/game/area/Space_Station_13_areas.dm
@@ -878,9 +878,9 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
nightshift_public_area = NIGHTSHIFT_AREA_RECREATION
sound_environment = SOUND_AREA_WOODFLOOR
-// /area/service/bar/Initialize(mapload)
-// . = ..()
-// GLOB.bar_areas += src
+/area/service/bar/Initialize(mapload)
+ . = ..()
+ GLOB.bar_areas += src
/area/service/bar/atrium
name = "Atrium"
diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm
index a6abcbefbd..bfc75a9dc1 100644
--- a/code/game/gamemodes/extended/extended.dm
+++ b/code/game/gamemodes/extended/extended.dm
@@ -30,4 +30,4 @@
/datum/game_mode/extended/announced/send_intercept(report = 0)
if(flipseclevel) //CIT CHANGE - allows the sec level to be flipped roundstart
return ..()
- priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", "commandreport")
+ priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound())
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 617be706cb..df1b0a701a 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -278,7 +278,7 @@
/datum/game_mode/proc/send_intercept()
if(flipseclevel && !(config_tag == "extended"))//CIT CHANGE - lets the security level be flipped roundstart
- priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", "commandreport")
+ priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound())
return
var/intercepttext = "Central Command Status Summary
"
intercepttext += "Central Command has intercepted and partially decoded a Syndicate transmission with vital information regarding their movements. The following report outlines the most \
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 7c22846ece..d705057217 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -244,7 +244,7 @@
nuke_request(reason, usr)
to_chat(usr, span_notice("Request sent."))
usr.log_message("has requested the nuclear codes from CentCom with reason \"[reason]\"", LOG_SAY)
- priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self-Destruct Codes Requested", "commandreport")
+ priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self-Destruct Codes Requested", SSstation.announcer.get_rand_report_sound())
playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
COOLDOWN_START(src, important_action_cooldown, IMPORTANT_ACTION_COOLDOWN)
if ("restoreBackupRoutingData")
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index ed62019824..84e7b93e1b 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -1356,6 +1356,7 @@
/obj/machinery/door/airlock/proc/remove_electrify()
secondsElectrified = NOT_ELECTRIFIED
unelectrify_timerid = null
+ diag_hud_set_electrified()
/obj/machinery/door/airlock/proc/set_electrified(seconds, mob/user)
secondsElectrified = seconds
diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm
index 403c618883..9e6cde9336 100644
--- a/code/game/objects/effects/effect_system/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/effects_smoke.dm
@@ -322,3 +322,9 @@
/obj/effect/particle_effect/smoke/transparent
opaque = FALSE
+
+/proc/do_smoke(range=0, location=null, smoke_type=/obj/effect/particle_effect/smoke)
+ var/datum/effect_system/smoke_spread/smoke = new
+ smoke.effect_type = smoke_type
+ smoke.set_up(range, location)
+ smoke.start()
diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index 2aaef553bf..22a9e86bb0 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -528,3 +528,70 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player)
name = "portal exit"
icon_state = "portal_exit"
var/id
+
+/obj/effect/landmark/start/hangover
+ name = "hangover spawn"
+ icon_state = "hangover_spawn"
+
+ /// A list of everything this hangover spawn created
+ var/list/debris = list()
+
+/obj/effect/landmark/start/hangover/Initialize(mapload)
+ . = ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/effect/landmark/start/hangover/Destroy()
+ debris = null
+ return ..()
+
+/obj/effect/landmark/start/hangover/LateInitialize()
+ . = ..()
+ if(!HAS_TRAIT(SSstation, STATION_TRAIT_HANGOVER))
+ return
+ if(prob(60))
+ debris += new /obj/effect/decal/cleanable/vomit(get_turf(src))
+ if(prob(70))
+ var/bottle_count = rand(1, 3)
+ for(var/index in 1 to bottle_count)
+ var/turf/turf_to_spawn_on = get_step(src, pick(GLOB.alldirs))
+ if(!isopenturf(turf_to_spawn_on))
+ continue
+ var/dense_object = FALSE
+ for(var/atom/content in turf_to_spawn_on.contents)
+ if(content.density)
+ dense_object = TRUE
+ break
+ if(dense_object)
+ continue
+ debris += new /obj/item/reagent_containers/food/drinks/beer/almost_empty(turf_to_spawn_on)
+
+///Spawns the mob with some drugginess/drunkeness, and some disgust.
+/obj/effect/landmark/start/hangover/proc/make_hungover(mob/hangover_mob)
+ if(!iscarbon(hangover_mob))
+ return
+ var/mob/living/carbon/spawned_carbon = hangover_mob
+ spawned_carbon.set_resting(TRUE, silent = TRUE)
+ if(prob(50))
+ spawned_carbon.adjust_drugginess(rand(15, 20))
+ else
+ spawned_carbon.drunkenness += rand(15, 25)
+ spawned_carbon.adjust_disgust(rand(5, 55)) //How hungover are you?
+ if(spawned_carbon.head)
+ return
+
+/obj/effect/landmark/start/hangover/JoinPlayerHere(mob/joining_mob, buckle)
+ . = ..()
+ make_hungover(joining_mob)
+
+/obj/effect/landmark/start/hangover/closet
+ name = "hangover spawn closet"
+ icon_state = "hangover_spawn_closet"
+
+/obj/effect/landmark/start/hangover/closet/JoinPlayerHere(mob/joining_mob, buckle)
+ make_hungover(joining_mob)
+ for(var/obj/structure/closet/closet in contents)
+ if(closet.opened)
+ continue
+ joining_mob.forceMove(closet)
+ return
+ return ..() //Call parent as fallback
diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm
index f19f8f0f95..0f7e00a538 100644
--- a/code/game/objects/effects/spawners/lootdrop.dm
+++ b/code/game/objects/effects/spawners/lootdrop.dm
@@ -2,6 +2,7 @@
icon = 'icons/effects/landmarks_static.dmi'
icon_state = "random_loot"
layer = OBJ_LAYER
+ var/spawn_on_init = TRUE
var/spawn_on_turf = TRUE
var/lootcount = 1 //how many items will be spawned
var/lootdoubles = TRUE //if the same item can be spawned twice
@@ -10,10 +11,19 @@
/obj/effect/spawner/lootdrop/Initialize(mapload)
..()
+ if(should_spawn_on_init())
+ spawn_loot()
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/spawner/lootdrop/proc/should_spawn_on_init()
+ return spawn_on_init
+
+/obj/effect/spawner/lootdrop/proc/spawn_loot(lootcount_override)
+ var/lootcount = isnull(lootcount_override) ? src.lootcount : lootcount_override
if(loot && loot.len)
var/atom/A = spawn_on_turf ? get_turf(src) : loc
var/loot_spawned = 0
- while((lootcount-loot_spawned) && loot.len)
+ while((lootcount-loot_spawned) > 0 && loot.len)
var/lootspawn = pickweight(loot)
if(!lootdoubles)
loot.Remove(lootspawn)
@@ -29,7 +39,6 @@
if (loot_spawned)
spawned_loot.pixel_x = spawned_loot.pixel_y = ((!(loot_spawned%2)*loot_spawned/2)*-1)+((loot_spawned%2)*(loot_spawned+1)/2*1)
loot_spawned++
- return INITIALIZE_HINT_QDEL
/obj/effect/spawner/lootdrop/bedsheet
icon = 'icons/obj/bedsheets.dmi'
@@ -162,6 +171,15 @@
loot = GLOB.maintenance_loot
. = ..()
+/obj/effect/spawner/lootdrop/maintenance/spawn_loot(lootcount_override)
+ if(isnull(lootcount_override))
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_FILLED_MAINT))
+ lootcount_override = round(lootcount * 1.5)
+
+ else if(HAS_TRAIT(SSstation, STATION_TRAIT_EMPTY_MAINT))
+ lootcount_override = round(lootcount * 0.5)
+ . = ..()
+
/obj/effect/spawner/lootdrop/glowstick
name = "random colored glowstick"
icon = 'icons/obj/lighting.dmi'
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index 728e5fd726..588e066a41 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -561,4 +561,4 @@
singular_name = "catwalk floor tile"
desc = "Flooring that shows its contents underneath. Engineers love it!"
icon_state = "catwalk_tile"
- turf_type = /turf/open/floor/plating/catwalk_floor
+ turf_type = /turf/open/floor/catwalk_floor
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index a245c059ae..79280faca6 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -121,6 +121,10 @@
else
new /obj/item/tank/internals/plasmaman/belt(src)
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
+ new /obj/item/flashlight/flare(src)
+ new /obj/item/radio/off(src)
+
/obj/item/storage/box/survival/radio/PopulateContents()
..() // we want the survival stuff too.
new /obj/item/radio/off(src)
@@ -773,6 +777,10 @@
else
new /obj/item/tank/internals/plasmaman/belt(src)
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
+ new /obj/item/flashlight/flare(src)
+ new /obj/item/radio/off(src)
+
/obj/item/storage/box/rubbershot
name = "box of rubber shots"
desc = "A box full of rubber shots, designed for riot shotguns."
diff --git a/code/game/turfs/open/floor/catwalk_plating.dm b/code/game/turfs/open/floor/catwalk_plating.dm
index 76d22847c9..ac0f3b63de 100644
--- a/code/game/turfs/open/floor/catwalk_plating.dm
+++ b/code/game/turfs/open/floor/catwalk_plating.dm
@@ -5,7 +5,7 @@
* you can crowbar it to interact with the underneath stuff without destroying the tile...
* unless you want to!
*/
-/turf/open/floor/plating/catwalk_floor
+/turf/open/floor/catwalk_floor
icon = 'icons/turf/floors/catwalk_plating.dmi'
icon_state = "catwalk_below"
floor_tile = /obj/item/stack/tile/catwalk
@@ -16,29 +16,35 @@
barefootstep = FOOTSTEP_CATWALK
clawfootstep = FOOTSTEP_CATWALK
heavyfootstep = FOOTSTEP_CATWALK
+ intact = FALSE
var/covered = TRUE
-/turf/open/floor/plating/catwalk_floor/Initialize(mapload)
+/turf/open/floor/catwalk_floor/Initialize(mapload)
. = ..()
layer = CATWALK_LAYER
update_icon(UPDATE_OVERLAYS)
-/turf/open/floor/plating/catwalk_floor/update_overlays()
+/turf/open/floor/catwalk_floor/update_overlays()
. = ..()
- var/static/catwalk_overlay
+ var/static/image/catwalk_overlay
if(isnull(catwalk_overlay))
- catwalk_overlay = iconstate2appearance(icon, "catwalk_above")
+ catwalk_overlay = new()
+ catwalk_overlay.icon = icon
+ catwalk_overlay.icon_state = "catwalk_above"
+ catwalk_overlay.plane = GAME_PLANE
+ catwalk_overlay.layer = CATWALK_LAYER
+ catwalk_overlay = catwalk_overlay.appearance
if(covered)
. += catwalk_overlay
-/turf/open/floor/plating/catwalk_floor/screwdriver_act(mob/living/user, obj/item/tool)
+/turf/open/floor/catwalk_floor/screwdriver_act(mob/living/user, obj/item/tool)
. = ..()
covered = !covered
user.balloon_alert(user, "[!covered ? "cover removed" : "cover added"]")
update_icon(UPDATE_OVERLAYS)
-/turf/open/floor/plating/catwalk_floor/pry_tile(obj/item/crowbar, mob/user, silent)
+/turf/open/floor/catwalk_floor/crowbar_act(mob/user, obj/item/I)
if(covered)
user.balloon_alert(user, "remove cover first!")
return FALSE
- . = ..()
+ return pry_tile(I, user)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index d3dfe3d10e..891f6250da 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -623,7 +623,7 @@
SSticker.start_immediately = FALSE
SSticker.SetTimeLeft(1800)
to_chat(world, "The game will start in 180 seconds.")
- SEND_SOUND(world, sound(get_announcer_sound("attention")))
+ SEND_SOUND(world, sound(SSstation.announcer.get_rand_alert_sound()))
message_admins("[usr.key] has cancelled immediate game start. Game will start in 180 seconds.")
log_admin("[usr.key] has cancelled immediate game start.")
else
@@ -706,7 +706,7 @@
log_admin("[key_name(usr)] delayed the round start.")
else
to_chat(world, "The game will start in [DisplayTimeText(newtime)].", confidential = TRUE)
- SEND_SOUND(world, sound(get_announcer_sound("attention")))
+ SEND_SOUND(world, sound(SSstation.announcer.get_rand_alert_sound()))
log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 646fa753ff..dba4ec5b46 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -75,6 +75,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/toggle_combo_hud, // toggle display of the combination pizza antag and taco sci/med/eng hud
/client/proc/toggle_AI_interact, /*toggle admin ability to interact with machines as an AI*/
/datum/admins/proc/open_shuttlepanel, /* Opens shuttle manipulator UI */
+ /datum/admins/proc/station_traits_panel, /* Opens station traits UI */
/client/proc/deadchat,
/client/proc/toggleprayers,
// /client/proc/toggle_prayer_sound,
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 85b3d9eaa4..f1884d5fb4 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -560,7 +560,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
var/announce_command_report = TRUE
switch(confirm)
if("Yes")
- priority_announce(input, null, "commandreport")
+ priority_announce(input, null, SSstation.announcer.get_rand_report_sound())
announce_command_report = FALSE
if("Cancel")
return
diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm
index 80344d076c..02289684f5 100644
--- a/code/modules/admin/verbs/secrets.dm
+++ b/code/modules/admin/verbs/secrets.dm
@@ -352,7 +352,7 @@
if(is_station_level(W.z) && !istype(get_area(W), /area/command) && !istype(get_area(W), /area/commons) && !istype(get_area(W), /area/service) && !istype(get_area(W), /area/command/heads_quarters) && !istype(get_area(W), /area/security/prison))
W.req_access = list()
message_admins("[key_name_admin(holder)] activated Egalitarian Station mode")
- priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, "commandreport")
+ priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, SSstation.announcer.get_rand_report_sound())
if("ancap")
if(!is_funmin)
return
@@ -360,9 +360,9 @@
SSeconomy.full_ancap = !SSeconomy.full_ancap
message_admins("[key_name_admin(holder)] toggled Anarcho-capitalist mode")
if(SSeconomy.full_ancap)
- priority_announce("The NAP is now in full effect.", null, "commandreport")
+ priority_announce("The NAP is now in full effect.", null, SSstation.announcer.get_rand_report_sound())
else
- priority_announce("The NAP has been revoked.", null, "commandreport")
+ priority_announce("The NAP has been revoked.", null, SSstation.announcer.get_rand_report_sound())
if("blackout")
if(!is_funmin)
return
@@ -528,7 +528,7 @@
message_admins("[key_name_admin(holder)] made everything kawaii.")
for(var/i in GLOB.human_list)
var/mob/living/carbon/human/H = i
- SEND_SOUND(H, sound(get_announcer_sound("animes")))
+ SEND_SOUND(H, sound(SSstation.announcer.event_sounds[ANNOUNCER_ANIMES]))
if(H.dna.species.id == "human")
if(H.dna.features["tail_human"] == "None" || H.dna.features["ears"] == "None")
diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
index 78e4d38b3c..cf1fadc260 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
@@ -53,7 +53,7 @@ GLOBAL_VAR_INIT(war_declared, FALSE)
if(!check_allowed(user) || !war_declaration)
return
- priority_announce(war_declaration, title = "Declaration of War", sound = 'sound/machines/alarm.ogg')
+ priority_announce(war_declaration, title = "Declaration of War", sound = 'sound/machines/alarm.ogg', has_important_message = TRUE)
to_chat(user, "You've attracted the attention of powerful forces within the syndicate. A bonus bundle of telecrystals has been granted to your team. Great things await you if you complete the mission.")
diff --git a/code/modules/atmospherics/auxgm/gas_types.dm b/code/modules/atmospherics/auxgm/gas_types.dm
index 9347a0c9fe..828af0e8ea 100644
--- a/code/modules/atmospherics/auxgm/gas_types.dm
+++ b/code/modules/atmospherics/auxgm/gas_types.dm
@@ -135,12 +135,13 @@
id = GAS_NITRIC
specific_heat = 20
name = "Nitric oxide"
- flags = GAS_FLAG_DANGEROUS
odor = "sharp sweetness"
odor_strength = 1
fusion_power = 15
enthalpy = 91290
heat_resistance = 2
+ powermix = -1
+ heat_penalty = -1
/datum/gas/nitryl
id = GAS_NITRYL
diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm
index 607274f71b..b104c51fb7 100644
--- a/code/modules/atmospherics/gasmixtures/reactions.dm
+++ b/code/modules/atmospherics/gasmixtures/reactions.dm
@@ -729,11 +729,11 @@
/datum/gas_reaction/nitric_oxide/react(datum/gas_mixture/air, datum/holder)
var/nitric = air.get_moles(GAS_NITRIC)
var/oxygen = air.get_moles(GAS_O2)
- var/max_amount = max(nitric / 10, MINIMUM_MOLE_COUNT)
- var/enthalpy = air.return_temperature() * (air.heat_capacity() + R_IDEAL_GAS_EQUATION * air.total_moles());
+ var/max_amount = max(nitric / 8, MINIMUM_MOLE_COUNT)
+ var/enthalpy = air.return_temperature() * (air.heat_capacity() + R_IDEAL_GAS_EQUATION * air.total_moles())
var/list/enthalpies = GLOB.gas_data.enthalpies
if(oxygen > MINIMUM_MOLE_COUNT)
- var/reaction_amount = min(max_amount, oxygen)
+ var/reaction_amount = min(max_amount, oxygen)/4
air.adjust_moles(GAS_NITRIC, -reaction_amount*2)
air.adjust_moles(GAS_O2, -reaction_amount)
air.adjust_moles(GAS_NITRYL, reaction_amount*2)
diff --git a/code/modules/cargo/bounty.dm b/code/modules/cargo/bounty.dm
index b888dc1a28..2a64f150a0 100644
--- a/code/modules/cargo/bounty.dm
+++ b/code/modules/cargo/bounty.dm
@@ -23,7 +23,7 @@ GLOBAL_LIST_EMPTY(bounties_list)
if(can_claim())
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(D)
- D.adjust_money(reward)
+ D.adjust_money(reward * SSeconomy.bounty_modifier)
claimed = TRUE
// If an item sent in the cargo shuttle can satisfy the bounty.
diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm
index b0eb932d85..390f02d419 100644
--- a/code/modules/cargo/console.dm
+++ b/code/modules/cargo/console.dm
@@ -99,7 +99,7 @@
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
data["cart"] += list(list(
"object" = SO.pack.name,
- "cost" = SO.pack.cost,
+ "cost" = SO.pack.get_cost(),
"id" = SO.id,
"orderer" = SO.orderer,
"paid" = !isnull(SO.paying_account) //paid by requester
@@ -109,7 +109,7 @@
for(var/datum/supply_order/SO in SSshuttle.requestlist)
data["requests"] += list(list(
"object" = SO.pack.name,
- "cost" = SO.pack.cost,
+ "cost" = SO.pack.get_cost(),
"orderer" = SO.orderer,
"reason" = SO.reason,
"id" = SO.id
@@ -132,7 +132,7 @@
continue
data["supplies"][P.group]["packs"] += list(list(
"name" = P.name,
- "cost" = P.cost,
+ "cost" = P.get_cost(),
"id" = pack,
"desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name.
"goody" = P.goody,
diff --git a/code/modules/cargo/exports/weapons.dm b/code/modules/cargo/exports/weapons.dm
index 42cd1dccea..5f2146200b 100644
--- a/code/modules/cargo/exports/weapons.dm
+++ b/code/modules/cargo/exports/weapons.dm
@@ -332,7 +332,7 @@
/datum/export/weapon/l6sawammo
cost = 60
unit_name = "L6 SAW ammo box"
- export_types = list(/obj/item/ammo_box/magazine/mm195x129)
+ export_types = list(/obj/item/ammo_box/magazine/mm712x82)
include_subtypes = TRUE
/datum/export/weapon/rocket
diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm
index 50f9541b1e..62fa7f8488 100644
--- a/code/modules/cargo/expressconsole.dm
+++ b/code/modules/cargo/expressconsole.dm
@@ -81,7 +81,7 @@
continue // i'd be right happy to
meme_pack_data[P.group]["packs"] += list(list(
"name" = P.name,
- "cost" = P.cost,
+ "cost" = P.get_cost(),
"id" = pack,
"desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name.
))
@@ -174,7 +174,7 @@
if(D)
points_to_check = D.account_balance
if(!(obj_flags & EMAGGED))
- if(SO.pack.cost <= points_to_check)
+ if(SO.pack.get_cost() <= points_to_check)
var/LZ
if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay
LZ = get_turf(beacon)
@@ -191,13 +191,13 @@
CHECK_TICK
if(empty_turfs && empty_turfs.len)
LZ = pick(empty_turfs)
- if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call
- D.adjust_money(-SO.pack.cost)
+ if (SO.pack.get_cost() <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call
+ D.adjust_money(-SO.pack.get_cost())
new /obj/effect/pod_landingzone(LZ, podType, SO)
. = TRUE
update_icon()
else
- if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^)
+ if(SO.pack.get_cost() * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^)
landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone
for(var/turf/open/floor/T in landingzone.contents)
if(is_blocked_turf(T))
@@ -205,7 +205,7 @@
LAZYADD(empty_turfs, T)
CHECK_TICK
if(empty_turfs && empty_turfs.len)
- D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS)))
+ D.adjust_money(-(SO.pack.get_cost() * (0.72*MAX_EMAG_ROCKETS)))
SO.generateRequisition(get_turf(src))
for(var/i in 1 to MAX_EMAG_ROCKETS)
diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm
index 13f1e77441..9d9478df4f 100644
--- a/code/modules/cargo/packs.dm
+++ b/code/modules/cargo/packs.dm
@@ -38,6 +38,9 @@
fill(C)
return C
+/datum/supply_pack/proc/get_cost()
+ return cost * SSeconomy.pack_price_modifier
+
/datum/supply_pack/proc/fill(obj/structure/closet/crate/C)
if (admin_spawned)
for(var/item in contains)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 8a9f122f43..e4056233c6 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -233,6 +233,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/screenshake = 100
var/damagescreenshake = 2
+ var/recoil_screenshake = 100
var/arousable = TRUE
var/autostand = TRUE
var/auto_ooc = FALSE
@@ -907,6 +908,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Screen Shake: [(screenshake==100) ? "Full" : ((screenshake==0) ? "None" : "[screenshake]")]
"
if (user && user.client && !user.client.prefs.screenshake==0)
dat += "Damage Screen Shake: [(damagescreenshake==1) ? "On" : ((damagescreenshake==0) ? "Off" : "Only when down")]
"
+ dat += "Recoil Screen Push: [(recoil_screenshake==100) ? "Full" : ((recoil_screenshake==0) ? "None" : "[screenshake]")]
"
var/p_chaos
if (!preferred_chaos)
p_chaos = "No preference"
@@ -2782,7 +2784,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("no_tetris_storage")
no_tetris_storage = !no_tetris_storage
if ("screenshake")
- var/desiredshake = input(user, "Set the amount of screenshake you want. \n(0 = disabled, 100 = full, 200 = maximum.)", "Character Preference", screenshake) as null|num
+ var/desiredshake = input(user, "Set the amount of screenshake you want. \n(0 = disabled, 100 = full, no maximum (at your own risk).)", "Character Preference", screenshake) as null|num
if (!isnull(desiredshake))
screenshake = desiredshake
if("damagescreenshake")
@@ -2795,6 +2797,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
damagescreenshake = 0
else
damagescreenshake = 1
+ if ("recoil_screenshake")
+ var/desiredshake = input(user, "Set the amount of recoil screenshake/push you want. \n(0 = disabled, 100 = full, no maximum (at your own risk).)", "Character Preference", screenshake) as null|num
+ if (!isnull(desiredshake))
+ recoil_screenshake = desiredshake
if("nameless")
nameless = !nameless
//END CITADEL EDIT
diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm
index 06c2c19ad8..cdf5512fc6 100644
--- a/code/modules/clothing/shoes/miscellaneous.dm
+++ b/code/modules/clothing/shoes/miscellaneous.dm
@@ -213,6 +213,7 @@
heat_protection = FEET|LEGS
max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF
+ lace_time = 10 SECONDS
/obj/item/clothing/shoes/cult
name = "\improper Nar'Sien invoker boots"
diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm
index d08e6267a0..f927a59ad6 100644
--- a/code/modules/events/carp_migration.dm
+++ b/code/modules/events/carp_migration.dm
@@ -6,6 +6,13 @@
earliest_start = 10 MINUTES
max_occurrences = 6
+/datum/round_event_control/carp_migration/New()
+ . = ..()
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_CARP_INFESTATION))
+ weight *= 3
+ max_occurrences *= 2
+ earliest_start *= 0.5
+
/datum/round_event/carp_migration
announceWhen = 3
startWhen = 50
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index 47a466bf0a..ed67c84a43 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -37,7 +37,7 @@
// if(PIRATES_DUTCHMAN)
// ship_name = "Flying Dutchman"
- priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", "commandreport")
+ priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", SSstation.announcer.get_rand_report_sound())
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(D)
payoff = max(payoff_min, FLOOR(D.account_balance * 0.80, 1000))
diff --git a/code/modules/events/untie_shoes.dm b/code/modules/events/untie_shoes.dm
index 92e53b4eab..708a72f40b 100644
--- a/code/modules/events/untie_shoes.dm
+++ b/code/modules/events/untie_shoes.dm
@@ -9,7 +9,7 @@
fakeable = FALSE
/datum/round_event/untied_shoes/start()
- var/iterations = 1
+ var/budget = rand(5 SECONDS,20 SECONDS)
for(var/mob/living/carbon/C in shuffle(GLOB.alive_mob_list))
if(!C.client)
continue
@@ -17,12 +17,15 @@
continue
if (HAS_TRAIT(C,TRAIT_EXEMPT_HEALTH_EVENTS))
continue
- if(!C.shoes || !C.shoes.can_be_tied || C.shoes.tied != SHOES_TIED)
+ if(!C.shoes || !C.shoes.can_be_tied || C.shoes.tied != SHOES_TIED || C.shoes.lace_time > budget)
+ continue
+ if(!is_station_level(C.z) && prob(50))
continue
if(prob(5))
C.shoes.adjust_laces(SHOES_KNOTTED)
+ budget -= C.shoes.lace_time // doubling up on the budget removal on purpose
else
C.shoes.adjust_laces(SHOES_UNTIED)
- iterations++
- if(prob(100/iterations))
+ budget -= C.shoes.lace_time
+ if(budget < 5 SECONDS)
return
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index c610543cdd..5920c33df1 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -888,7 +888,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
if("blob alert")
to_chat(target, "Biohazard Alert
")
to_chat(target, "
Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.
")
- SEND_SOUND(target, get_announcer_sound("outbreak5"))
+ SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_OUTBREAK5])
if("ratvar")
target.playsound_local(target, 'sound/machines/clockcult/ark_deathrattle.ogg', 50, FALSE, pressure_affected = FALSE)
target.playsound_local(target, 'sound/effects/clockcult_gateway_disrupted.ogg', 50, FALSE, pressure_affected = FALSE)
@@ -897,15 +897,15 @@ GLOBAL_LIST_INIT(hallucination_list, list(
if("shuttle dock")
to_chat(target, "Priority Announcement
")
to_chat(target, "
The Emergency Shuttle has docked with the station. You have 3 minutes to board the Emergency Shuttle.
")
- SEND_SOUND(target, get_announcer_sound("shuttledock"))
+ SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLEDOCK])
if("malf ai") //AI is doomsdaying!
to_chat(target, "Anomaly Alert
")
to_chat(target, "
Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.
")
- SEND_SOUND(target, get_announcer_sound("aimalf"))
+ SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_AIMALF])
if("meteors") //Meteors inbound!
to_chat(target, "Meteor Alert
")
to_chat(target, "
[generateMeteorString(rand(60, 90),FALSE,pick(GLOB.cardinals))]
")
- SEND_SOUND(target, get_announcer_sound("meteors"))
+ SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_METEORS])
if("supermatter")
SEND_SOUND(target, 'sound/magic/charge.ogg')
to_chat(target, "You feel reality distort for a moment...")
diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm
index a6a632b859..f4185ccf77 100644
--- a/code/modules/food_and_drinks/drinks/drinks.dm
+++ b/code/modules/food_and_drinks/drinks/drinks.dm
@@ -347,6 +347,9 @@
foodtype = GRAIN | ALCOHOL
custom_price = PRICE_PRETTY_CHEAP
+/obj/item/reagent_containers/food/drinks/beer/almost_empty
+ list_reagents = list(/datum/reagent/consumable/ethanol/beer = 1)
+
/obj/item/reagent_containers/food/drinks/beer/light
name = "Carp Lite"
desc = "Brewed with \"Pure Ice Asteroid Spring Water\"."
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index e66a173404..ff9f86e932 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -66,6 +66,9 @@
/// Should this job be allowed to be picked for the bureaucratic error event?
var/allow_bureaucratic_error = TRUE
+ ///Is this job affected by weird spawns like the ones from station traits
+ var/random_spawns_possible = TRUE
+
var/display_order = JOB_DISPLAY_ORDER_DEFAULT
//If a job complies with dresscodes, loadout items will not be equipped instead of the job's outfit, instead placing the items into the player's backpack.
@@ -109,14 +112,16 @@
//Only override this proc
//H is usually a human unless an /equip override transformed it
-/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
+/datum/job/proc/after_spawn(mob/living/spawned, client/player_client, latejoin = FALSE)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_SPAWN, src, spawned, player_client)
//do actions on H but send messages to M as the key may not have been transferred_yet
if(mind_traits)
for(var/t in mind_traits)
- ADD_TRAIT(H.mind, t, JOB_TRAIT)
+ ADD_TRAIT(spawned.mind, t, JOB_TRAIT)
if(/datum/quirk/paraplegic in blacklisted_quirks)
- H.regenerate_limbs() //if you can't be a paraplegic, attempt to regenerate limbs to stop amputated limb selection
- H.set_resting(FALSE, TRUE) //they probably shouldn't be on the floor because they had no legs then suddenly had legs
+ spawned.regenerate_limbs() //if you can't be a paraplegic, attempt to regenerate limbs to stop amputated limb selection
+ spawned.set_resting(FALSE, TRUE) //they probably shouldn't be on the floor because they had no legs then suddenly had legs
/datum/job/proc/announce(mob/living/carbon/human/H)
if(head_announce)
@@ -323,3 +328,66 @@
if(CONFIG_GET(flag/security_has_maint_access))
return list(ACCESS_MAINT_TUNNELS)
return list()
+
+/// Handles finding and picking a valid roundstart effect landmark spawn point, in case no uncommon different spawning events occur.
+/datum/job/proc/get_default_roundstart_spawn_point()
+ for(var/obj/effect/landmark/start/spawn_point as anything in GLOB.start_landmarks_list)
+ if(spawn_point.name != title)
+ continue
+ . = spawn_point
+ if(spawn_point.used) //so we can revert to spawning them on top of eachother if something goes wrong
+ continue
+ spawn_point.used = TRUE
+ break
+ if(!.)
+ log_world("Couldn't find a round start spawn point for [title]")
+
+/// Finds a valid latejoin spawn point, checking for events and special conditions.
+/datum/job/proc/get_latejoin_spawn_point()
+ if(length(GLOB.jobspawn_overrides[title])) //We're doing something special today.
+ return pick(GLOB.jobspawn_overrides[title])
+ if(length(SSjob.latejoin_trackers))
+ return pick(SSjob.latejoin_trackers)
+ return SSjob.get_last_resort_spawn_points()
+
+/// Returns an atom where the mob should spawn in.
+/datum/job/proc/get_roundstart_spawn_point(var/mob/M)
+ if(random_spawns_possible)
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_LATE_ARRIVALS))
+ return get_latejoin_spawn_point()
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_RANDOM_ARRIVALS))
+ return get_safe_random_station_turf(typesof(/area/hallway)) || get_latejoin_spawn_point()
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_HANGOVER))
+ if(!M || (!HAS_TRAIT(M, TRAIT_TOXIC_ALCOHOL) && !(HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE) && prob(70))))
+ var/obj/effect/landmark/start/hangover_spawn_point
+ for(var/obj/effect/landmark/start/hangover/hangover_landmark in GLOB.start_landmarks_list)
+ hangover_spawn_point = hangover_landmark
+ if(hangover_landmark.used) //so we can revert to spawning them on top of eachother if something goes wrong
+ continue
+ hangover_landmark.used = TRUE
+ break
+ return hangover_spawn_point || get_latejoin_spawn_point()
+ if(length(GLOB.jobspawn_overrides[title]))
+ return pick(GLOB.jobspawn_overrides[title])
+ var/obj/effect/landmark/start/spawn_point = get_default_roundstart_spawn_point()
+ if(!spawn_point) //if there isn't a spawnpoint send them to latejoin, if there's no latejoin go yell at your mapper
+ return get_latejoin_spawn_point()
+ return spawn_point
+
+/**
+ * Called after a successful roundstart spawn.
+ * Client is not yet in the mob.
+ * This happens after after_spawn()
+ */
+/datum/job/proc/after_roundstart_spawn(mob/living/spawning, client/player_client)
+ SHOULD_CALL_PARENT(TRUE)
+
+
+/**
+ * Called after a successful latejoin spawn.
+ * Client is in the mob.
+ * This happens after after_spawn()
+ */
+/datum/job/proc/after_latejoin_spawn(mob/living/spawning)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN, src, spawning)
diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm
index 171ea047e6..c5cb978f40 100644
--- a/code/modules/jobs/job_types/ai.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -15,6 +15,7 @@
exp_type_department = EXP_TYPE_SILICON
display_order = JOB_DISPLAY_ORDER_AI
allow_bureaucratic_error = FALSE
+ random_spawns_possible = FALSE
var/do_special_check = TRUE
threat = 5
considered_combat_role = TRUE
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
index dc2f60434c..380215a93b 100644
--- a/code/modules/jobs/job_types/clown.dm
+++ b/code/modules/jobs/job_types/clown.dm
@@ -49,6 +49,11 @@
chameleon_extras = /obj/item/stamp/clown
+/datum/outfit/job/clown/pre_equip(mob/living/carbon/human/H, visualsOnly)
+ . = ..()
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_BANANIUM_SHIPMENTS))
+ backpack_contents[/obj/item/stack/sheet/mineral/bananium] = 5
+
/datum/outfit/job/clown/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
..()
if(visualsOnly)
diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm
index 17a7cfad74..280d7616c2 100644
--- a/code/modules/jobs/job_types/cyborg.dm
+++ b/code/modules/jobs/job_types/cyborg.dm
@@ -12,7 +12,7 @@
exp_requirements = 120
exp_type = EXP_TYPE_CREW
considered_combat_role = TRUE
-
+ random_spawns_possible = FALSE
starting_modifiers = list(/datum/skill_modifier/job/level/wiring/basic)
display_order = JOB_DISPLAY_ORDER_CYBORG
@@ -22,8 +22,11 @@
CRASH("dynamic preview is unsupported")
return H.Robotize(FALSE, latejoin)
-/datum/job/cyborg/after_spawn(mob/living/silicon/robot/R, mob/M)
- R.updatename(M.client)
+/datum/job/cyborg/after_spawn(mob/living/silicon/robot/R, client/player_client)
+ . = ..()
+ if(!istype(R))
+ return
+ R.updatename(player_client)
R.gender = NEUTER
/datum/job/cyborg/radio_help_message(mob/M)
diff --git a/code/modules/jobs/job_types/prisoner.dm b/code/modules/jobs/job_types/prisoner.dm
index ef2ad0fc27..12d2471568 100644
--- a/code/modules/jobs/job_types/prisoner.dm
+++ b/code/modules/jobs/job_types/prisoner.dm
@@ -7,13 +7,18 @@
total_positions = 0
spawn_positions = 0
supervisors = "the security team"
+ random_spawns_possible = FALSE
outfit = /datum/outfit/job/prisoner
plasma_outfit = /datum/outfit/plasmaman/prisoner
display_order = JOB_DISPLAY_ORDER_PRISONER
+/datum/job/prisoner/get_latejoin_spawn_point()
+ return get_roundstart_spawn_point()
+
/datum/job/prisoner/after_spawn(mob/living/carbon/human/H, mob/M)
+ . = ..()
var/list/policies = CONFIG_GET(keyed_list/policy)
var/policy = policies[POLICYCONFIG_JOB_PRISONER]
if(policy)
diff --git a/code/modules/mapping/map_config.dm b/code/modules/mapping/map_config.dm
index 1f4b558e21..b690419ade 100644
--- a/code/modules/mapping/map_config.dm
+++ b/code/modules/mapping/map_config.dm
@@ -30,8 +30,6 @@
var/maptype = MAP_TYPE_STATION //This should be used to adjust ingame behavior depending on the specific type of map being played. For instance, if an overmap were added, it'd be appropriate for it to only generate with a MAP_TYPE_SHIP
- var/announcertype = "standard" //Determines the announcer the map uses. standard uses the default announcer, classic, but has a random chance to use other similarly-themed announcers, like medibot
-
var/allow_custom_shuttles = TRUE
var/shuttles = list(
"cargo" = "cargo_box",
@@ -178,9 +176,6 @@
if ("maptype" in json)
maptype = json["maptype"]
- if ("announcertype" in json)
- announcertype = json["announcertype"]
-
if ("orientation" in json)
orientation = json["orientation"]
if(!(orientation in GLOB.cardinals))
@@ -277,7 +272,6 @@
jsonlist["year_offset"] = year_offset
jsonlist["minetype"] = minetype
jsonlist["maptype"] = maptype
- jsonlist["announcertype"] = announcertype
jsonlist["orientation"] = orientation
jsonlist["allow_custom_shuttles"] = allow_custom_shuttles
jsonlist["job_whitelist"] = job_whitelist
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index 8ede497c83..561dc9c096 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -19,6 +19,7 @@
attack_verb = list("smashed", "crushed", "cleaved", "chopped", "pulped")
sharpness = SHARP_EDGED
actions_types = list(/datum/action/item_action/toggle_light)
+ obj_flags = UNIQUE_RENAME
var/list/trophies = list()
var/charged = TRUE
var/charge_time = 15
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index cfcb881977..069be0a8ab 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -468,12 +468,14 @@
SSjob.AssignRole(src, rank, 1)
var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind
+ var/datum/job/job = SSjob.GetJob(rank)
+
var/equip = SSjob.EquipRank(character, rank, TRUE)
+ job.after_latejoin_spawn(character)
+
if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob.
character = equip
- var/datum/job/job = SSjob.GetJob(rank)
-
if(job && !job.override_latejoin_spawn(character))
SSjob.SendToLateJoin(character)
if(!arrivals_docked)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 77ad6c7d5a..94e0685e62 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -832,15 +832,15 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/left_state = DEFAULT_LEFT_EYE_STATE
var/right_state = DEFAULT_RIGHT_EYE_STATE
if(eye_type in GLOB.eye_types)
- left_state = eye_type + "_left_eye"
- right_state = eye_type + "_right_eye"
+ left_state = "[eye_type]_left_eye"
+ right_state = "[eye_type]_right_eye"
var/mutable_appearance/left_eye = mutable_appearance('icons/mob/eyes.dmi', left_state, -BODY_LAYER)
var/mutable_appearance/right_eye = mutable_appearance('icons/mob/eyes.dmi', right_state, -BODY_LAYER)
left_eye.category = "HEAD"
right_eye.category = "HEAD"
if((EYECOLOR in species_traits) && has_eyes)
- left_eye.color = "#" + H.left_eye_color
- right_eye.color = "#" + H.right_eye_color
+ left_eye.color = "#[H.left_eye_color]"
+ right_eye.color = "#[H.right_eye_color]"
if(OFFSET_EYES in offset_features)
left_eye.pixel_x += offset_features[OFFSET_EYES][1]
left_eye.pixel_y += offset_features[OFFSET_EYES][2]
diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm
index 16d4ea61fa..e4d042a4cc 100644
--- a/code/modules/mob/living/simple_animal/friendly/dog.dm
+++ b/code/modules/mob/living/simple_animal/friendly/dog.dm
@@ -366,6 +366,8 @@ GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list(
return valid
+
+
/mob/living/simple_animal/pet/dog/corgi/proc/update_corgi_fluff()
// First, change back to defaults
name = real_name
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index 265785d9ce..eb6c29df5f 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -436,7 +436,7 @@ Difficulty: Very Hard
H.dropItemToGround(W)
var/datum/job/clown/C = new /datum/job/clown()
C.equip(H)
- C.after_spawn(H, H, TRUE)
+ C.after_spawn(H, H.client, TRUE)
qdel(C)
affected_targets.Add(H)
diff --git a/code/modules/mob/living/simple_animal/hostile/netherworld.dm b/code/modules/mob/living/simple_animal/hostile/netherworld.dm
index d538cc0412..a1e135d038 100644
--- a/code/modules/mob/living/simple_animal/hostile/netherworld.dm
+++ b/code/modules/mob/living/simple_animal/hostile/netherworld.dm
@@ -36,7 +36,7 @@
/mob/living/simple_animal/hostile/netherworld/migo/Initialize(mapload)
. = ..()
- migo_sounds = list('sound/items/bubblewrap.ogg', 'sound/items/change_jaws.ogg', 'sound/items/crowbar.ogg', 'sound/items/drink.ogg', 'sound/items/deconstruct.ogg', 'sound/items/carhorn.ogg', 'sound/items/change_drill.ogg', 'sound/items/dodgeball.ogg', 'sound/items/eatfood.ogg', 'sound/items/megaphone.ogg', 'sound/items/screwdriver.ogg', 'sound/items/weeoo1.ogg', 'sound/items/wirecutter.ogg', 'sound/items/welder.ogg', 'sound/items/zip.ogg', 'sound/items/rped.ogg', 'sound/items/ratchet.ogg', 'sound/items/polaroid1.ogg', 'sound/items/pshoom.ogg', 'sound/items/airhorn.ogg', 'sound/items/geiger/high1.ogg', 'sound/items/geiger/high2.ogg', 'sound/voice/beepsky/creep.ogg', 'sound/voice/beepsky/iamthelaw.ogg', 'sound/voice/ed209_20sec.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss6.ogg', 'sound/voice/medbot/patchedup.ogg', 'sound/voice/medbot/feelbetter.ogg', 'sound/voice/human/manlaugh1.ogg', 'sound/voice/human/womanlaugh.ogg', 'sound/weapons/sear.ogg', 'sound/ambience/antag/clockcultalr.ogg', 'sound/ambience/antag/ling_aler.ogg', 'sound/ambience/antag/tatoralert.ogg', 'sound/ambience/antag/monkey.ogg', 'sound/mecha/nominal.ogg', 'sound/mecha/weapdestr.ogg', 'sound/mecha/critdestr.ogg', 'sound/mecha/imag_enh.ogg', 'sound/effects/adminhelp.ogg', 'sound/effects/alert.ogg', 'sound/effects/attackblob.ogg', 'sound/effects/bamf.ogg', 'sound/effects/blobattack.ogg', 'sound/effects/break_stone.ogg', 'sound/effects/bubbles.ogg', 'sound/effects/bubbles2.ogg', 'sound/effects/clang.ogg', 'sound/effects/clockcult_gateway_disrupted.ogg', 'sound/effects/clownstep2.ogg', 'sound/effects/curse1.ogg', 'sound/effects/dimensional_rend.ogg', 'sound/effects/doorcreaky.ogg', 'sound/effects/empulse.ogg', 'sound/effects/explosion_distant.ogg', 'sound/effects/explosionfar.ogg', 'sound/effects/explosion1.ogg', 'sound/effects/grillehit.ogg', 'sound/effects/genetics.ogg', 'sound/effects/heart_beat.ogg', 'sound/effects/hyperspace_begin.ogg', 'sound/effects/hyperspace_end.ogg', 'sound/effects/his_grace_awaken.ogg', 'sound/effects/pai_boot.ogg', 'sound/effects/phasein.ogg', 'sound/effects/picaxe1.ogg', 'sound/effects/ratvar_reveal.ogg', 'sound/effects/sparks1.ogg', 'sound/effects/smoke.ogg', 'sound/effects/splat.ogg', 'sound/effects/snap.ogg', 'sound/effects/tendril_destroyed.ogg', 'sound/effects/supermatter.ogg', 'sound/misc/desceration-01.ogg', 'sound/misc/desceration-02.ogg', 'sound/misc/desceration-03.ogg', 'sound/misc/bloblarm.ogg', 'sound/misc/airraid.ogg', 'sound/misc/bang.ogg','sound/misc/highlander.ogg', 'sound/misc/interference.ogg', 'sound/misc/notice1.ogg', 'sound/misc/notice2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/misc/slip.ogg', 'sound/misc/splort.ogg', 'sound/weapons/armbomb.ogg', 'sound/weapons/beam_sniper.ogg', 'sound/weapons/chainsawhit.ogg', 'sound/weapons/emitter.ogg', 'sound/weapons/emitter2.ogg', 'sound/weapons/blade1.ogg', 'sound/weapons/bladeslice.ogg', 'sound/weapons/blastcannon.ogg', 'sound/weapons/blaster.ogg', 'sound/weapons/bulletflyby3.ogg', 'sound/weapons/circsawhit.ogg', 'sound/weapons/cqchit2.ogg', 'sound/weapons/drill.ogg', 'sound/weapons/genhit1.ogg', 'sound/weapons/gunshot_silenced.ogg', 'sound/weapons/gunshot2.ogg', 'sound/weapons/handcuffs.ogg', 'sound/weapons/homerun.ogg', 'sound/weapons/kenetic_accel.ogg', 'sound/machines/clockcult/steam_whoosh.ogg', 'sound/machines/fryer/deep_fryer_emerge.ogg', 'sound/machines/airlock.ogg', 'sound/machines/airlock_alien_prying.ogg', 'sound/machines/airlockclose.ogg', 'sound/machines/airlockforced.ogg', 'sound/machines/airlockopen.ogg', 'sound/machines/alarm.ogg', 'sound/machines/blender.ogg', 'sound/machines/boltsdown.ogg', 'sound/machines/boltsup.ogg', 'sound/machines/buzz-sigh.ogg', 'sound/machines/buzz-two.ogg', 'sound/machines/chime.ogg', 'sound/machines/cryo_warning.ogg', 'sound/machines/defib_charge.ogg', 'sound/machines/defib_failed.ogg', 'sound/machines/defib_ready.ogg', 'sound/machines/defib_zap.ogg', 'sound/machines/deniedbeep.ogg', 'sound/machines/ding.ogg', 'sound/machines/disposalflush.ogg', 'sound/machines/door_close.ogg', 'sound/machines/door_open.ogg', 'sound/machines/engine_alert1.ogg', 'sound/machines/engine_alert2.ogg', 'sound/machines/hiss.ogg', 'sound/machines/honkbot_evil_laugh.ogg', 'sound/machines/juicer.ogg', 'sound/machines/ping.ogg', 'sound/machines/signal.ogg', 'sound/machines/synth_no.ogg', 'sound/machines/synth_yes.ogg', 'sound/machines/terminal_alert.ogg', 'sound/machines/triple_beep.ogg', 'sound/machines/twobeep.ogg', 'sound/machines/ventcrawl.ogg', 'sound/machines/warning-buzzer.ogg', get_announcer_sound("outbreak5"), get_announcer_sound("outbreak7"), get_announcer_sound("poweroff"), get_announcer_sound("radiation"), get_announcer_sound("shuttlerecalled"), get_announcer_sound("shuttledock"), get_announcer_sound("shuttlecalled"), get_announcer_sound("aimalf")) //hahahaha fuck you code divers
+ migo_sounds = list('sound/items/bubblewrap.ogg', 'sound/items/change_jaws.ogg', 'sound/items/crowbar.ogg', 'sound/items/drink.ogg', 'sound/items/deconstruct.ogg', 'sound/items/carhorn.ogg', 'sound/items/change_drill.ogg', 'sound/items/dodgeball.ogg', 'sound/items/eatfood.ogg', 'sound/items/megaphone.ogg', 'sound/items/screwdriver.ogg', 'sound/items/weeoo1.ogg', 'sound/items/wirecutter.ogg', 'sound/items/welder.ogg', 'sound/items/zip.ogg', 'sound/items/rped.ogg', 'sound/items/ratchet.ogg', 'sound/items/polaroid1.ogg', 'sound/items/pshoom.ogg', 'sound/items/airhorn.ogg', 'sound/items/geiger/high1.ogg', 'sound/items/geiger/high2.ogg', 'sound/voice/beepsky/creep.ogg', 'sound/voice/beepsky/iamthelaw.ogg', 'sound/voice/ed209_20sec.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss6.ogg', 'sound/voice/medbot/patchedup.ogg', 'sound/voice/medbot/feelbetter.ogg', 'sound/voice/human/manlaugh1.ogg', 'sound/voice/human/womanlaugh.ogg', 'sound/weapons/sear.ogg', 'sound/ambience/antag/clockcultalr.ogg', 'sound/ambience/antag/ling_aler.ogg', 'sound/ambience/antag/tatoralert.ogg', 'sound/ambience/antag/monkey.ogg', 'sound/mecha/nominal.ogg', 'sound/mecha/weapdestr.ogg', 'sound/mecha/critdestr.ogg', 'sound/mecha/imag_enh.ogg', 'sound/effects/adminhelp.ogg', 'sound/effects/alert.ogg', 'sound/effects/attackblob.ogg', 'sound/effects/bamf.ogg', 'sound/effects/blobattack.ogg', 'sound/effects/break_stone.ogg', 'sound/effects/bubbles.ogg', 'sound/effects/bubbles2.ogg', 'sound/effects/clang.ogg', 'sound/effects/clockcult_gateway_disrupted.ogg', 'sound/effects/clownstep2.ogg', 'sound/effects/curse1.ogg', 'sound/effects/dimensional_rend.ogg', 'sound/effects/doorcreaky.ogg', 'sound/effects/empulse.ogg', 'sound/effects/explosion_distant.ogg', 'sound/effects/explosionfar.ogg', 'sound/effects/explosion1.ogg', 'sound/effects/grillehit.ogg', 'sound/effects/genetics.ogg', 'sound/effects/heart_beat.ogg', 'sound/effects/hyperspace_begin.ogg', 'sound/effects/hyperspace_end.ogg', 'sound/effects/his_grace_awaken.ogg', 'sound/effects/pai_boot.ogg', 'sound/effects/phasein.ogg', 'sound/effects/picaxe1.ogg', 'sound/effects/ratvar_reveal.ogg', 'sound/effects/sparks1.ogg', 'sound/effects/smoke.ogg', 'sound/effects/splat.ogg', 'sound/effects/snap.ogg', 'sound/effects/tendril_destroyed.ogg', 'sound/effects/supermatter.ogg', 'sound/misc/desceration-01.ogg', 'sound/misc/desceration-02.ogg', 'sound/misc/desceration-03.ogg', 'sound/misc/bloblarm.ogg', 'sound/misc/airraid.ogg', 'sound/misc/bang.ogg','sound/misc/highlander.ogg', 'sound/misc/interference.ogg', 'sound/misc/notice1.ogg', 'sound/misc/notice2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/misc/slip.ogg', 'sound/misc/splort.ogg', 'sound/weapons/armbomb.ogg', 'sound/weapons/beam_sniper.ogg', 'sound/weapons/chainsawhit.ogg', 'sound/weapons/emitter.ogg', 'sound/weapons/emitter2.ogg', 'sound/weapons/blade1.ogg', 'sound/weapons/bladeslice.ogg', 'sound/weapons/blastcannon.ogg', 'sound/weapons/blaster.ogg', 'sound/weapons/bulletflyby3.ogg', 'sound/weapons/circsawhit.ogg', 'sound/weapons/cqchit2.ogg', 'sound/weapons/drill.ogg', 'sound/weapons/genhit1.ogg', 'sound/weapons/gunshot_silenced.ogg', 'sound/weapons/gunshot2.ogg', 'sound/weapons/handcuffs.ogg', 'sound/weapons/homerun.ogg', 'sound/weapons/kenetic_accel.ogg', 'sound/machines/clockcult/steam_whoosh.ogg', 'sound/machines/fryer/deep_fryer_emerge.ogg', 'sound/machines/airlock.ogg', 'sound/machines/airlock_alien_prying.ogg', 'sound/machines/airlockclose.ogg', 'sound/machines/airlockforced.ogg', 'sound/machines/airlockopen.ogg', 'sound/machines/alarm.ogg', 'sound/machines/blender.ogg', 'sound/machines/boltsdown.ogg', 'sound/machines/boltsup.ogg', 'sound/machines/buzz-sigh.ogg', 'sound/machines/buzz-two.ogg', 'sound/machines/chime.ogg', 'sound/machines/cryo_warning.ogg', 'sound/machines/defib_charge.ogg', 'sound/machines/defib_failed.ogg', 'sound/machines/defib_ready.ogg', 'sound/machines/defib_zap.ogg', 'sound/machines/deniedbeep.ogg', 'sound/machines/ding.ogg', 'sound/machines/disposalflush.ogg', 'sound/machines/door_close.ogg', 'sound/machines/door_open.ogg', 'sound/machines/engine_alert1.ogg', 'sound/machines/engine_alert2.ogg', 'sound/machines/hiss.ogg', 'sound/machines/honkbot_evil_laugh.ogg', 'sound/machines/juicer.ogg', 'sound/machines/ping.ogg', 'sound/machines/signal.ogg', 'sound/machines/synth_no.ogg', 'sound/machines/synth_yes.ogg', 'sound/machines/terminal_alert.ogg', 'sound/machines/triple_beep.ogg', 'sound/machines/twobeep.ogg', 'sound/machines/ventcrawl.ogg', 'sound/machines/warning-buzzer.ogg', SSstation.announcer.event_sounds[ANNOUNCER_OUTBREAK5], SSstation.announcer.event_sounds[ANNOUNCER_OUTBREAK7], SSstation.announcer.event_sounds[ANNOUNCER_POWEROFF], SSstation.announcer.event_sounds[ANNOUNCER_RADIATION], SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLERECALLED], SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLEDOCK], SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLECALLED], SSstation.announcer.event_sounds[ANNOUNCER_AIMALF]) //hahahaha fuck you code divers
/mob/living/simple_animal/hostile/netherworld/migo/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
..()
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 8595839831..90a7aae95e 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -250,7 +250,17 @@ It's fairly easy to fix if dealing with single letters but not so much with comp
animate(pixel_x=rand(min,max), pixel_y=rand(min,max), time=1)
animate(pixel_x=oldx, pixel_y=oldy, time=1)
-
+/proc/directional_recoil(mob/M, strength=1, angle = 0)
+ if(!M || !M.client)
+ return
+ var/client/C = M.client
+ var/client_screenshake = (C.prefs.recoil_screenshake * 0.01)
+ strength *= client_screenshake
+ var/recoil_x = -sin(angle)*4*strength + rand(-strength, strength)
+ var/recoil_y = -cos(angle)*4*strength + rand(-strength, strength)
+ animate(C, pixel_x=recoil_x, pixel_y=recoil_y, time=1, easing=SINE_EASING|EASE_OUT, flags=ANIMATION_PARALLEL|ANIMATION_RELATIVE)
+ animate(pixel_x=0, pixel_y=0, time=3, easing=SINE_EASING|EASE_IN) // according to bhjin this works on more recent byond versions
+ // if you havent updated uuh sucks to be you then
/proc/findname(msg)
if(!istext(msg))
diff --git a/code/modules/modular_computers/file_system/programs/budgetordering.dm b/code/modules/modular_computers/file_system/programs/budgetordering.dm
index 84ddab9e37..83c1ae3f95 100644
--- a/code/modules/modular_computers/file_system/programs/budgetordering.dm
+++ b/code/modules/modular_computers/file_system/programs/budgetordering.dm
@@ -104,7 +104,7 @@
continue
data["supplies"][P.group]["packs"] += list(list(
"name" = P.name,
- "cost" = P.cost,
+ "cost" = P.get_cost(),
"id" = pack,
"desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name.
"goody" = P.goody,
@@ -131,7 +131,7 @@
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
data["cart"] += list(list(
"object" = SO.pack.name,
- "cost" = SO.pack.cost,
+ "cost" = SO.pack.get_cost(),
"id" = SO.id,
"orderer" = SO.orderer,
"paid" = !isnull(SO.paying_account) //paid by requester
@@ -141,7 +141,7 @@
for(var/datum/supply_order/SO in SSshuttle.requestlist)
data["requests"] += list(list(
"object" = SO.pack.name,
- "cost" = SO.pack.cost,
+ "cost" = SO.pack.get_cost(),
"orderer" = SO.orderer,
"reason" = SO.reason,
"id" = SO.id
diff --git a/code/modules/modular_computers/file_system/programs/signaler.dm b/code/modules/modular_computers/file_system/programs/signaler.dm
index b7bbcacaa0..ded70f7f37 100644
--- a/code/modules/modular_computers/file_system/programs/signaler.dm
+++ b/code/modules/modular_computers/file_system/programs/signaler.dm
@@ -15,40 +15,29 @@
/// Radio connection datum used by signalers.
var/datum/radio_frequency/radio_connection
-/datum/computer_file/program/signaler/run_program(mob/living/user)
- . = ..()
- if (!.)
- return
- if(!computer?.get_modular_computer_part(MC_SIGNALER)) //Giving a clue to users why the program is spitting out zeros.
- to_chat(user, span_warning("\The [computer] flashes an error: \"hardware\\signal_hardware\\startup.bin -- file not found\"."))
-
+/datum/computer_file/program/signaler/New()
+ set_frequency(signal_frequency)
+ return ..()
/datum/computer_file/program/signaler/ui_data(mob/user)
var/list/data = get_header_data()
- var/obj/item/computer_hardware/radio_card/sensor = computer?.get_modular_computer_part(MC_SIGNALER)
- if(sensor?.check_functionality())
- data["frequency"] = signal_frequency
- data["code"] = signal_code
- data["minFrequency"] = MIN_FREE_FREQ
- data["maxFrequency"] = MAX_FREE_FREQ
+ data["frequency"] = signal_frequency
+ data["code"] = signal_code
+ data["minFrequency"] = MIN_FREE_FREQ
+ data["maxFrequency"] = MAX_FREE_FREQ
return data
/datum/computer_file/program/signaler/ui_act(action, list/params)
. = ..()
if(.)
return
- var/obj/item/computer_hardware/radio_card/sensor = computer?.get_modular_computer_part(MC_SIGNALER)
- if(!(sensor?.check_functionality()))
- playsound(src, 'sound/machines/scanbuzz.ogg', 100, FALSE)
- return
switch(action)
if("signal")
INVOKE_ASYNC(src, .proc/signal)
. = TRUE
if("freq")
- signal_frequency = unformat_frequency(params["freq"])
- signal_frequency = sanitize_frequency(signal_frequency, TRUE)
- set_frequency(signal_frequency)
+ var/new_signal_frequency = sanitize_frequency(unformat_frequency(params["freq"]), TRUE)
+ set_frequency(new_signal_frequency)
. = TRUE
if("code")
signal_code = text2num(params["code"])
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 0ad49e192d..cb9570e02f 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -173,7 +173,7 @@
O.renamedByPlayer = TRUE
if(penchoice == "Change description")
- var/input = stripped_input(user,"Describe [O] here:", ,"[O.desc]", 140)
+ var/input = stripped_input(user,"Describe [O] here:", ,"[O.desc]", 350)
var/olddesc = O.desc
if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE))
return
diff --git a/code/modules/projectiles/ammunition/ballistic/lmg.dm b/code/modules/projectiles/ammunition/ballistic/lmg.dm
index bf8b4007bf..1c39a73f1f 100644
--- a/code/modules/projectiles/ammunition/ballistic/lmg.dm
+++ b/code/modules/projectiles/ammunition/ballistic/lmg.dm
@@ -1,36 +1,28 @@
-// 1.95x129mm (SAW)
+// 7.12x82mm (SAW)
-/obj/item/ammo_casing/mm195x129
- name = "1.95x129mm bullet casing"
- desc = "A 1.95x129mm bullet casing."
+/obj/item/ammo_casing/mm712x82
+ name = "7.12x82mm bullet casing"
+ desc = "A 7.12x82mm bullet casing."
icon_state = "762-casing"
- caliber = "mm195129"
- projectile_type = /obj/item/projectile/bullet/mm195x129
+ caliber = "mm71282"
+ projectile_type = /obj/item/projectile/bullet/mm712x82
-/obj/item/ammo_casing/mm195x129/ap
- name = "1.95x129mm armor-piercing bullet casing"
- desc = "A 1.95x129mm bullet casing designed with a hardened-tipped core to help penetrate armored targets."
- projectile_type = /obj/item/projectile/bullet/mm195x129_ap
+/obj/item/ammo_casing/mm712x82/ap
+ name = "7.12x82mm armor-piercing bullet casing"
+ desc = "A 7.12x82mm bullet casing designed with a hardened-tipped core to help penetrate armored targets."
+ projectile_type = /obj/item/projectile/bullet/mm712x82_ap
-/obj/item/ammo_casing/mm195x129/hollow
- name = "1.95x129mm hollow-point bullet casing"
- desc = "A 1.95x129mm bullet casing designed to cause more damage to unarmored targets."
- projectile_type = /obj/item/projectile/bullet/mm195x129_hp
+/obj/item/ammo_casing/mm712x82/hollow
+ name = "7.12x82mm hollow-point bullet casing"
+ desc = "A 7.12x82mm bullet casing designed to cause more damage to unarmored targets."
+ projectile_type = /obj/item/projectile/bullet/mm712x82_hp
-/obj/item/ammo_casing/mm195x129/incen
- name = "1.95x129mm incendiary bullet casing"
- desc = "A 1.95x129mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames."
- projectile_type = /obj/item/projectile/bullet/incendiary/mm195x129
+/obj/item/ammo_casing/mm712x82/incen
+ name = "7.12x82mm incendiary bullet casing"
+ desc = "A 7.12x82mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames."
+ projectile_type = /obj/item/projectile/bullet/incendiary/mm712x82
/obj/item/ammo_casing/mm712x82/match
name = "7.12x82mm match bullet casing"
desc = "A 7.12x82mm bullet casing manufactured to unfailingly high standards, you could pull off some cool trickshots with this."
- projectile_type = /obj/item/projectile/bullet/mm712x82_match
-
-/obj/item/projectile/bullet/mm712x82_match
- name = "7.12x82mm match bullet"
- damage = 40
- ricochets_max = 2
- ricochet_chance = 60
- ricochet_auto_aim_range = 4
- ricochet_incidence_leeway = 35
+ projectile_type = /obj/item/projectile/bullet/mm712x82/match
diff --git a/code/modules/projectiles/boxes_magazines/external/lmg.dm b/code/modules/projectiles/boxes_magazines/external/lmg.dm
index 95ba17c733..e8447950ce 100644
--- a/code/modules/projectiles/boxes_magazines/external/lmg.dm
+++ b/code/modules/projectiles/boxes_magazines/external/lmg.dm
@@ -1,23 +1,23 @@
-/obj/item/ammo_box/magazine/mm195x129
- name = "box magazine (1.95x129mm)"
+/obj/item/ammo_box/magazine/mm712x82
+ name = "box magazine (7.12x82mm)"
icon_state = "a762-50"
- ammo_type = /obj/item/ammo_casing/mm195x129
- caliber = "mm195129"
+ ammo_type = /obj/item/ammo_casing/mm712x82
+ caliber = "mm71282"
max_ammo = 50
-/obj/item/ammo_box/magazine/mm195x129/hollow
- name = "box magazine (Hollow-Point 1.95x129mm)"
- ammo_type = /obj/item/ammo_casing/mm195x129/hollow
+/obj/item/ammo_box/magazine/mm712x82/hollow
+ name = "box magazine (Hollow-Point 7.12x82mm)"
+ ammo_type = /obj/item/ammo_casing/mm712x82/hollow
-/obj/item/ammo_box/magazine/mm195x129/ap
- name = "box magazine (Armor Penetrating 1.95x129mm)"
- ammo_type = /obj/item/ammo_casing/mm195x129/ap
+/obj/item/ammo_box/magazine/mm712x82/ap
+ name = "box magazine (Armor Penetrating 7.12x82mm)"
+ ammo_type = /obj/item/ammo_casing/mm712x82/ap
-/obj/item/ammo_box/magazine/mm195x129/incen
- name = "box magazine (Incendiary 1.95x129mm)"
- ammo_type = /obj/item/ammo_casing/mm195x129/incen
+/obj/item/ammo_box/magazine/mm712x82/incen
+ name = "box magazine (Incendiary 7.12x82mm)"
+ ammo_type = /obj/item/ammo_casing/mm712x82/incen
-/obj/item/ammo_box/magazine/mm195x129/update_icon()
+/obj/item/ammo_box/magazine/mm712x82/update_icon()
..()
icon_state = "a762-[round(ammo_count(),10)]"
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 3210ad2d15..a4d3c3a63a 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -94,6 +94,9 @@
var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds
+ /// directional recoil multiplier
+ var/dir_recoil_amp = 10
+
/obj/item/gun/Initialize(mapload)
. = ..()
if(no_pin_required)
@@ -159,7 +162,7 @@
/obj/item/gun/proc/shoot_live_shot(mob/living/user, pointblank = FALSE, mob/pbtarget, message = 1, stam_cost = 0)
if(recoil)
- shake_camera(user, recoil + 1, recoil)
+ directional_recoil(user, recoil*dir_recoil_amp, Get_Angle(user, pbtarget))
if(stam_cost) //CIT CHANGE - makes gun recoil cause staminaloss
var/safe_cost = clamp(stam_cost, 0, user.stamina_buffer)*(firing && burst_size >= 2 ? 1/burst_size : 1)
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index aa3ac20cb8..4b871d59b2 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -296,13 +296,13 @@
/obj/item/gun/ballistic/automatic/l6_saw
name = "\improper L6 SAW"
- desc = "A heavily modified 1.95x129mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation."
+ desc = "A heavily modified 7.12x82mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation."
icon_state = "l6closed100"
item_state = "l6closedmag"
fire_sound = "sound/weapons/lmgshot.ogg"
w_class = WEIGHT_CLASS_HUGE
slot_flags = 0
- mag_type = /obj/item/ammo_box/magazine/mm195x129
+ mag_type = /obj/item/ammo_box/magazine/mm712x82
weapon_weight = WEAPON_HEAVY
var/cover_open = FALSE
can_suppress = FALSE
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index fe42fc2d99..f3b07aa7a4 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -167,6 +167,7 @@
icon_state = "goldrevolver"
fire_sound = 'sound/weapons/resonator_blast.ogg'
recoil = 8
+ dir_recoil_amp = 5 // 40 directional recoil is already really funny
pin = /obj/item/firing_pin
/obj/item/gun/ballistic/revolver/nagant
diff --git a/code/modules/projectiles/projectile/bullets/lmg.dm b/code/modules/projectiles/projectile/bullets/lmg.dm
index e3eff6dcb0..177a98201c 100644
--- a/code/modules/projectiles/projectile/bullets/lmg.dm
+++ b/code/modules/projectiles/projectile/bullets/lmg.dm
@@ -21,22 +21,22 @@
/obj/item/projectile/bullet/syndicate_turret
damage = 20
-// 1.95x129mm (SAW)
+// 7.12x82mm (SAW)
-/obj/item/projectile/bullet/mm195x129
- name = "1.95x129mm bullet"
+/obj/item/projectile/bullet/mm712x82
+ name = "7.12x82mm bullet"
damage = 40
armour_penetration = 5
wound_bonus = -50
wound_falloff_tile = 0
-/obj/item/projectile/bullet/mm195x129_ap
- name = "1.95x129mm armor-piercing bullet"
+/obj/item/projectile/bullet/mm712x82_ap
+ name = "7.12x82mm armor-piercing bullet"
damage = 40
armour_penetration = 75
-/obj/item/projectile/bullet/mm195x129_hp
- name = "1.95x129mm hollow-point bullet"
+/obj/item/projectile/bullet/mm712x82_hp
+ name = "7.12x82mm hollow-point bullet"
damage = 50
armour_penetration = -60
sharpness = SHARP_EDGED
@@ -44,7 +44,15 @@
bare_wound_bonus = 30
wound_falloff_tile = -8
-/obj/item/projectile/bullet/incendiary/mm195x129
- name = "1.95x129mm incendiary bullet"
+/obj/item/projectile/bullet/incendiary/mm712x82
+ name = "7.12x82mm incendiary bullet"
damage = 20
fire_stacks = 3
+
+/obj/item/projectile/bullet/mm712x82/match
+ name = "7.12x82mm match bullet"
+ damage = 40
+ ricochets_max = 2
+ ricochet_chance = 60
+ ricochet_auto_aim_range = 4
+ ricochet_incidence_leeway = 35
diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm
index c4b7c8ffee..21d616bee4 100644
--- a/code/modules/shuttle/supply.dm
+++ b/code/modules/shuttle/supply.dm
@@ -131,7 +131,7 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list(
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
if(!empty_turfs.len)
break
- var/price = SO.pack.cost
+ var/price = SO.pack.get_cost()
if(SO.applied_coupon)
price *= (1 - SO.applied_coupon.discount_pct_off)
@@ -163,8 +163,8 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list(
LAZYADD(goodies_by_buyer[SO.paying_account], SO)
D.bank_card_talk("Cargo order #[SO.id] has shipped. [price] credits have been charged to your bank account.")
var/datum/bank_account/department/cargo = SSeconomy.get_dep_account(ACCOUNT_CAR)
- cargo.adjust_money(price - SO.pack.cost) //Cargo gets the handling fee
- value += SO.pack.cost
+ cargo.adjust_money(price - SO.pack.get_cost()) //Cargo gets the handling fee
+ value += SO.pack.get_cost()
SSshuttle.shoppinglist -= SO
SSshuttle.orderhistory += SO
QDEL_NULL(SO.applied_coupon)
@@ -172,7 +172,7 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list(
if(!SO.pack.goody && !ispath(SO.pack.crate_type, /obj/structure/closet/secure_closet/cargo)) //we handle goody crates and material closets below
SO.generate(pick_n_take(empty_turfs))
- SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[SO.pack.cost]", "[SO.pack.name]"))
+ SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[SO.pack.get_cost()]", "[SO.pack.name]"))
investigate_log("Order #[SO.id] ([SO.pack.name], placed by [key_name(SO.orderer_ckey)]), paid by [D.account_holder] has shipped.", INVESTIGATE_CARGO)
if(SO.pack.dangerous)
message_admins("\A [SO.pack.name] ordered by [ADMIN_LOOKUPFLW(SO.orderer_ckey)], paid by [D.account_holder] has shipped.")
diff --git a/code/modules/station_goals/station_goal.dm b/code/modules/station_goals/station_goal.dm
index b68fef3e2f..856bf08ff3 100644
--- a/code/modules/station_goals/station_goal.dm
+++ b/code/modules/station_goals/station_goal.dm
@@ -12,7 +12,7 @@
var/report_message = "Complete this goal."
/datum/station_goal/proc/send_report()
- priority_announce("Priority Nanotrasen directive received. Project \"[name]\" details inbound.", "Incoming Priority Message", "commandreport")
+ priority_announce("Priority Nanotrasen directive received. Project \"[name]\" details inbound.", "Incoming Priority Message", SSstation.announcer.get_rand_report_sound())
print_command_report(get_report(),"Nanotrasen Directive [pick(GLOB.phonetic_alphabet)] \Roman[rand(1,50)]", announce=FALSE)
on_report()
diff --git a/code/modules/tcg/pack_nuclear.dm b/code/modules/tcg/pack_nuclear.dm
index 96ec174a4b..cb82c98571 100644
--- a/code/modules/tcg/pack_nuclear.dm
+++ b/code/modules/tcg/pack_nuclear.dm
@@ -59,7 +59,7 @@
/datum/tcg_card/pack_nuclear/l6saw
name = "L6 Saw LMG"
- desc = "A heavily modified 1.95x129mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation."
+ desc = "A heavily modified 7.12x82mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation."
rules = "After equipped unit dies, this card goes to the bottom of draw deck"
icon_state = "l6saw"
diff --git a/code/modules/uplink/uplink_items/uplink_ammo.dm b/code/modules/uplink/uplink_items/uplink_ammo.dm
index a1c96d1082..1ce8b7914a 100644
--- a/code/modules/uplink/uplink_items/uplink_ammo.dm
+++ b/code/modules/uplink/uplink_items/uplink_ammo.dm
@@ -209,29 +209,29 @@
purchasable_from = UPLINK_NUKE_OPS
/datum/uplink_item/ammo/machinegun/basic
- name = "1.95x129mm Box Magazine"
- desc = "A 50-round magazine of 1.95x129mm ammunition for use with the L6 SAW. \
+ name = "7.12x82mm Box Magazine"
+ desc = "A 50-round magazine of 7.12x82mm ammunition for use with the L6 SAW. \
By the time you need to use this, you'll already be standing on a pile of corpses"
- item = /obj/item/ammo_box/magazine/mm195x129
+ item = /obj/item/ammo_box/magazine/mm712x82
/datum/uplink_item/ammo/machinegun/ap
- name = "1.95x129mm (Armor Penetrating) Box Magazine"
- desc = "A 50-round magazine of 1.95x129mm ammunition for use in the L6 SAW; equipped with special properties \
+ name = "7.12x82mm (Armor Penetrating) Box Magazine"
+ desc = "A 50-round magazine of 7.12x82mm ammunition for use in the L6 SAW; equipped with special properties \
to puncture even the most durable armor."
- item = /obj/item/ammo_box/magazine/mm195x129/ap
+ item = /obj/item/ammo_box/magazine/mm712x82/ap
cost = 9
/datum/uplink_item/ammo/machinegun/hollow
- name = "1.95x129mm (Hollow-Point) Box Magazine"
- desc = "A 50-round magazine of 1.95x129mm ammunition for use in the L6 SAW; equipped with hollow-point tips to help \
+ name = "7.12x82mm (Hollow-Point) Box Magazine"
+ desc = "A 50-round magazine of 7.12x82mm ammunition for use in the L6 SAW; equipped with hollow-point tips to help \
with the unarmored masses of crew."
- item = /obj/item/ammo_box/magazine/mm195x129/hollow
+ item = /obj/item/ammo_box/magazine/mm712x82/hollow
/datum/uplink_item/ammo/machinegun/incen
- name = "1.95x129mm (Incendiary) Box Magazine"
- desc = "A 50-round magazine of 1.95x129mm ammunition for use in the L6 SAW; tipped with a special flammable \
+ name = "7.12x82mm (Incendiary) Box Magazine"
+ desc = "A 50-round magazine of 7.12x82mm ammunition for use in the L6 SAW; tipped with a special flammable \
mixture that'll ignite anyone struck by the bullet. Some men just want to watch the world burn."
- item = /obj/item/ammo_box/magazine/mm195x129/incen
+ item = /obj/item/ammo_box/magazine/mm712x82/incen
/datum/uplink_item/ammo/rocket
purchasable_from = UPLINK_NUKE_OPS
diff --git a/code/modules/uplink/uplink_items/uplink_dangerous.dm b/code/modules/uplink/uplink_items/uplink_dangerous.dm
index dac426db2b..6bfe3f5bb8 100644
--- a/code/modules/uplink/uplink_items/uplink_dangerous.dm
+++ b/code/modules/uplink/uplink_items/uplink_dangerous.dm
@@ -213,7 +213,7 @@
/datum/uplink_item/dangerous/machinegun
name = "L6 Squad Automatic Weapon"
desc = "A fully-loaded Aussec Armoury belt-fed machine gun. \
- This deadly weapon has a massive 50-round magazine of devastating 1.95x129mm ammunition."
+ This deadly weapon has a massive 50-round magazine of devastating 7.12x82mm ammunition."
item = /obj/item/gun/ballistic/automatic/l6_saw
cost = 18
surplus = 0
diff --git a/html/changelogs/AutoChangeLog-pr-15736.yml b/html/changelogs/AutoChangeLog-pr-15736.yml
deleted file mode 100644
index 3cb4b78aba..0000000000
--- a/html/changelogs/AutoChangeLog-pr-15736.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "TripleShades"
-delete-after: True
-changes:
- - qol: "Three gateways will no longer lose power via Magical SMES Units"
diff --git a/html/changelogs/AutoChangeLog-pr-15742.yml b/html/changelogs/AutoChangeLog-pr-15742.yml
deleted file mode 100644
index 874b8d34c3..0000000000
--- a/html/changelogs/AutoChangeLog-pr-15742.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "timothyteakettle"
-delete-after: True
-changes:
- - rscadd: "lets AIs control status displays (vtuber mode)"
diff --git a/html/changelogs/AutoChangeLog-pr-15743.yml b/html/changelogs/AutoChangeLog-pr-15743.yml
deleted file mode 100644
index b92677bc00..0000000000
--- a/html/changelogs/AutoChangeLog-pr-15743.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "MrJWhit"
-delete-after: True
-changes:
- - rscadd: "Adds more AI screens on box"
diff --git a/html/changelogs/archive/2022-08.yml b/html/changelogs/archive/2022-08.yml
index 6c70e7d07c..95abd8d628 100644
--- a/html/changelogs/archive/2022-08.yml
+++ b/html/changelogs/archive/2022-08.yml
@@ -14,3 +14,44 @@
- code_imp: doesn't snowflake the bomb cap for linux anymore, servers that need
to tweak it can do it themselves (I didn't even put it down right, should be
5000 instead of 50000, yikes)
+2022-08-04:
+ MrJWhit:
+ - rscadd: Adds more AI screens on box
+ TripleShades:
+ - qol: Three gateways will no longer lose power via Magical SMES Units
+ timothyteakettle:
+ - rscadd: lets AIs control status displays (vtuber mode)
+2022-08-05:
+ Hatterhat:
+ - tweak: The L6 Squad Automatic Weapon, as used by Syndicate nuclear operatives,
+ now fires 7.12x82mm, with no change in ballistic performance. This has the side
+ effect of making it now capable of utilizing match-grade ammunition, which some
+ would think are mutually incompatible concepts.
+ Putnam3145:
+ - tweak: Untied shoelaces probabilities rework
+ - balance: Miner boots are slightly harder to tie/untie now
+2022-08-08:
+ Hatterhat:
+ - tweak: Proto-kinetic crushers and variants can now be renamed via pen.
+ - tweak: Custom descriptions on items tagged with UNIQUE_RENAME can now go to 350
+ characters, up from 140.
+ - tweak: Recoil now pushes your screen in the opposite direction of where you're
+ shooting. It makes sense in context, I swear. Adjust the setting for this in
+ your client prefs, because it defaults to on.
+ Linzolle:
+ - bugfix: catwalk overlays behave properly, appearing on top of cables and such
+ Putnam3145:
+ - tweak: Various gases now have shortened names for filters
+ - balance: Nitric oxide now removes from the power mix like nitrogen/pluoxium do
+ - balance: Nitric oxide now becomes, at most, 20% nitrogen dioxide instead of 50%
+ SandPoot:
+ - bugfix: Fixed the lack of eyes making ERROR appear on people.
+ - qol: Set up for the dmi editor extension, very helpful as you can see the existant
+ states and their names opposed to only images in a huge spritesheet.
+2022-08-09:
+ Putnam3145:
+ - rscadd: 'Adds station traits: Small modifiers that can randomly be chosen each
+ round. They are usually logged in the intercept the station receives early in
+ the round'
+ - refactor: Announcement audio is now stored in a datum to make it easy to add different
+ announcers
diff --git a/icons/mob/eyes.dmi b/icons/mob/eyes.dmi
index 4b8dba160a..c4927ea06b 100644
Binary files a/icons/mob/eyes.dmi and b/icons/mob/eyes.dmi differ
diff --git a/icons/turf/floors/catwalk_plating.dmi b/icons/turf/floors/catwalk_plating.dmi
index 24954e4a17..c9ad36653b 100644
Binary files a/icons/turf/floors/catwalk_plating.dmi and b/icons/turf/floors/catwalk_plating.dmi differ
diff --git a/sound/announcer/intern/alerts/1.ogg b/sound/announcer/intern/alerts/1.ogg
new file mode 100644
index 0000000000..c4d182bc8c
Binary files /dev/null and b/sound/announcer/intern/alerts/1.ogg differ
diff --git a/sound/announcer/intern/alerts/10.ogg b/sound/announcer/intern/alerts/10.ogg
new file mode 100644
index 0000000000..7380ccdeef
Binary files /dev/null and b/sound/announcer/intern/alerts/10.ogg differ
diff --git a/sound/announcer/intern/alerts/11.ogg b/sound/announcer/intern/alerts/11.ogg
new file mode 100644
index 0000000000..ca548dcc20
Binary files /dev/null and b/sound/announcer/intern/alerts/11.ogg differ
diff --git a/sound/announcer/intern/alerts/12.ogg b/sound/announcer/intern/alerts/12.ogg
new file mode 100644
index 0000000000..8d71419798
Binary files /dev/null and b/sound/announcer/intern/alerts/12.ogg differ
diff --git a/sound/announcer/intern/alerts/13.ogg b/sound/announcer/intern/alerts/13.ogg
new file mode 100644
index 0000000000..128c7aa424
Binary files /dev/null and b/sound/announcer/intern/alerts/13.ogg differ
diff --git a/sound/announcer/intern/alerts/14.ogg b/sound/announcer/intern/alerts/14.ogg
new file mode 100644
index 0000000000..81d54101be
Binary files /dev/null and b/sound/announcer/intern/alerts/14.ogg differ
diff --git a/sound/announcer/intern/alerts/2.ogg b/sound/announcer/intern/alerts/2.ogg
new file mode 100644
index 0000000000..a2ef615d56
Binary files /dev/null and b/sound/announcer/intern/alerts/2.ogg differ
diff --git a/sound/announcer/intern/alerts/3.ogg b/sound/announcer/intern/alerts/3.ogg
new file mode 100644
index 0000000000..51613ff036
Binary files /dev/null and b/sound/announcer/intern/alerts/3.ogg differ
diff --git a/sound/announcer/intern/alerts/4.ogg b/sound/announcer/intern/alerts/4.ogg
new file mode 100644
index 0000000000..874536ca72
Binary files /dev/null and b/sound/announcer/intern/alerts/4.ogg differ
diff --git a/sound/announcer/intern/alerts/5.ogg b/sound/announcer/intern/alerts/5.ogg
new file mode 100644
index 0000000000..0af0d28ce1
Binary files /dev/null and b/sound/announcer/intern/alerts/5.ogg differ
diff --git a/sound/announcer/intern/alerts/6.ogg b/sound/announcer/intern/alerts/6.ogg
new file mode 100644
index 0000000000..a65006a8c0
Binary files /dev/null and b/sound/announcer/intern/alerts/6.ogg differ
diff --git a/sound/announcer/intern/alerts/7.ogg b/sound/announcer/intern/alerts/7.ogg
new file mode 100644
index 0000000000..4a1d3f013a
Binary files /dev/null and b/sound/announcer/intern/alerts/7.ogg differ
diff --git a/sound/announcer/intern/alerts/8.ogg b/sound/announcer/intern/alerts/8.ogg
new file mode 100644
index 0000000000..83ca80f493
Binary files /dev/null and b/sound/announcer/intern/alerts/8.ogg differ
diff --git a/sound/announcer/intern/alerts/9.ogg b/sound/announcer/intern/alerts/9.ogg
new file mode 100644
index 0000000000..3c0c45b25d
Binary files /dev/null and b/sound/announcer/intern/alerts/9.ogg differ
diff --git a/sound/announcer/intern/aliens.ogg b/sound/announcer/intern/aliens.ogg
new file mode 100644
index 0000000000..9dd3c07697
Binary files /dev/null and b/sound/announcer/intern/aliens.ogg differ
diff --git a/sound/announcer/intern/animes.ogg b/sound/announcer/intern/animes.ogg
new file mode 100644
index 0000000000..36102c3e60
Binary files /dev/null and b/sound/announcer/intern/animes.ogg differ
diff --git a/sound/announcer/intern/commandreport/1.ogg b/sound/announcer/intern/commandreport/1.ogg
new file mode 100644
index 0000000000..e3108b13d1
Binary files /dev/null and b/sound/announcer/intern/commandreport/1.ogg differ
diff --git a/sound/announcer/intern/commandreport/2.ogg b/sound/announcer/intern/commandreport/2.ogg
new file mode 100644
index 0000000000..cd67500426
Binary files /dev/null and b/sound/announcer/intern/commandreport/2.ogg differ
diff --git a/sound/announcer/intern/commandreport/3.ogg b/sound/announcer/intern/commandreport/3.ogg
new file mode 100644
index 0000000000..94241c5ba5
Binary files /dev/null and b/sound/announcer/intern/commandreport/3.ogg differ
diff --git a/sound/announcer/intern/granomalies.ogg b/sound/announcer/intern/granomalies.ogg
new file mode 100644
index 0000000000..88944b63b2
Binary files /dev/null and b/sound/announcer/intern/granomalies.ogg differ
diff --git a/sound/announcer/intern/intercept.ogg b/sound/announcer/intern/intercept.ogg
new file mode 100644
index 0000000000..a87274abd9
Binary files /dev/null and b/sound/announcer/intern/intercept.ogg differ
diff --git a/sound/announcer/intern/ionstorm.ogg b/sound/announcer/intern/ionstorm.ogg
new file mode 100644
index 0000000000..9e7b5c6b23
Binary files /dev/null and b/sound/announcer/intern/ionstorm.ogg differ
diff --git a/sound/announcer/intern/meteors.ogg b/sound/announcer/intern/meteors.ogg
new file mode 100644
index 0000000000..c68c4bd8cc
Binary files /dev/null and b/sound/announcer/intern/meteors.ogg differ
diff --git a/sound/announcer/intern/outbreak5.ogg b/sound/announcer/intern/outbreak5.ogg
new file mode 100644
index 0000000000..cf98b95fd7
Binary files /dev/null and b/sound/announcer/intern/outbreak5.ogg differ
diff --git a/sound/announcer/intern/outbreak7.ogg b/sound/announcer/intern/outbreak7.ogg
new file mode 100644
index 0000000000..297a1bbe8d
Binary files /dev/null and b/sound/announcer/intern/outbreak7.ogg differ
diff --git a/sound/announcer/intern/poweroff.ogg b/sound/announcer/intern/poweroff.ogg
new file mode 100644
index 0000000000..4b71053653
Binary files /dev/null and b/sound/announcer/intern/poweroff.ogg differ
diff --git a/sound/announcer/intern/poweron.ogg b/sound/announcer/intern/poweron.ogg
new file mode 100644
index 0000000000..509cd398e6
Binary files /dev/null and b/sound/announcer/intern/poweron.ogg differ
diff --git a/sound/announcer/intern/radiation.ogg b/sound/announcer/intern/radiation.ogg
new file mode 100644
index 0000000000..08db53ebfd
Binary files /dev/null and b/sound/announcer/intern/radiation.ogg differ
diff --git a/sound/announcer/intern/shuttlecalled.ogg b/sound/announcer/intern/shuttlecalled.ogg
new file mode 100644
index 0000000000..c903367cdf
Binary files /dev/null and b/sound/announcer/intern/shuttlecalled.ogg differ
diff --git a/sound/announcer/intern/shuttledock.ogg b/sound/announcer/intern/shuttledock.ogg
new file mode 100644
index 0000000000..9f6ccd1a93
Binary files /dev/null and b/sound/announcer/intern/shuttledock.ogg differ
diff --git a/sound/announcer/intern/shuttlerecalled.ogg b/sound/announcer/intern/shuttlerecalled.ogg
new file mode 100644
index 0000000000..e259a79f35
Binary files /dev/null and b/sound/announcer/intern/shuttlerecalled.ogg differ
diff --git a/sound/announcer/intern/spanomalies.ogg b/sound/announcer/intern/spanomalies.ogg
new file mode 100644
index 0000000000..9bed8eae3a
Binary files /dev/null and b/sound/announcer/intern/spanomalies.ogg differ
diff --git a/sound/announcer/intern/welcome/1.ogg b/sound/announcer/intern/welcome/1.ogg
new file mode 100644
index 0000000000..758f1967e0
Binary files /dev/null and b/sound/announcer/intern/welcome/1.ogg differ
diff --git a/sound/announcer/intern/welcome/2.ogg b/sound/announcer/intern/welcome/2.ogg
new file mode 100644
index 0000000000..c2e72be510
Binary files /dev/null and b/sound/announcer/intern/welcome/2.ogg differ
diff --git a/sound/announcer/intern/welcome/3.ogg b/sound/announcer/intern/welcome/3.ogg
new file mode 100644
index 0000000000..004f57371d
Binary files /dev/null and b/sound/announcer/intern/welcome/3.ogg differ
diff --git a/sound/announcer/intern/welcome/4.ogg b/sound/announcer/intern/welcome/4.ogg
new file mode 100644
index 0000000000..c4e1f7667c
Binary files /dev/null and b/sound/announcer/intern/welcome/4.ogg differ
diff --git a/sound/announcer/intern/welcome/5.ogg b/sound/announcer/intern/welcome/5.ogg
new file mode 100644
index 0000000000..641b8208a4
Binary files /dev/null and b/sound/announcer/intern/welcome/5.ogg differ
diff --git a/sound/announcer/intern/welcome/6.ogg b/sound/announcer/intern/welcome/6.ogg
new file mode 100644
index 0000000000..b0fc38237f
Binary files /dev/null and b/sound/announcer/intern/welcome/6.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index ca7d9ebda1..bf2750ea64 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -116,6 +116,7 @@
#include "code\__DEFINES\species.dm"
#include "code\__DEFINES\stat.dm"
#include "code\__DEFINES\stat_tracking.dm"
+#include "code\__DEFINES\station.dm"
#include "code\__DEFINES\status_effects.dm"
#include "code\__DEFINES\strippable.dm"
#include "code\__DEFINES\subsystems.dm"
@@ -437,6 +438,7 @@
#include "code\controllers\subsystem\processing\processing.dm"
#include "code\controllers\subsystem\processing\projectiles.dm"
#include "code\controllers\subsystem\processing\quirks.dm"
+#include "code\controllers\subsystem\processing\station.dm"
#include "code\controllers\subsystem\processing\status_effects.dm"
#include "code\controllers\subsystem\processing\weather.dm"
#include "code\controllers\subsystem\processing\wet_floors.dm"
@@ -495,6 +497,10 @@
#include "code\datums\achievements\misc_scores.dm"
#include "code\datums\achievements\skill_achievements.dm"
#include "code\datums\actions\beam_rifle.dm"
+#include "code\datums\announcers\_announcer.dm"
+#include "code\datums\announcers\default_announcer.dm"
+#include "code\datums\announcers\intern_announcer.dm"
+#include "code\datums\announcers\medbot_announcer.dm"
#include "code\datums\atmosphere\_atmosphere.dm"
#include "code\datums\atmosphere\planetary.dm"
#include "code\datums\brain_damage\brain_trauma.dm"
@@ -547,6 +553,7 @@
#include "code\datums\components\mirage_border.dm"
#include "code\datums\components\mirv.dm"
#include "code\datums\components\mood.dm"
+#include "code\datums\components\multiple_lives.dm"
#include "code\datums\components\nanites.dm"
#include "code\datums\components\ntnet_interface.dm"
#include "code\datums\components\omen.dm"
@@ -765,6 +772,11 @@
#include "code\datums\skills\modifiers\job.dm"
#include "code\datums\skills\modifiers\mood.dm"
#include "code\datums\skills\modifiers\organs.dm"
+#include "code\datums\station_traits\_station_trait.dm"
+#include "code\datums\station_traits\admin_panel.dm"
+#include "code\datums\station_traits\negative_traits.dm"
+#include "code\datums\station_traits\neutral_traits.dm"
+#include "code\datums\station_traits\positive_traits.dm"
#include "code\datums\status_effects\buffs.dm"
#include "code\datums\status_effects\debuffs.dm"
#include "code\datums\status_effects\gas.dm"
diff --git a/tgui/packages/common/collections.ts b/tgui/packages/common/collections.ts
index 7abe49ff23..a5ccd7003e 100644
--- a/tgui/packages/common/collections.ts
+++ b/tgui/packages/common/collections.ts
@@ -123,6 +123,26 @@ export const map = iterateeFn => collection => {
throw new Error(`map() can't iterate on type ${typeof collection}`);
};
+/**
+ * Given a collection, will run each element through an iteratee function.
+ * Will then filter out undefined values.
+ */
+export const filterMap = (
+ collection: T[],
+ iterateeFn: (value: T) => U | undefined
+): U[] => {
+ const finalCollection: U[] = [];
+
+ for (const value of collection) {
+ const output = iterateeFn(value);
+ if (output !== undefined) {
+ finalCollection.push(output);
+ }
+ }
+
+ return finalCollection;
+};
+
const COMPARATOR = (objA, objB) => {
const criteriaA = objA.criteria;
const criteriaB = objB.criteria;
diff --git a/tgui/packages/common/exhaustive.ts b/tgui/packages/common/exhaustive.ts
new file mode 100644
index 0000000000..bc41757515
--- /dev/null
+++ b/tgui/packages/common/exhaustive.ts
@@ -0,0 +1,19 @@
+/**
+ * Throws an error such that a non-exhaustive check will error at compile time
+ * when using TypeScript, rather than at runtime.
+ *
+ * For example:
+ * enum Color { Red, Green, Blue }
+ * switch (color) {
+ * case Color.Red:
+ * return "red";
+ * case Color.Green:
+ * return "green";
+ * default:
+ * // This will error at compile time that we forgot blue.
+ * exhaustiveCheck(color);
+ * }
+ */
+export const exhaustiveCheck = (input: never) => {
+ throw new Error(`Unhandled case: ${input}`);
+};
diff --git a/tgui/packages/tgui/constants.js b/tgui/packages/tgui/constants.js
index bc20e9ac9c..a5ac6aceb0 100644
--- a/tgui/packages/tgui/constants.js
+++ b/tgui/packages/tgui/constants.js
@@ -181,6 +181,12 @@ const GASES = [
'label': 'N₂O',
'color': 'red',
},
+ {
+ 'id': 'no',
+ 'name': 'Nitric Oxide',
+ 'label': 'NO',
+ 'color': 'red',
+ },
{
'id': 'no2',
'name': 'Nitryl',
@@ -223,6 +229,24 @@ const GASES = [
'label': 'H₂',
'color': 'white',
},
+ {
+ 'id': 'methane',
+ 'name': 'Methane',
+ 'label': 'CH₄',
+ 'color': 'grey',
+ },
+ {
+ 'id': 'methyl_bromide',
+ 'name': 'Methyl Bromide',
+ 'label': 'CH₃Br',
+ 'color': 'brown',
+ },
+ {
+ 'id': 'qcd',
+ 'name': 'Quark Matter',
+ 'label': 'QGP',
+ 'color': 'pink',
+ },
];
export const getGasLabel = (gasId, fallbackValue) => {
diff --git a/tgui/packages/tgui/interfaces/StationTraitsPanel.tsx b/tgui/packages/tgui/interfaces/StationTraitsPanel.tsx
new file mode 100644
index 0000000000..d09d109d62
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/StationTraitsPanel.tsx
@@ -0,0 +1,251 @@
+import { filterMap } from 'common/collections';
+import { exhaustiveCheck } from 'common/exhaustive';
+import { BooleanLike } from 'common/react';
+import { useBackend, useLocalState } from '../backend';
+import { Box, Button, Divider, Dropdown, Stack, Tabs } from '../components';
+import { Window } from '../layouts';
+
+type CurrentStationTrait = {
+ can_revert: BooleanLike;
+ name: string;
+ ref: string;
+};
+
+type ValidStationTrait = {
+ name: string;
+ path: string;
+};
+
+type StationTraitsData = {
+ current_traits: CurrentStationTrait[];
+ future_station_traits?: ValidStationTrait[];
+ too_late_to_revert: BooleanLike;
+ valid_station_traits: ValidStationTrait[];
+};
+
+enum Tab {
+ SetupFutureStationTraits,
+ ViewStationTraits,
+}
+
+const FutureStationTraitsPage = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { future_station_traits } = data;
+
+ const [selectedTrait, setSelectedTrait] = useLocalState(
+ context,
+ 'selectedFutureTrait',
+ null
+ );
+
+ const traitsByName = Object.fromEntries(
+ data.valid_station_traits.map((trait) => {
+ return [trait.name, trait.path];
+ })
+ );
+
+ const traitNames = Object.keys(traitsByName);
+ traitNames.sort();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array.isArray(future_station_traits) ? (
+ future_station_traits.length > 0 ? (
+
+ {future_station_traits.map((trait) => (
+
+
+ {trait.name}
+
+
+
+
+
+
+ ))}
+
+ ) : (
+ <>
+ No station traits will run next round.
+
+
+
+
+ >
+ )
+ ) : (
+ <>
+ No future station traits are planned.
+
+
+
+
+ >
+ )}
+
+ );
+};
+
+const ViewStationTraitsPage = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return data.current_traits.length > 0 ? (
+
+ {data.current_traits.map((stationTrait) => (
+
+
+ {stationTrait.name}
+
+
+
+ act('revert', {
+ ref: stationTrait.ref,
+ })}
+ />
+
+
+
+ ))}
+
+ ) : (
+ There are no active station traits.
+ );
+};
+
+export const StationTraitsPanel = (props, context) => {
+ const [currentTab, setCurrentTab] = useLocalState(
+ context,
+ 'station_traits_tab',
+ Tab.ViewStationTraits
+ );
+
+ let currentPage;
+
+ switch (currentTab) {
+ case Tab.SetupFutureStationTraits:
+ currentPage = ;
+ break;
+ case Tab.ViewStationTraits:
+ currentPage = ;
+ break;
+ default:
+ exhaustiveCheck(currentTab);
+ }
+
+ return (
+
+
+
+ setCurrentTab(Tab.ViewStationTraits)}>
+ View
+
+
+ setCurrentTab(Tab.SetupFutureStationTraits)}>
+ Edit
+
+
+
+
+
+ {currentPage}
+
+
+ );
+};
diff --git a/tools/midi2piano/midi2piano.py b/tools/midi2piano/midi2piano.py
index f4494801d3..ed01297b93 100644
--- a/tools/midi2piano/midi2piano.py
+++ b/tools/midi2piano/midi2piano.py
@@ -8,7 +8,7 @@ import easygui as egui
import pyperclip as pclip
LINE_LENGTH_LIM = 50
-LINES_LIMIT = 200
+LINES_LIMIT = 1000
TICK_LAG = 0.5
OVERALL_IMPORT_LIM = 2*LINE_LENGTH_LIM*LINES_LIMIT
END_OF_LINE_CHAR = """