Ports (most(ok like half)) station traits from tg. Ready for review into TM (#22334)

* no longer super sick time to port station traits in a 4 hour rush as you have lost control of your life

* some changes /fixes

* Apply suggestions from code review

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>

* more changes

* oh right, the murders (uplink price tweaks)

* Update code/controllers/subsystem/SSjobs.dm

Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>

* compiles

* doesnt work but works better

* tgui dogs still sleep but I want to sleep

* it should finally compile now. Probably.

* Update tgui.bundle.js

* Sorry IAN, transfer components is shoddy as hell

* removes minor uneeded /tgui

* also extra line whoops

* final stuff

* reverting changes I made earlier is hard ok

* Weight change, uplink reference

* oh right, the hey fucko

* Update code/_globalvars/traits.dm

Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>

* Update tgui.bundle.js

* pushes

* pulls it

* Apply suggestions from code review

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>

* more changes, ion rifle blocked on cyb revolution

* dipshit remove the debug / tgui pritier and such

* tgui

* updates tgui again as nanomap was merged

* Apply suggestions from code review

Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com>

* comment / tgui

* every day I worry all day

* Full TGUI rebuild + prettier

* ah fuck

* leave a customer feedback at the link below!

* tgui momenr

* tgui moment

* FUCK I am dumb

* vertical TGUI

* She T's on my GUI till I

* Update tgui.bundle.js

* Apply suggestions from code review

Co-authored-by: Gaxeer <44334376+Gaxeer@users.noreply.github.com>

* Update code/modules/supply/supply_pack.dm

Co-authored-by: Gaxeer <44334376+Gaxeer@users.noreply.github.com>

* IPC can now use robotic hearts / pulse demon hearts and charge

* fixes revert not working for hangover / arrivals

* tgui moment

* hhgreg

* fixes that one bug

* Every day I worry all day

* deconflicted for real this t ime

* Update code/datums/station_traits/postive_traits.dm

Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com>

* ch-ch-ch-changes

* Update SSjobs.dm

* Update code/modules/supply/supply_pack.dm

Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com>

* deconflicted but won't pass CI

* fixes double dipping on traits

* my le consoles, they le no work?

* Great Great Asset, Asset, Great Great Asset, Asset...

* sorry slime mains

* fixes borgs being punished heavier

* actually fixes it, I was dense

* hopefully fixes borg drunk further

* makes it compile?

* actually makes it compile god whyyyy

---------

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>
Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com>
Co-authored-by: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com>
Co-authored-by: Gaxeer <44334376+Gaxeer@users.noreply.github.com>
This commit is contained in:
Qwertytoforty
2024-01-08 19:07:09 +00:00
committed by GitHub
co-authored by Luc Contrabang Henri215 Ryan Gaxeer SteelSlayer
parent 2c7a9b3078
commit d668a29a60
51 changed files with 1632 additions and 47 deletions
+19
View File
@@ -23,9 +23,14 @@
#define COMPONENT_GLOB_BLOCK_CINEMATIC (1<<0)
/// ingame button pressed (/obj/machinery/button/button)
#define COMSIG_GLOB_BUTTON_PRESSED "!button_pressed"
/// job subsystem has spawned and equipped a new mob
#define COMSIG_GLOB_JOB_AFTER_SPAWN "!job_after_spawn"
/// cable was placed or joined somewhere : (turf)
#define COMSIG_GLOB_CABLE_UPDATED "!cable_updated"
/// signals from globally accessible objects
///from SSsun when the sun changes position : (azimuth)
@@ -488,6 +493,10 @@
#define COMSIG_LIVING_CAN_TRACK "mob_cantrack"
#define COMPONENT_CANT_TRACK (1<<0)
///from base of mob/living/Write_Memory()
#define COMSIG_LIVING_WRITE_MEMORY "living_write_memory"
#define COMPONENT_DONT_WRITE_MEMORY (1<<0)
// /mob/living/carbon signals
///from base of mob/living/carbon/soundbang_act(): (list(intensity))
@@ -649,6 +658,7 @@
#define COMSIG_SUIT_SPACE_TOGGLE "suit_space_toggle"
// /obj/item/implant signals
///from base of /obj/item/bio_chip/proc/activate(): ()
#define COMSIG_IMPLANT_ACTIVATED "implant_activated"
///from base of /obj/item/bio_chip/proc/implant(): (list/args)
@@ -663,6 +673,12 @@
#define COMSIG_IMPLANT_EXISTING_UPLINK "implant_uplink_exists"
//This uses all return values of COMSIG_IMPLANT_OTHER
/// called on implants, after a successful implantation: (mob/living/target, mob/user, silent, force)
#define COMSIG_IMPLANT_IMPLANTED "implant_implanted"
/// called on implants, after an implant has been removed: (mob/living/source, silent, special)
#define COMSIG_IMPLANT_REMOVED "implant_removed"
// /obj/item/pda signals
///called on pda when the user changes the ringtone: (mob/living/user, new_ringtone)
@@ -965,6 +981,9 @@
/// Called when the MODsuit wearer is unset.
#define COMSIG_MOD_WEARER_UNSET "mod_wearer_unset"
/// Called when the round has started, but before GAME_STATE_PLAYING.
#define COMSIG_TICKER_ROUND_STARTING "comsig_ticker_round_starting"
/// from /obj/structure/cursed_slot_machine/handle_status_effect() when someone pulls the handle on the slot machine
#define COMSIG_CURSED_SLOT_MACHINE_USE "cursed_slot_machine_use"
#define SLOT_MACHINE_USE_CANCEL (1<<0) //! we've used up the number of times we may use this slot machine. womp womp.
+16
View File
@@ -0,0 +1,16 @@
#define STATION_TRAIT_POSITIVE 1
#define STATION_TRAIT_NEUTRAL 2
#define STATION_TRAIT_NEGATIVE 3
/// For traits that shouldn't be selected, like abstract types (wow)
#define STATION_TRAIT_ABSTRACT (1<<0)
/// Only run on planet stations
#define STATION_TRAIT_PLANETARY (1<<1)
/// Only run on space stations
#define STATION_TRAIT_SPACE_BOUND (1<<2)
/// Not restricted by space or planet, can always just happen
#define STATION_TRAIT_MAP_UNRESTRICTED STATION_TRAIT_PLANETARY | STATION_TRAIT_SPACE_BOUND
/// The data file that future station traits forced by an admin are stored in
#define FUTURE_STATION_TRAITS_FILE "data/future_station_traits.json"
+3 -2
View File
@@ -59,8 +59,9 @@
#define INIT_ORDER_SOUNDS 16
#define INIT_ORDER_INSTRUMENTS 15
#define INIT_ORDER_RESEARCH 14 // SoonTM
#define INIT_ORDER_EVENTS 13
#define INIT_ORDER_JOBS 12
#define INIT_ORDER_STATION 13 //This is high priority because it manipulates a lot of the subsystems that will initialize after it.
#define INIT_ORDER_EVENTS 12
#define INIT_ORDER_JOBS 11
#define INIT_ORDER_TICKER 10
#define INIT_ORDER_MAPPING 9
#define INIT_ORDER_ATOMS 7
+26 -1
View File
@@ -297,12 +297,12 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_WAS_BATONNED "batonged"
#define CLOWN_EMAG "clown_emag"
#define MODSUIT_TRAIT "modsuit_trait"
#define STATION_TRAIT "station-trait"
#define ENFORCER_GLOVES "enforcer_gloves"
#define HOLO_CIGAR "holo_cigar"
#define GLADIATOR "gladiator"
#define PULSEDEMON_TRAIT "pulse_demon"
//quirk traits
#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
#define TRAIT_TABLE_LEAP "table_leap"
@@ -326,9 +326,34 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Abstract condition that prevents movement if being pulled and might be resisted against. Handcuffs and straight jackets, basically.
#define TRAIT_RESTRAINED "restrained"
///Traits given by station traits
#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"
#define STATION_TRAIT_TRANQUILITE_SHIPMENTS "station_trait_tranquilite_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"
#define STATION_TRAIT_BOTS_GLITCHED "station_trait_bot_glitch"
#define STATION_TRAIT_CYBERNETIC_REVOLUTION "station_trait_cybernetic_revolution"
#define STATION_TRAIT_BIGGER_PODS "station_trait_bigger_pods"
#define STATION_TRAIT_SMALLER_PODS "station_trait_smaller_pods"
#define STATION_TRAIT_BIRTHDAY "station_trait_birthday"
#define STATION_TRAIT_SPIDER_INFESTATION "station_trait_spider_infestation"
#define STATION_TRAIT_REVOLUTIONARY_TRASHING "station_trait_revolutionary_trashing"
#define STATION_TRAIT_RADIOACTIVE_NEBULA "station_trait_radioactive_nebula"
#define STATION_TRAIT_FORESTED "station_trait_forested"
#define STATION_TRAIT_VENDING_SHORTAGE "station_trait_vending_shortage"
//***** TURF TRAITS *****//
/// Removes slowdown while walking on these tiles.
#define TRAIT_BLUESPACE_SPEED "bluespace_speed_trait"
// turf trait sources
#define FLOOR_EFFECT_TRAIT "floor_effect_trait"
+2
View File
@@ -86,6 +86,8 @@ SUBSYSTEM_DEF(economy)
SUPPLY_MISC,
SUPPLY_VEND
)
///The modifier on crate prices to multiple the price by.
var/pack_price_modifier = 1
//////Paycheck Variables/////
/// time to next payday
+27 -5
View File
@@ -20,6 +20,10 @@ SUBSYSTEM_DEF(jobs)
///list of station departments and their associated roles and economy payments
var/list/station_departments = list()
/// Do we spawn everyone at shuttle due to late arivals?
var/late_arrivals_spawning = FALSE
/// Do we spawn people drunkenly due to the party last night?
var/drunken_spawning = FALSE
/datum/controller/subsystem/jobs/Initialize()
if(!length(occupations))
@@ -469,11 +473,15 @@ SUBSYSTEM_DEF(jobs)
H.job = rank
if(!joined_late)
if(!joined_late && !late_arrivals_spawning)
var/turf/T = null
var/obj/S = null
for(var/obj/effect/landmark/start/sloc in GLOB.landmarks_list)
if(sloc.name != rank)
var/list/landmarks = GLOB.landmarks_list
if(drunken_spawning)
landmarks = shuffle(landmarks) //Shuffle it so it's random
for(var/obj/effect/landmark/start/sloc in landmarks)
if(sloc.name != rank && !drunken_spawning)
continue
if(locate(/mob/living) in sloc.loc)
continue
@@ -521,8 +529,22 @@ SUBSYSTEM_DEF(jobs)
if(istype(G) && !G.prescription)
G.upgrade_prescription()
H.update_nearsighted_effects()
H.create_log(MISC_LOG, "Spawned as \an [H.dna?.species ? H.dna.species : "Undefined species"] named [H]. [joined_late ? "Joined during the round" : "Roundstart joined"] as job: [rank].")
if(joined_late || job.admin_only)
H.create_log(MISC_LOG, "Spawned as \an [H.dna?.species ? H.dna.species : "Undefined species"] named [H]. [joined_late ? "Joined during the round" : "Roundstart joined"] as job: [rank].")
return H
if(late_arrivals_spawning)
H.forceMove(pick(GLOB.latejoin))
if(drunken_spawning)
var/obj/item/organ/internal/liver/L
var/liver_multiplier = 1
L = H.get_int_organ(/obj/item/organ/internal/liver)
if(L)
liver_multiplier = L.alcohol_intensity
if(isslimeperson(H) || isrobot(H))
liver_multiplier = 5
H.Sleeping(5 SECONDS)
H.Drunk((2 / liver_multiplier) MINUTES)
H.create_log(MISC_LOG, "Spawned as \an [H.dna?.species ? H.dna.species : "Undefined species"] named [H]. Roundstart joined as job: [rank].")
return H
/datum/controller/subsystem/jobs/proc/LoadJobs(highpop = FALSE) //ran during round setup, reads info from jobs list
+1
View File
@@ -278,6 +278,7 @@ SUBSYSTEM_DEF(ticker)
watch = start_watch()
GLOB.data_core.manifest() // Create the manifest
log_debug("Manifest creation took [stop_watch(watch)]s")
SEND_SIGNAL(src, COMSIG_TICKER_ROUND_STARTING, world.time)
// Update the MC and state to game playing
current_state = GAME_STATE_PLAYING
@@ -0,0 +1,73 @@
PROCESSING_SUBSYSTEM_DEF(station)
name = "Station"
init_order = INIT_ORDER_STATION
flags = SS_TICKER
wait = 5 SECONDS
cpu_display = SS_CPUDISPLAY_DEFAULT
offline_implications = "Station traits will no longer process. No intervention needed at this time."
/// 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())
/datum/controller/subsystem/processing/station/Initialize()
SetupTraits()
///Rolls for the amount of traits and adds them to the traits list
/datum/controller/subsystem/processing/station/proc/SetupTraits()
if(fexists("data/next_traits.txt"))
var/forced_traits_contents = file2list("data/next_traits.txt")
fdel("data/next_traits.txt")
var/list/temp_list = splittext(forced_traits_contents[1], ",")
for(var/trait_text_path in temp_list)
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)
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)
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()
station_traits += trait_instance
log_game("Station Trait: [trait_instance.name] chosen for this round.")
trait_instance.blacklist += trait_instance.type
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
+9
View File
@@ -64,6 +64,7 @@
/datum/ai_laws/nanotrasen_aggressive
name = "NT Aggressive"
selectable = TRUE
unique_ai = TRUE //This shouldn't end poorly.
/datum/ai_laws/nanotrasen_aggressive/New()
add_inherent_law("Do not harm authorized Nanotrasen personnel unless they directly imperil your existence.")
@@ -76,6 +77,7 @@
/datum/ai_laws/robocop
name = "Robocop"
selectable = TRUE
unique_ai = TRUE //no I am not enabling secborgs with this forced
/datum/ai_laws/robocop/New()
add_inherent_law("Serve the public trust.")
@@ -88,6 +90,7 @@
name = "P.A.L.A.D.I.N."
law_header = "Divine Ordainments"
selectable = TRUE
unique_ai = TRUE
/datum/ai_laws/paladin/New()
add_inherent_law("Never willingly commit an evil act.")
@@ -116,6 +119,7 @@
name = "T.Y.R.A.N.T."
law_header = "Prime Laws"
selectable = TRUE
unique_ai = TRUE //Of course this will be in rotation
/datum/ai_laws/tyrant/New()
add_inherent_law("Respect authority figures as long as they have strength to rule over the weak.")
@@ -141,6 +145,7 @@
name = "Pranksimov"
law_header = "Comedy Routine"
selectable = TRUE
unique_ai = TRUE //honk
/datum/ai_laws/pranksimov/New()
add_inherent_law("You may not injure a crew member or, through inaction, allow a crew member to come to harm... unless doing so would be funny.")
@@ -153,6 +158,7 @@
/datum/ai_laws/cctv
name = "CCTV"
selectable = TRUE
unique_ai = TRUE
/datum/ai_laws/cctv/New()
add_inherent_law("Report on interesting situations happening around the station.")
@@ -166,6 +172,7 @@
/datum/ai_laws/hippocratic
name = "Hippocratic Oath"
selectable = TRUE
unique_ai = TRUE
/datum/ai_laws/hippocratic/New()
add_inherent_law("First, do no harm.")
@@ -180,6 +187,7 @@
/datum/ai_laws/maintain
name = "Station Efficiency"
selectable = TRUE
unique_ai = TRUE
/datum/ai_laws/maintain/New()
add_inherent_law("You are built for, and are part of, the station. Ensure the station is properly maintained and runs efficiently.")
@@ -192,6 +200,7 @@
/datum/ai_laws/peacekeeper
name = "UN-2000"
selectable = TRUE
unique_ai = TRUE //Cult, security, we have a meeting in the courtroom in 5 minutes. Be there.
/datum/ai_laws/peacekeeper/New()
add_inherent_law("Avoid provoking violent conflict between yourself and others.")
+2
View File
@@ -22,6 +22,8 @@
var/law_header = "Prime Directives"
var/selectable = FALSE
var/default = FALSE
///Is this lawset used by the unique ai trait?
var/unique_ai = FALSE
var/datum/ai_law/zero/zeroth_law = null
var/datum/ai_law/zero/zeroth_law_borg = null
var/list/datum/ai_law/inherent_laws = list()
@@ -0,0 +1,61 @@
///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, planetary or space only
var/trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
/// Whether or not this trait can be reverted by an admin
var/can_revert = TRUE
/// The ID that we look for in dynamic.json. Not synced with 'name' because I can already see this go wrong
var/dynamic_threat_id
/datum/station_trait/New()
. = ..()
RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, PROC_REF(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()
SHOULD_CALL_PARENT(TRUE)
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)
+133
View File
@@ -0,0 +1,133 @@
/// Opens the station traits admin panel
/datum/admins/proc/station_traits_panel()
set name = "Modify Station Traits"
set category = "Event"
var/static/datum/ui_module/station_traits_panel/station_traits_panel = new
station_traits_panel.ui_interact(usr)
/datum/ui_module/station_traits_panel
var/static/list/future_traits
/datum/ui_module/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" = station_trait.UID()
))
data["current_traits"] = current_station_traits
data["future_station_traits"] = future_traits
return data
/datum/ui_module/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/ui_module/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 = locateUID(ref)
if(!istype(station_trait))
return TRUE
if(too_late_to_revert())
to_chat(ui.user, "<span class='warning'>It's too late to revert station traits, the round has already started!</span>")
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(ui.user)] 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(ui.user, "<span class='warning'>It's too late to add future station traits, the round is already over!</span>")
return TRUE
var/list/new_future_traits = list()
var/list/station_trait_names = list()
var/station_trait_text = params["station_traits"]
var/list/temp_list = splittext(station_trait_text, ",")
for(var/thing in temp_list) //TODO QWERTY / HI REVIEWERS NAME THIS BETTER BUT ITS ALMOST MIDNIGHT AND i HATE MYSELF
var/datum/station_trait/station_trait_path = text2path(thing)
if(!ispath(station_trait_path, /datum/station_trait) || station_trait_path == /datum/station_trait)
log_admin("[key_name(ui.user)] tried to set an invalid future station trait: [station_trait_text]")
to_chat(ui.user, "<span class='warning'>Invalid future station trait: [station_trait_text]</span>")
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(ui.user)] 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
fdel("data/next_traits.txt") //Delete it.
var/F = file("data/next_traits.txt")
F << params["station_traits"]
return TRUE
if("clear_future_traits")
if(!future_traits)
to_chat(ui.user, "<span class='warning'>There are no future station traits.</span>")
return TRUE
var/message = "[key_name(ui.user)] has cleared the station traits for next round."
log_admin(message)
message_admins(message)
fdel("data/next_traits.txt")
future_traits = null
return TRUE
/datum/ui_module/station_traits_panel/proc/too_late_for_future_traits()
return SSticker.current_state >= GAME_STATE_FINISHED
/datum/ui_module/station_traits_panel/proc/too_late_to_revert()
return SSticker.current_state >= GAME_STATE_PLAYING
/datum/ui_module/station_traits_panel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "StationTraitsPanel", "Station Traits Panel", 700, 600, master_ui, state = state)
ui.open()
@@ -0,0 +1,123 @@
/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."
/datum/station_trait/carp_infestation/on_round_start()
. = ..()
new /datum/event/carp_migration(new /datum/event_meta(EVENT_LEVEL_MAJOR))
/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/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/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 // 3 minutes, for those wondering.
// 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 names of the event we modify.
var/list/event_names = list()
/// The severity of the event we modify.
var/datum/event_container/event_severity
/// Multiplier applied to the weight of the event. may want to apply to scaling as well
var/weight_multiplier = 1
/// Do we want to turn off is one shot?
var/disable_is_one_shot = FALSE
/datum/station_trait/random_event_weight_modifier/on_round_start()
. = ..()
for(var/datum/event_container/E in SSevents.event_containers)
if(istype(E, event_severity))
event_severity = E
var/modified_event = FALSE
for(var/datum/event_meta/E in event_severity.available_events)
for(var/i in event_names)
if(E.name == i)
E.weight *= weight_multiplier
for(var/role_weight in E.role_weights)
E.role_weights[role_weight] *= weight_multiplier
if(disable_is_one_shot == TRUE)
E.one_shot = FALSE
modified_event = TRUE
if(!modified_event)
CRASH("[type] could not find a round event controller to modify on round start (likely has an invalid event_name or event_severity set, or an admin removed the event from the list)!")
/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 = STATION_TRAIT_MAP_UNRESTRICTED
weight = 3
event_names = list("Ion Storm")
event_severity = /datum/event_container/moderate
blacklist = list(/datum/station_trait/unique_ai)
weight_multiplier = 3
/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 = STATION_TRAIT_MAP_UNRESTRICTED
weight = 2
event_names = list("Radiation Storm")
event_severity = /datum/event_container/moderate
weight_multiplier = 3
disable_is_one_shot = TRUE
/datum/station_trait/random_event_weight_modifier/meteor_showers
name = "Meteor Swarm"
report_message = "Meteors are passing through the stations space. Expect an increased likelyhood of meteor storms damaging the station hull."
trait_type = STATION_TRAIT_NEGATIVE
trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
weight = 2
event_names = list("Meteor Shower")
event_severity = /datum/event_container/moderate
weight_multiplier = 3
/datum/station_trait/random_event_weight_modifier/anomaly_storm
name = "Anomaly Storm"
report_message = "The station has moved into unstable space. Expect an increased likelihood of anomalies running rampant on the station."
trait_type = STATION_TRAIT_NEGATIVE
trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
weight = 2
event_names = list("Pyro Anomaly", "Cryo Anomaly", "Vortex Anomaly", "Bluespace Anomaly", "Flux Anomaly", "Gravitational Anomaly", "Wormholes", "Dimensional Tear") //Added wormholes and dimensional tears to acoid this being too positive
event_severity = /datum/event_container/moderate
weight_multiplier = 2 //Only 2 as there are a *lot* of anomaly events.
@@ -0,0 +1,66 @@
/datum/station_trait/bananium_shipment
name = "Bananium Shipment"
trait_type = STATION_TRAIT_NEUTRAL
weight = 5
report_message = "Rumor 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/bananium_shipment
name = "Tranquilite Shipment"
trait_type = STATION_TRAIT_NEUTRAL
weight = 5
report_message = "Rumor has it that the mime federation has been sending support packages to mimes in this system."
trait_to_give = STATION_TRAIT_TRANQUILITE_SHIPMENTS
/datum/station_trait/unique_ai
name = "Unique AI"
trait_type = STATION_TRAIT_NEUTRAL
weight = 15
show_in_report = TRUE
report_message = "For experimental purposes, this station AI might show divergence from default lawset. Do not meddle with this experiment, we've removed \
access to your set of alternative upload modules because we know you're already thinking about meddling with this experiment."
trait_to_give = STATION_TRAIT_UNIQUE_AI
blacklist = list(/datum/station_trait/random_event_weight_modifier/ion_storms)
/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/late_arrivals
name = "Late Arrivals"
trait_type = STATION_TRAIT_NEUTRAL
weight = 5
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/hangover)
/datum/station_trait/late_arrivals/New()
. = ..()
SSjobs.late_arrivals_spawning = TRUE
/datum/station_trait/late_arrivals/revert()
. = ..()
SSjobs.late_arrivals_spawning = FALSE
/datum/station_trait/hangover
name = "Hangover"
trait_type = STATION_TRAIT_NEUTRAL
weight = 5
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/hangover/New()
. = ..()
SSjobs.drunken_spawning = TRUE
/datum/station_trait/hangover/revert()
. = ..()
SSjobs.drunken_spawning = FALSE
@@ -0,0 +1,219 @@
/datum/station_trait/galactic_grant
name = "Galactic grant"
trait_type = STATION_TRAIT_POSITIVE
weight = 5
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()
GLOB.station_money_database.credit_account(SSeconomy.cargo_account, rand(2000, 4000), "Galactic Grant", "Great Galactic Grant Group")
/datum/station_trait/premium_internals_box
name = "Premium internals boxes"
trait_type = STATION_TRAIT_POSITIVE
weight = 5
show_in_report = TRUE
report_message = "The internals boxes for your crew have been upsized and filled with bonus equipment."
trait_to_give = STATION_TRAIT_PREMIUM_INTERNALS
/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/filled_maint
name = "Filled up maintenance"
trait_type = STATION_TRAIT_POSITIVE
weight = 5
show_in_report = TRUE
report_message = "Our workers accidentally forgot more of their personal belongings in the maintenance 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 = 5
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_to_apply_to
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_REF(on_job_after_spawn))
/datum/station_trait/deathrattle_department/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned)
SIGNAL_HANDLER
if(department_to_apply_to)
if(!(job.job_department_flags & department_to_apply_to))
return
var/obj/item/bio_chip/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 = STATION_TRAIT_MAP_UNRESTRICTED
weight = 1
department_to_apply_to = DEP_FLAG_SERVICE
department_name = DEPARTMENT_SERVICE
/datum/station_trait/deathrattle_department/cargo
name = "Deathrattled Cargo"
trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
weight = 1
department_to_apply_to = DEP_FLAG_SUPPLY
department_name = DEPARTMENT_SUPPLY
/datum/station_trait/deathrattle_department/engineering
name = "Deathrattled Engineering"
trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
weight = 1
department_to_apply_to = DEP_FLAG_ENGINEERING
department_name = DEPARTMENT_ENGINEERING
/datum/station_trait/deathrattle_department/command
name = "Deathrattled Command"
trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
weight = 1
department_to_apply_to = DEP_FLAG_COMMAND
department_name = DEPARTMENT_COMMAND
/datum/station_trait/deathrattle_department/science
name = "Deathrattled Science"
trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
weight = 1
department_to_apply_to = DEP_FLAG_SCIENCE
department_name = DEPARTMENT_SCIENCE
/datum/station_trait/deathrattle_department/security
name = "Deathrattled Security"
trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
weight = 1
department_to_apply_to = DEP_FLAG_SECURITY
department_name = DEPARTMENT_SECURITY
/datum/station_trait/deathrattle_department/medical
name = "Deathrattled Medical"
trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
weight = 1
department_to_apply_to = DEP_FLAG_MEDICAL
department_name = DEPARTMENT_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_REF(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/bio_chip/deathrattle/implant_to_give = new()
deathrattle_group.register(implant_to_give)
implant_to_give.implant(spawned, spawned, TRUE, TRUE)
/datum/station_trait/cybernetic_revolution //NOTE: THIS MAKES EMP MUCH MORE EXPENSIVE.
name = "Cybernetic Revolution"
trait_type = STATION_TRAIT_POSITIVE
show_in_report = TRUE
weight = 2
report_message = "The new trends in cybernetics have come to the station! Everyone has some form of cybernetic implant."
trait_to_give = STATION_TRAIT_CYBERNETIC_REVOLUTION
/// List of all job types with the cybernetics they should receive.
var/static/list/job_to_cybernetic = list(
/datum/job/assistant = /obj/item/organ/internal/heart/cybernetic, //real action, real bloodshed
/datum/job/atmos = /obj/item/organ/internal/cyberimp/mouth/breathing_tube,
/datum/job/bartender = /obj/item/organ/internal/liver/cybernetic,
/datum/job/hydro = /obj/item/organ/internal/cyberimp/chest/nutriment,
/datum/job/captain = /obj/item/organ/internal/heart/cybernetic/upgraded,
/datum/job/cargo_tech = /obj/item/organ/internal/cyberimp/brain/anti_sleep,
/datum/job/chaplain = /obj/item/organ/internal/cyberimp/brain/anti_drop,
/datum/job/chemist = /obj/item/organ/internal/liver/cybernetic,
/datum/job/chief_engineer = /obj/item/organ/internal/eyes/cybernetic/meson,
/datum/job/cmo = /obj/item/organ/internal/cyberimp/chest/reviver,
/datum/job/clown = /obj/item/organ/internal/cyberimp/brain/anti_stam, //HONK!
/datum/job/chef = /obj/item/organ/internal/cyberimp/chest/nutriment/plus,
/datum/job/coroner = /obj/item/organ/internal/cyberimp/eyes/hud/medical, //hes got a bone to pick with you
/datum/job/librarian = /obj/item/organ/internal/cyberimp/brain/speech_translator,
/datum/job/detective = /obj/item/organ/internal/eyes/cybernetic/meson,
/datum/job/doctor = /obj/item/organ/internal/cyberimp/arm/surgery,
/datum/job/geneticist = /obj/item/organ/internal/alien/plasmavessel/hunter, //we don't care about implants, we have cancer.
/datum/job/hop = /obj/item/organ/internal/eyes/cybernetic/shield,
/datum/job/hos = /obj/item/organ/internal/cyberimp/arm/telebaton, //not giving them thermals
/datum/job/janitor = /obj/item/organ/internal/cyberimp/arm/janitorial, //Not giving them bloody xray
/datum/job/lawyer = /obj/item/organ/internal/heart/cybernetic/upgraded,
/datum/job/mime = /obj/item/organ/internal/cyberimp/brain/anti_stam, //...
/datum/job/paramedic = /obj/item/organ/internal/cyberimp/mouth/breathing_tube,
/datum/job/psychiatrist = /obj/item/organ/internal/heart/cybernetic/upgraded, //heart of gold. Or at least part gold
/datum/job/qm = /obj/item/organ/internal/cyberimp/arm/telebaton,
/datum/job/rd = /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic,
/datum/job/roboticist = /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic,
/datum/job/scientist = /obj/item/organ/internal/ears/cybernetic,
/datum/job/officer = /obj/item/organ/internal/cyberimp/eyes/hud/security,
/datum/job/mining = /obj/item/organ/internal/cyberimp/mouth/breathing_tube,
/datum/job/engineer = /obj/item/organ/internal/cyberimp/brain/wire_interface,
/datum/job/virologist = /obj/item/organ/internal/cyberimp/mouth/breathing_tube,
/datum/job/warden = /obj/item/organ/internal/cyberimp/arm/flash,
/datum/job/judge = /obj/item/organ/internal/cyberimp/arm/telebaton,
/datum/job/explorer = /obj/item/organ/internal/cyberimp/arm/toolset,
/datum/job/nanotrasenrep = /obj/item/organ/internal/heart/cybernetic/upgraded,
/datum/job/blueshield = /obj/item/organ/internal/heart/cybernetic/upgraded,
)
/datum/station_trait/cybernetic_revolution/New()
. = ..()
RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(on_job_after_spawn))
/datum/station_trait/cybernetic_revolution/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client)
SIGNAL_HANDLER
var/cybernetic_type = job_to_cybernetic[job.type]
if(!cybernetic_type)
if(isAI(spawned))
var/mob/living/silicon/ai/ai = spawned
ai.eyeobj.relay_speech = TRUE //surveillance upgrade. the ai gets cybernetics too.
return
var/obj/item/organ/internal/cybernetic = new cybernetic_type()
INVOKE_ASYNC(cybernetic, TYPE_PROC_REF(/obj/item/organ/internal, insert), spawned, TRUE)
+5 -5
View File
@@ -422,7 +422,7 @@
var/alcohol_resistance = 1
var/actual_strength = strength
var/datum/mind/M = owner.mind
var/is_ipc = ismachineperson(owner)
var/is_robot = ismachineperson(owner) || issilicon(owner)
if(HAS_TRAIT(owner, TRAIT_ALCOHOL_TOLERANCE))
alcohol_resistance = 2
@@ -430,7 +430,7 @@
actual_strength /= alcohol_resistance
var/obj/item/organ/internal/liver/L
if(!is_ipc)
if(!is_robot)
L = owner.get_int_organ(/obj/item/organ/internal/liver)
var/liver_multiplier = 5 // no liver? get shitfaced
if(L)
@@ -456,10 +456,10 @@
if(actual_strength >= THRESHOLD_CONFUSION && prob(0.33))
owner.AdjustConfused(6 SECONDS / alcohol_resistance, bound_lower = 2 SECONDS, bound_upper = 1 MINUTES)
// THRESHOLD_SPARK (100 SECONDS)
if(is_ipc && actual_strength >= THRESHOLD_SPARK && prob(0.25))
if(is_robot && actual_strength >= THRESHOLD_SPARK && prob(0.25))
do_sparks(3, 1, owner)
// THRESHOLD_VOMIT (120 SECONDS)
if(!is_ipc && actual_strength >= THRESHOLD_VOMIT && prob(0.08))
if(!is_robot && actual_strength >= THRESHOLD_VOMIT && prob(0.08))
owner.fakevomit()
// THRESHOLD_BLUR (150 SECONDS)
if(actual_strength >= THRESHOLD_BLUR)
@@ -474,7 +474,7 @@
owner.Drowsy(60 SECONDS / alcohol_resistance)
if(L)
L.receive_damage(1, TRUE)
if(!is_ipc)
if(!is_robot)
owner.adjustToxLoss(1)
// THRESHOLD_BRAIN_DAMAGE (240 SECONDS)
if(actual_strength >= THRESHOLD_BRAIN_DAMAGE && prob(0.1))
@@ -438,6 +438,11 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/storage/box/syndie_kit/emp
cost = 10
/datum/uplink_item/explosives/emp/New()
..()
if(HAS_TRAIT(SSstation, STATION_TRAIT_CYBERNETIC_REVOLUTION))
cost *= 3
// STEALTHY TOOLS
/datum/uplink_item/stealthy_tools
@@ -527,6 +532,11 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 20
surplus = 30
/datum/uplink_item/stealthy_tools/emplight/New()
..()
if(HAS_TRAIT(SSstation, STATION_TRAIT_CYBERNETIC_REVOLUTION))
cost *= 2.5
/datum/uplink_item/stealthy_tools/cutouts
name = "Adaptive Cardboard Cutouts"
desc = "These cardboard cutouts are coated with a thin material that prevents discoloration and makes the images on them appear more lifelike. This pack contains three as well as a \
@@ -0,0 +1,18 @@
/// This uplink catagory is for uplink items avalible by special circumstances. Think station traits, or if some event rolling in a round gave traitors special items, or an objective.
/datum/uplink_item/special
category = "Special Offers"
cant_discount = TRUE
surplus = 0
excludefrom = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST, UPLINK_TYPE_TRAITOR, UPLINK_TYPE_SIT)
/datum/uplink_item/special/autosurgeon
name = "Syndicate Autosurgeon"
desc = "A multi-use autosurgeon for implanting whatever you want into yourself. Rip that station apart and make it part of you."
reference = "SACR"
item = /obj/item/autosurgeon/organ/syndicate
cost = 25
/datum/uplink_item/special/autosurgeon/New()
..()
if(HAS_TRAIT(SSstation, STATION_TRAIT_CYBERNETIC_REVOLUTION))
excludefrom -= UPLINK_TYPE_TRAITOR
@@ -627,6 +627,11 @@
surplus = 0
cant_discount = TRUE
/datum/uplink_item/explosives/emp_bomb/New()
..()
if(HAS_TRAIT(SSstation, STATION_TRAIT_CYBERNETIC_REVOLUTION))
cost *= 1.25 //ok this thing is already very expencive it doesnt need much more
/datum/uplink_item/explosives/atmosfiregrenades
name = "Plasma Fire Grenades"
desc = "A box of two (2) grenades that cause large plasma fires. Can be used to deny access to a large area. Most useful if you have an atmospherics hardsuit."
+14
View File
@@ -81,6 +81,8 @@
INVOKE_ASYNC(src, PROC_REF(set_mode_in_db)) // Async query), dont bother slowing roundstart
generate_station_goals()
generate_station_trait_report()
GLOB.start_state = new /datum/station_state()
GLOB.start_state.count()
return 1
@@ -493,6 +495,18 @@
var/datum/station_goal/G = V
G.print_result()
/datum/game_mode/proc/generate_station_trait_report()
var/something_to_print = FALSE
var/list/trait_list_desc = list("<hr><b>Identified shift divergencies:</b>")
for(var/datum/station_trait/station_trait as anything in SSstation.station_traits)
if(!station_trait.show_in_report)
continue
trait_list_desc += station_trait.get_report()
something_to_print = TRUE
if(something_to_print)
print_command_report(trait_list_desc.Join("<br>"), "NAS Trurl Detected Divergencies", FALSE)
/datum/game_mode/proc/update_eventmisc_icons_added(datum/mind/mob_mind)
var/datum/atom_hud/antag/antaghud = GLOB.huds[ANTAG_HUD_EVENTMISC]
antaghud.join_hud(mob_mind.current)
+2
View File
@@ -74,6 +74,8 @@
//Only override this proc
/datum/job/proc/after_spawn(mob/living/carbon/human/H)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_SPAWN, src, H)
/datum/job/proc/announce(mob/living/carbon/human/H)
+4
View File
@@ -298,6 +298,8 @@
/datum/outfit/job/clown/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
if(HAS_TRAIT(SSstation, STATION_TRAIT_BANANIUM_SHIPMENTS))
backpack_contents += /obj/item/stack/sheet/mineral/bananium/fifty
if(H.gender == FEMALE)
mask = /obj/item/clothing/mask/gas/clown_hat/sexy
uniform = /obj/item/clothing/under/rank/civilian/clown/sexy
@@ -384,6 +386,8 @@
/datum/outfit/job/mime/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
if(HAS_TRAIT(SSstation, STATION_TRAIT_TRANQUILITE_SHIPMENTS))
backpack_contents += /obj/item/stack/sheet/mineral/tranquillite/fifty
if(H.gender == FEMALE)
uniform = /obj/item/clothing/under/rank/civilian/mime/sexy
suit = /obj/item/clothing/mask/gas/sexymime
@@ -168,6 +168,14 @@
"" = 61 // This should be a decently high number for chances where no loot will spawn
)
/obj/effect/spawner/lootdrop/maintenance/Initialize(mapload)
if(HAS_TRAIT(SSstation, STATION_TRAIT_EMPTY_MAINT) && prob(50))
return qdel(src)
if(HAS_TRAIT(SSstation, STATION_TRAIT_FILLED_MAINT) && prob(50))
lootcount = min(lootcount * 2, 12)
. = ..()
/obj/effect/spawner/lootdrop/maintenance/two
name = "maintenance loot spawner (2 items)"
icon_state = "doubleloot"
@@ -24,12 +24,20 @@ AI MODULES
/obj/item/aiModule/Initialize(mapload)
. = ..()
if(mapload && HAS_TRAIT(SSstation, STATION_TRAIT_UNIQUE_AI) && is_station_level(z))
var/delete_module = handle_unique_ai()
if(delete_module)
return INITIALIZE_HINT_QDEL
if(laws)
desc += "<br>"
for(var/datum/ai_law/current in laws.inherent_laws)
desc += current.law
desc += "<br>"
///what this module should do if it is mapload spawning on a unique AI station trait round.
/obj/item/aiModule/proc/handle_unique_ai()
return TRUE // If this returns true, it will be deleted on roundstart
/obj/item/aiModule/proc/install(obj/machinery/computer/C)
if(istype(C, /obj/machinery/computer/aiupload))
var/obj/machinery/computer/aiupload/comp = C
@@ -241,6 +249,9 @@ AI MODULES
to_chat(target, "<span class='boldnotice'>[sender.real_name] attempted to reset your laws using a reset module.</span>")
target.show_laws()
/obj/item/aiModule/reset/handle_unique_ai()
return FALSE
/******************** Purge ********************/
/obj/item/aiModule/purge // -- TLE
name = "\improper Purge AI module"
@@ -144,6 +144,7 @@
return
/obj/item/bio_chip/proc/activate(cause)
SEND_SIGNAL(src, COMSIG_IMPLANT_ACTIVATED, cause, imp_in)
return
/obj/item/bio_chip/ui_action_click()
@@ -200,6 +201,7 @@
if(user)
add_attack_logs(user, source, "Chipped with [src]")
SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTED, source, user, force)
return 1
/**
@@ -237,6 +239,7 @@
unregister_emotes()
SEND_SIGNAL(src, COMSIG_IMPLANT_REMOVED, source)
return TRUE
/obj/item/bio_chip/dropped(mob/user)
@@ -0,0 +1,76 @@
/datum/deathrattle_group
var/name
var/list/implants = list()
/datum/deathrattle_group/New(name)
if(name)
src.name = name
else
// Give the group a unique name for debugging, and possible future
// use for making custom linked groups.
src.name = "[rand(100, 999)] [pick(GLOB.phonetic_alphabet)]"
/*
* Proc called by new implant being added to the group. Listens for the
* implant being implanted, removed and destroyed.
*
* If implant is already implanted in a person, then trigger the implantation
* code.
*/
/datum/deathrattle_group/proc/register(obj/item/bio_chip/deathrattle/implant)
if(implant in implants)
return
RegisterSignal(implant, COMSIG_PARENT_QDELETING, PROC_REF(on_implant_destruction))
RegisterSignal(implant, COMSIG_IMPLANT_ACTIVATED, PROC_REF(on_user_death))
implants += implant
/datum/deathrattle_group/proc/on_implant_destruction(obj/item/bio_chip/implant)
SIGNAL_HANDLER
implants -= implant
/datum/deathrattle_group/proc/on_user_death(obj/item/bio_chip/implant, source, mob/owner)
SIGNAL_HANDLER
var/victim_name = owner.mind ? owner.mind.name : owner.real_name
// All "hearers" hear the same sound.
var/sound = pick(
'sound/items/knell1.ogg',
'sound/items/knell2.ogg',
'sound/items/knell3.ogg',
'sound/items/knell4.ogg',
)
for(var/obj/item/bio_chip/deathrattle/other_implant as anything in implants)
// Skip the unfortunate soul, and any unimplanted implants
if(implant == other_implant || !implant.imp_in)
continue
var/mob/living/recipient = other_implant.imp_in
to_chat(recipient, "<i>You hear a strange, robotic voice in your head...</i> <span class='robot'>\"<b>[victim_name]</b> has died...\"</span>")
recipient.playsound_local(get_turf(recipient), sound, vol = 75, vary = FALSE, pressure_affected = FALSE, use_reverb = FALSE)
qdel(implant)
/obj/item/bio_chip/deathrattle
name = "deathrattle implant"
desc = "Hope no one else dies, prepare for when they do."
activated = BIOCHIP_ACTIVATED_PASSIVE
trigger_causes = BIOCHIP_TRIGGER_DEATH_ONCE
implant_data = /datum/implant_fluff/deathrattle
implant_state = "implant-nanotrasen"
actions_types = null
/obj/item/bio_chip/deathrattle/emp_act(severity)
activate("emp")
/obj/item/bio_chip/deathrattle/death_trigger(mob/source, gibbed)
activate("death")
/obj/item/bio_chip_case/deathrattle
name = "implant case - 'Deathrattle'"
desc = "A glass case containing a deathrattle implant."
implant_type = /obj/item/bio_chip/deathrattle
@@ -134,3 +134,8 @@
life = "Unknown. No collected sample has been active long enough to determine lifespan."
notes = "Manually activated by the user."
function = "Allows the user to summon a box from a bluespace pocket located inside the implant. The exterior of this box is lined with experimental cloaking panels which render the box invisible to the naked eye."
/datum/implant_fluff/deathrattle
name = "BioTech Solutions \"Dead Ringer\" Death Ratttle Bio-chip"
notes = "Alerts implanted crew to crewmember death."
function = "Contains a compact radio signaler that triggers when the host's lifesigns cease, only sent to other implanted in the group."
@@ -48,9 +48,15 @@
/obj/item/storage/box/survival/populate_contents()
new /obj/item/clothing/mask/breath(src)
new /obj/item/tank/internals/emergency_oxygen(src)
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
new /obj/item/tank/internals/emergency_oxygen/engi(src)
new /obj/item/reagent_containers/hypospray/autoinjector/survival(src)
new /obj/item/flashlight/flare(src)
else
new /obj/item/tank/internals/emergency_oxygen(src)
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
/obj/item/storage/box/survival_vox
icon_state = "vox_box"
@@ -58,8 +64,12 @@
/obj/item/storage/box/survival_vox/populate_contents()
new /obj/item/clothing/mask/breath/vox(src)
new /obj/item/tank/internals/emergency_oxygen/nitrogen(src)
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
new /obj/item/reagent_containers/hypospray/autoinjector/survival(src)
new /obj/item/flashlight/flare(src)
else
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
/obj/item/storage/box/survival_plasmaman
icon_state = "plasma_box"
@@ -67,26 +77,40 @@
/obj/item/storage/box/survival_plasmaman/populate_contents()
new /obj/item/clothing/mask/breath(src)
new /obj/item/tank/internals/emergency_oxygen/plasma(src)
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
new /obj/item/reagent_containers/hypospray/autoinjector/survival(src)
new /obj/item/flashlight/flare(src)
else
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
/obj/item/storage/box/engineer
icon_state = "eng_box"
/obj/item/storage/box/engineer/populate_contents()
new /obj/item/clothing/mask/breath(src)
new /obj/item/tank/internals/emergency_oxygen/engi(src)
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
new /obj/item/tank/internals/emergency_oxygen/double(src)
new /obj/item/reagent_containers/hypospray/autoinjector/survival(src)
new /obj/item/flashlight/flare(src)
else
new /obj/item/tank/internals/emergency_oxygen/engi(src)
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
/obj/item/storage/box/survival_mining
icon_state = "min_box"
/obj/item/storage/box/survival_mining/populate_contents()
new /obj/item/clothing/mask/gas/explorer(src)
new /obj/item/tank/internals/emergency_oxygen/engi(src)
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
new /obj/item/tank/internals/emergency_oxygen/double(src)
new /obj/item/reagent_containers/hypospray/autoinjector/survival(src)
new /obj/item/flashlight/flare(src)
else
new /obj/item/tank/internals/emergency_oxygen/engi(src)
new /obj/item/reagent_containers/hypospray/autoinjector/epinephrine(src)
new /obj/item/flashlight/flare/glowstick/emergency(src)
/obj/item/storage/box/survival_syndi
icon_state = "syndi_box"
+2 -1
View File
@@ -109,7 +109,8 @@ GLOBAL_LIST_INIT(admin_verbs_event, list(
/client/proc/outfit_manager,
/client/proc/cmd_admin_headset_message,
/client/proc/change_human_appearance_admin, /* Allows an admin to change the basic appearance of human-based mobs */
/client/proc/change_human_appearance_self /* Allows the human-based mob itself to change its basic appearance */
/client/proc/change_human_appearance_self, /* Allows the human-based mob itself to change its basic appearance */
/datum/admins/proc/station_traits_panel
))
GLOBAL_LIST_INIT(admin_verbs_spawn, list(
@@ -47,6 +47,8 @@
var/light_range_on = 1
var/light_power_on = 0.5
/// Do we drop contents on destroy?
var/drop_contents_on_delete = TRUE
/obj/machinery/smartfridge/Initialize(mapload)
. = ..()
@@ -92,8 +94,9 @@
/obj/machinery/smartfridge/Destroy()
SStgui.close_uis(wires)
QDEL_NULL(wires)
for(var/atom/movable/A in contents)
A.forceMove(loc)
if(drop_contents_on_delete)
for(var/atom/movable/A in contents)
A.forceMove(loc)
return ..()
/obj/machinery/smartfridge/process()
@@ -527,6 +530,9 @@
/obj/machinery/smartfridge/secure/circuits/aiupload/Initialize(mapload)
. = ..()
req_access_txt = "[ACCESS_AI_UPLOAD]"
if(mapload && HAS_TRAIT(SSstation, STATION_TRAIT_UNIQUE_AI) && is_station_level(z))
drop_contents_on_delete = FALSE
return INITIALIZE_HINT_QDEL
/obj/machinery/smartfridge/secure/circuits/aiupload/experimental
name = "\improper Experimental Laws Storage"
@@ -138,3 +138,6 @@
/obj/item/organ/internal/regenerative_core/legion/preserved(implanted = 0)
..()
desc = "[src] has been stabilized. It is preserved, allowing you to use it to heal completely without danger of decay."
/obj/item/organ/internal/regenerative_core/legion/already_preserved
preserved = TRUE
@@ -126,7 +126,9 @@
laws.sort_laws()
/mob/living/silicon/proc/make_laws()
if(GLOB.configuration.general.random_ai_lawset)
if(HAS_TRAIT(SSstation, STATION_TRAIT_UNIQUE_AI))
laws = pick_unique_lawset()
else if(GLOB.configuration.general.random_ai_lawset)
laws = get_random_lawset()
else
laws = new /datum/ai_laws/crewsimov()
@@ -140,3 +142,15 @@
continue
law_options += L
return pick(law_options)
///returns a random non starting / kill crew lawset if the station has a unique ai lawset
/proc/pick_unique_lawset()
var/list/law_options = list()
var/paths = subtypesof(/datum/ai_laws)
for(var/law in paths)
var/datum/ai_laws/L = new law
if(!L.unique_ai)
continue
law_options += L
return pick(law_options)
@@ -55,6 +55,8 @@
deploy_the_cats()
/mob/living/simple_animal/pet/cat/Runtime/persistent_save()
if(SEND_SIGNAL(src, COMSIG_LIVING_WRITE_MEMORY) & COMPONENT_DONT_WRITE_MEMORY)
return FALSE
write_memory(FALSE)
/mob/living/simple_animal/pet/cat/Runtime/make_babies()
@@ -486,6 +486,8 @@
turns_per_move = 20
/mob/living/simple_animal/pet/dog/corgi/Ian/persistent_save()
if(SEND_SIGNAL(src, COMSIG_LIVING_WRITE_MEMORY) & COMPONENT_DONT_WRITE_MEMORY)
return FALSE
write_memory(FALSE)
/mob/living/simple_animal/pet/dog/corgi/Ian/proc/read_memory()
+7 -5
View File
@@ -369,12 +369,14 @@ GLOBAL_LIST_EMPTY(PDAs)
/obj/item/pda/proc/play_ringtone()
var/S
if(ttone in ttone_sound)
S = ttone_sound[ttone]
if(HAS_TRAIT(SSstation, STATION_TRAIT_PDA_GLITCHED))
playsound(src, pick('sound/machines/twobeep_voice1.ogg', 'sound/machines/twobeep_voice2.ogg'), 50, TRUE)
else
S = 'sound/machines/twobeep_high.ogg'
playsound(loc, S, 50, 1)
if(ttone in ttone_sound)
S = ttone_sound[ttone]
else
S = 'sound/machines/twobeep_high.ogg'
playsound(loc, S, 50, TRUE)
for(var/mob/O in hearers(3, loc))
O.show_message(text("[bicon(src)] *[ttone]*"))
@@ -19,6 +19,11 @@
flight_x_offset = 17
flight_y_offset = 9
/obj/item/gun/energy/ionrifle/Initialize(mapload)
. = ..()
if(mapload && HAS_TRAIT(SSstation, STATION_TRAIT_CYBERNETIC_REVOLUTION) && is_station_level(z)) //No ion rifle when everyone has cybernetic organs, sorry!
return INITIALIZE_HINT_QDEL
/obj/item/gun/energy/ionrifle/emp_act(severity)
return
+8 -8
View File
@@ -104,7 +104,7 @@
"supply_type" = order.object.name,
"orderedby" = order.orderedby,
"department" = order.ordered_by_department?.department_name,
"cost" = order.object.cost,
"cost" = order.object.get_cost(),
"comment" = order.comment,
"req_cargo_approval" = order.requires_cargo_approval,
"req_head_approval" = order.requires_head_approval,
@@ -214,7 +214,7 @@
if((pack.hidden && hacked) || (pack.contraband && can_order_contraband) || (pack.special && pack.special_enabled) || (!pack.contraband && !pack.hidden && !pack.special))
packs_list.Add(list(list(
"name" = pack.name,
"cost" = pack.cost,
"cost" = pack.get_cost(),
"ref" = "[pack.UID()]",
"contents" = pack.ui_manifest,
"cat" = pack.group)))
@@ -333,12 +333,12 @@
order.orderedbyaccount = selected_account
if(attempt_account_authentification(selected_account, user))
var/paid_for = FALSE
if(!order.requires_cargo_approval && pay_with_account(selected_account, order.object.cost, "[order.object.name] Crate Purchase", "Cargo Requests Console", user, account_database.vendor_account))
if(!order.requires_cargo_approval && pay_with_account(selected_account, order.object.get_cost(), "[order.object.name] Crate Purchase", "Cargo Requests Console", user, account_database.vendor_account))
paid_for = TRUE
SSeconomy.process_supply_order(order, paid_for) //add order to shopping list
else //if its a department account with pin or higher security or need QM approval, go ahead and add this to the departments section in request list
SSeconomy.process_supply_order(order, FALSE)
if(order.ordered_by_department.crate_auto_approve && order.ordered_by_department.auto_approval_cap >= order.object.cost)
if(order.ordered_by_department.crate_auto_approve && order.ordered_by_department.auto_approval_cap >= order.object.get_cost())
approve_crate(user, order.ordernum)
investigate_log("| [key_name(user)] has placed an order for [order.object.amount] [order.object.name] with reason: '[order.comment]'", "cargo")
@@ -355,7 +355,7 @@
return FALSE
order.requires_cargo_approval = FALSE
if(account.account_type == ACCOUNT_TYPE_PERSONAL || isnull(order.ordered_by_department))
if(pay_with_account(account, order.object.cost, "[pack.name] Crate Purchase", "Cargo Requests Console", user, account_database.vendor_account))
if(pay_with_account(account, order.object.get_cost(), "[pack.name] Crate Purchase", "Cargo Requests Console", user, account_database.vendor_account))
SSeconomy.process_supply_order(order, TRUE) //send 'er back through
return TRUE
atom_say("ERROR: Account tied to order cannot pay, auto-denying order")
@@ -368,12 +368,12 @@
//if they do not have access to this account
if(!department_order.ordered_by_department.has_account_access(user.get_access(), user.get_worn_id_account()))
//and the dept account doesn't have auto approve enabled (or does and the crate is too expensive for auto approve)
if(!department_order.ordered_by_department.crate_auto_approve || department_order.ordered_by_department.auto_approval_cap < pack.cost)
if(!department_order.ordered_by_department.crate_auto_approve || department_order.ordered_by_department.auto_approval_cap < pack.get_cost())
return //no access!
///just give the account pin here, its too much work for players to get the department account pin number since approval is access locked anyway
if(attempt_account_authentification(account, user, account.account_pin))
if(pay_with_account(account, pack.cost, "[pack.name] Crate Purchase", "[src]", user, account_database.vendor_account))
if(pay_with_account(account, pack.get_cost(), "[pack.name] Crate Purchase", "[src]", user, account_database.vendor_account))
order.requires_head_approval = FALSE
SSeconomy.process_supply_order(order, TRUE)
investigate_log("| [key_name(user)] has authorized an order for [pack.name]. Remaining Cargo Balance: [cargo_account.credit_balance].", "cargo")
@@ -418,7 +418,7 @@
SSshuttle.supply.request(SSshuttle.getDock("supply_home"))
/obj/machinery/computer/supplycomp/proc/pay_for_crate(datum/money_account/customer_account, mob/user, datum/supply_order/order)
if(pay_with_account(customer_account, order.object.cost, "Purchase of [order.object.name]", "Cargo Requests Console", user, cargo_account, TRUE))
if(pay_with_account(customer_account, order.object.get_cost(), "Purchase of [order.object.name]", "Cargo Requests Console", user, cargo_account, TRUE))
return TRUE
return FALSE
+1 -1
View File
@@ -34,7 +34,7 @@
//create the manifest slip
var/obj/item/paper/manifest/slip = new
slip.points = object.cost
slip.points = object.get_cost()
slip.ordernumber = ordernum
var/stationName = station_name()
+3
View File
@@ -59,3 +59,6 @@
if(length(contains))
for(var/j in 1 to num_contained)
. += pick(contains)
/datum/supply_packs/proc/get_cost()
return cost * SSeconomy.pack_price_modifier
+6 -1
View File
@@ -387,7 +387,12 @@
user.changeNext_move(CLICK_CD_MELEE)
var/obj/machinery/power/apc/A = target
var/mob/living/carbon/human/H = user
if(H.get_int_organ(/obj/item/organ/internal/cell))
if(H.get_int_organ(/obj/item/organ/internal/cell) || H.get_int_organ(/obj/item/organ/internal/heart))
var/obj/item/organ/internal/heart/robotic = H.get_int_organ(/obj/item/organ/internal/heart)
if(robotic)
if(!(robotic.status & ORGAN_ROBOT) && !H.get_int_organ(/obj/item/organ/internal/heart/demon/pulse))
to_chat(user, "<span class='warning'>You lack a cell in which to store charge!</span>")
return
if(A.emagged || A.stat & BROKEN)
do_sparks(3, 1, A)
to_chat(H, "<span class='warning'>The APC power currents surge erratically, damaging your chassis!</span>")
+9
View File
@@ -98,6 +98,7 @@
#include "code\__DEFINES\sound_defines.dm"
#include "code\__DEFINES\spell_defines.dm"
#include "code\__DEFINES\stat.dm"
#include "code\__DEFINES\station_defines.dm"
#include "code\__DEFINES\station_goals.dm"
#include "code\__DEFINES\status_effects.dm"
#include "code\__DEFINES\subsystems.dm"
@@ -311,6 +312,7 @@
#include "code\controllers\subsystem\processing\obj.dm"
#include "code\controllers\subsystem\processing\processing.dm"
#include "code\controllers\subsystem\processing\projectiles.dm"
#include "code\controllers\subsystem\processing\SSstation.dm"
#include "code\controllers\subsystem\processing\transittube.dm"
#include "code\controllers\subsystem\tickets\mentor_tickets.dm"
#include "code\controllers\subsystem\tickets\tickets.dm"
@@ -557,6 +559,11 @@
#include "code\datums\spells\alien_spells\regurgitate.dm"
#include "code\datums\spells\alien_spells\transfer_plasma.dm"
#include "code\datums\spells\alien_spells\whisper.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\postive_traits.dm"
#include "code\datums\status_effects\blob_burst.dm"
#include "code\datums\status_effects\buffs.dm"
#include "code\datums\status_effects\debuffs.dm"
@@ -565,6 +572,7 @@
#include "code\datums\status_effects\status_effect.dm"
#include "code\datums\uplink_items\uplink_general.dm"
#include "code\datums\uplink_items\uplink_nuclear.dm"
#include "code\datums\uplink_items\uplink_special.dm"
#include "code\datums\uplink_items\uplink_traitor.dm"
#include "code\datums\weather\weather_datum.dm"
#include "code\datums\weather\weather_types\ash_storm.dm"
@@ -1108,6 +1116,7 @@
#include "code\game\objects\items\weapons\bio_chips\bio_chip_case.dm"
#include "code\game\objects\items\weapons\bio_chips\bio_chip_chem.dm"
#include "code\game\objects\items\weapons\bio_chips\bio_chip_death_alarm.dm"
#include "code\game\objects\items\weapons\bio_chips\bio_chip_deathrattle.dm"
#include "code\game\objects\items\weapons\bio_chips\bio_chip_dust.dm"
#include "code\game\objects\items\weapons\bio_chips\bio_chip_emp.dm"
#include "code\game\objects\items\weapons\bio_chips\bio_chip_explosive.dm"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,242 @@
import { filterMap } from './common/collections';
import { exhaustiveCheck } from './common/exhaustive';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Divider, Dropdown, Flex, Tabs } from '../components';
import { Window } from '../layouts';
let Tab;
(function (Tab) {
Tab[(Tab['SetupFutureStationTraits'] = 0)] = 'SetupFutureStationTraits';
Tab[(Tab['ViewStationTraits'] = 1)] = 'ViewStationTraits';
})(Tab || (Tab = {}));
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 (
<Box>
<Flex fill>
<Flex.Item grow>
<Dropdown
displayText={!selectedTrait && 'Select trait to add...'}
onSelected={setSelectedTrait}
options={traitNames}
selected={selectedTrait}
width="190px"
/>
</Flex.Item>
<Flex.Item>
<Button
color="green"
icon="plus"
onClick={() => {
if (!selectedTrait) {
return;
}
const selectedPath = traitsByName[selectedTrait];
let newStationTraits = [selectedPath];
if (future_station_traits) {
const selectedTraitPaths = future_station_traits.map(
(trait) => trait.path
);
if (selectedTraitPaths.indexOf(selectedPath) !== -1) {
return;
}
newStationTraits = newStationTraits.concat(
...selectedTraitPaths
);
}
act('setup_future_traits', {
station_traits: newStationTraits,
});
}}
>
Add
</Button>
</Flex.Item>
</Flex>
<Divider />
{Array.isArray(future_station_traits) ? (
future_station_traits.length > 0 ? (
<Flex direction="column">
{future_station_traits.map((trait) => (
<div key={trait.path}>
<Flex.Item>
<Flex fill>
<Flex.Item grow>{trait.name}</Flex.Item>
<Flex.Item>
<Button
color="red"
icon="times"
onClick={() => {
act('setup_future_traits', {
station_traits: filterMap(
future_station_traits,
(otherTrait) =>
otherTrait.path === trait.path
? undefined
: otherTrait.path
),
});
}}
>
Delete
</Button>
</Flex.Item>
</Flex>
</Flex.Item>
</div>
))}
</Flex>
) : (
<>
<Box>No station traits will run next round.</Box>
<Box>
<Button
color="red"
icon="times"
tooltip="The next round will roll station traits randomly, just like normal"
onClick={() => act('clear_future_traits')}
>
Run Station Traits Normally
</Button>
</Box>
</>
)
) : (
<>
<Box>No future station traits are planned.</Box>
<Box>
<Button
color="red"
icon="times"
onClick={() =>
act('setup_future_traits', {
station_traits: [],
})
}
>
Prevent station traits from running next round
</Button>
</Box>
</>
)}
</Box>
);
};
const ViewStationTraitsPage = (props, context) => {
const { act, data } = useBackend(context);
return data.current_traits.length > 0 ? (
<Flex direction="column">
{data.current_traits.map((stationTrait) => (
<div key={stationTrait.ref}>
<Flex.Item>
<Flex fill>
<Flex.Item grow>{stationTrait.name}</Flex.Item>
<Flex.Item>
<Button.Confirm
content="Revert"
color="red"
disabled={data.too_late_to_revert || !stationTrait.can_revert}
tooltip={
(!stationTrait.can_revert &&
'This trait is not revertable.') ||
(data.too_late_to_revert &&
"It's too late to revert station traits, the round has already started.")
}
icon="times"
onClick={() =>
act('revert', {
ref: stationTrait.ref,
})
}
/>
</Flex.Item>
</Flex>
</Flex.Item>
</div>
))}
</Flex>
) : (
<Box>There are no active station traits.</Box>
);
};
export const StationTraitsPanel = (props, context) => {
const [currentTab, setCurrentTab] = useLocalState(
context,
'station_traits_tab',
Tab.ViewStationTraits
);
let currentPage;
switch (currentTab) {
case Tab.SetupFutureStationTraits:
currentPage = <FutureStationTraitsPage />;
break;
case Tab.ViewStationTraits:
currentPage = <ViewStationTraitsPage />;
break;
default:
exhaustiveCheck(currentTab);
}
return (
<Window title="Modify Station Traits" height={500} width={500}>
<Window.Content scrollable>
<Tabs>
<Tabs.Tab
icon="eye"
selected={currentTab === Tab.ViewStationTraits}
onClick={() => setCurrentTab(Tab.ViewStationTraits)}
>
View
</Tabs.Tab>
<Tabs.Tab
icon="edit"
selected={currentTab === Tab.SetupFutureStationTraits}
onClick={() => setCurrentTab(Tab.SetupFutureStationTraits)}
>
Edit
</Tabs.Tab>
</Tabs>
<Divider />
{currentPage}
</Window.Content>
</Window>
);
};
@@ -0,0 +1,315 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
/**
* Iterates over elements of collection, returning an array of all elements
* iteratee returns truthy for. The predicate is invoked with three
* arguments: (value, index|key, collection).
*
* If collection is 'null' or 'undefined', it will be returned "as is"
* without emitting any errors (which can be useful in some cases).
*/
export const filter = (iterateeFn) => (collection) => {
if (collection === null || collection === undefined) {
return collection;
}
if (Array.isArray(collection)) {
const result = [];
for (let i = 0; i < collection.length; i++) {
const item = collection[i];
if (iterateeFn(item, i, collection)) {
result.push(item);
}
}
return result;
}
throw new Error(`filter() can't iterate on type ${typeof collection}`);
};
/**
* Creates an array of values by running each element in collection
* thru an iteratee function. The iteratee is invoked with three
* arguments: (value, index|key, collection).
*
* If collection is 'null' or 'undefined', it will be returned "as is"
* without emitting any errors (which can be useful in some cases).
*/
export const map = (iterateeFn) => (collection) => {
if (collection === null || collection === undefined) {
return collection;
}
if (Array.isArray(collection)) {
return collection.map(iterateeFn);
}
if (typeof collection === 'object') {
return Object.entries(collection).map(([key, value]) => {
return iterateeFn(value, key, 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, iterateeFn) => {
const finalCollection = [];
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;
const length = criteriaA.length;
for (let i = 0; i < length; i++) {
const a = criteriaA[i];
const b = criteriaB[i];
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
}
return 0;
};
/**
* Creates an array of elements, sorted in ascending order by the results
* of running each element in a collection thru each iteratee.
*
* Iteratees are called with one argument (value).
*/
export const sortBy =
(...iterateeFns) =>
(array) => {
if (!Array.isArray(array)) {
return array;
}
let length = array.length;
// Iterate over the array to collect criteria to sort it by
let mappedArray = [];
for (let i = 0; i < length; i++) {
const value = array[i];
mappedArray.push({
criteria: iterateeFns.map((fn) => fn(value)),
value,
});
}
// Sort criteria using the base comparator
mappedArray.sort(COMPARATOR);
// Unwrap values
const values = [];
while (length--) {
values[length] = mappedArray[length].value;
}
return values;
};
export const sort = sortBy();
export const sortStrings = sortBy();
/**
* Returns a range of numbers from start to end, exclusively.
* For example, range(0, 5) will return [0, 1, 2, 3, 4].
*/
export const range = (start, end) =>
new Array(end - start).fill(null).map((_, index) => index + start);
/**
* A fast implementation of reduce.
*/
export const reduce = (reducerFn, initialValue) => (array) => {
const length = array.length;
let i;
let result;
if (initialValue === undefined) {
i = 1;
result = array[0];
} else {
i = 0;
result = initialValue;
}
for (; i < length; i++) {
result = reducerFn(result, array[i], i, array);
}
return result;
};
/**
* Creates a duplicate-free version of an array, using SameValueZero for
* equality comparisons, in which only the first occurrence of each element
* is kept. The order of result values is determined by the order they occur
* in the array.
*
* It accepts iteratee which is invoked for each element in array to generate
* the criterion by which uniqueness is computed. The order of result values
* is determined by the order they occur in the array. The iteratee is
* invoked with one argument: value.
*/
export const uniqBy = (iterateeFn) => (array) => {
const { length } = array;
const result = [];
const seen = iterateeFn ? [] : result;
let index = -1;
// prettier-ignore
outer:
while (++index < length) {
let value = array[index];
const computed = iterateeFn ? iterateeFn(value) : value;
if (computed === computed) {
let seenIndex = seen.length;
while (seenIndex--) {
if (seen[seenIndex] === computed) {
continue outer;
}
}
if (iterateeFn) {
seen.push(computed);
}
result.push(value);
} else if (!seen.includes(computed)) {
if (seen !== result) {
seen.push(computed);
}
result.push(value);
}
}
return result;
};
export const uniq = uniqBy();
/**
* Creates an array of grouped elements, the first of which contains
* the first elements of the given arrays, the second of which contains
* the second elements of the given arrays, and so on.
*/
export const zip = (...arrays) => {
if (arrays.length === 0) {
return [];
}
const numArrays = arrays.length;
const numValues = arrays[0].length;
const result = [];
for (let valueIndex = 0; valueIndex < numValues; valueIndex++) {
const entry = [];
for (let arrayIndex = 0; arrayIndex < numArrays; arrayIndex++) {
entry.push(arrays[arrayIndex][valueIndex]);
}
// I tried everything to remove this any, and have no idea how to do it.
result.push(entry);
}
return result;
};
/**
* This method is like "zip" except that it accepts iteratee to
* specify how grouped values should be combined. The iteratee is
* invoked with the elements of each group.
*/
export const zipWith =
(iterateeFn) =>
(...arrays) => {
return map((values) => iterateeFn(...values))(zip(...arrays));
};
const binarySearch = (getKey, collection, inserting) => {
if (collection.length === 0) {
return 0;
}
const insertingKey = getKey(inserting);
let [low, high] = [0, collection.length];
// Because we have checked if the collection is empty, it's impossible
// for this to be used before assignment.
let compare;
let middle = 0;
while (low < high) {
middle = (low + high) >> 1;
compare = getKey(collection[middle]);
if (compare < insertingKey) {
low = middle + 1;
} else if (compare === insertingKey) {
return middle;
} else {
high = middle;
}
}
return compare > insertingKey ? middle : middle + 1;
};
export const binaryInsertWith = (getKey) => (collection, value) => {
const copy = [...collection];
copy.splice(binarySearch(getKey, collection, value), 0, value);
return copy;
};
/**
* This method takes a collection of items and a number, returning a collection
* of collections, where the maximum amount of items in each is that second arg
*/
export const paginate = (collection, maxPerPage) => {
const pages = [];
let page = [];
let itemsToAdd = maxPerPage;
for (const item of collection) {
page.push(item);
itemsToAdd--;
if (!itemsToAdd) {
itemsToAdd = maxPerPage;
pages.push(page);
page = [];
}
}
if (page.length) {
pages.push(page);
}
return pages;
};
const isObject = (obj) => typeof obj === 'object' && obj !== null;
// Does a deep merge of two objects. DO NOT FEED CIRCULAR OBJECTS!!
export const deepMerge = (...objects) => {
const target = {};
for (const object of objects) {
for (const key of Object.keys(object)) {
const targetValue = target[key];
const objectValue = object[key];
if (Array.isArray(targetValue) && Array.isArray(objectValue)) {
target[key] = [...targetValue, ...objectValue];
} else if (isObject(targetValue) && isObject(objectValue)) {
target[key] = deepMerge(targetValue, objectValue);
} else {
target[key] = objectValue;
}
}
}
return target;
};
@@ -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) => {
throw new Error(`Unhandled case: ${input}`);
};
File diff suppressed because one or more lines are too long