diff --git a/_maps/map_files/BirdStation/BirdStation.dmm b/_maps/map_files/BirdStation/BirdStation.dmm index 3152aa03f1e..35ae80339e6 100644 --- a/_maps/map_files/BirdStation/BirdStation.dmm +++ b/_maps/map_files/BirdStation/BirdStation.dmm @@ -2066,6 +2066,7 @@ /obj/machinery/light{ dir = 1 }, +/obj/machinery/ntnet_relay, /turf/open/floor/plasteel/black, /area/turret_protected/tcomeast{ name = "\improper Telecommunications Chamber" diff --git a/_maps/map_files/DreamStation/dreamstation04.dmm b/_maps/map_files/DreamStation/dreamstation04.dmm index ce5aee3ffa3..998b7be8a72 100644 --- a/_maps/map_files/DreamStation/dreamstation04.dmm +++ b/_maps/map_files/DreamStation/dreamstation04.dmm @@ -25112,6 +25112,7 @@ pixel_x = -22; tag = "icon-alarm0 (EAST)" }, +/obj/machinery/ntnet_relay, /turf/open/floor/plasteel/darkyellow{ name = "Mainframe Floor"; initial_gas_mix = "n2=100;TEMP=80" diff --git a/_maps/map_files/EfficiencyStation/EfficiencyStation.dmm b/_maps/map_files/EfficiencyStation/EfficiencyStation.dmm index 63de9b61a30..067630f1b1a 100644 --- a/_maps/map_files/EfficiencyStation/EfficiencyStation.dmm +++ b/_maps/map_files/EfficiencyStation/EfficiencyStation.dmm @@ -55719,6 +55719,7 @@ /area/tcommsat/server) "cib" = ( /obj/machinery/power/terminal, +/obj/machinery/ntnet_relay, /turf/open/floor/bluegrid{ name = "Mainframe Base"; initial_gas_mix = "n2=100;TEMP=80" diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 2b50d03428e..d7da3f0c00f 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -54567,6 +54567,7 @@ network = list("SS13","tcomm") }, /obj/machinery/atmospherics/pipe/simple/supply/hidden, +/obj/machinery/ntnet_relay, /turf/open/floor/plasteel/black{ name = "Mainframe Floor"; initial_gas_mix = "n2=100;TEMP=80" diff --git a/_maps/map_files/TgStation/tgstation.2.1.3.dmm b/_maps/map_files/TgStation/tgstation.2.1.3.dmm index 4fca40808ca..eab81182fa2 100644 --- a/_maps/map_files/TgStation/tgstation.2.1.3.dmm +++ b/_maps/map_files/TgStation/tgstation.2.1.3.dmm @@ -19381,7 +19381,7 @@ }, /area/bridge) "aRo" = ( -/obj/machinery/computer/card, +/obj/machinery/modular_computer/console/preset/command, /turf/open/floor/plasteel/green/side{ dir = 10 }, @@ -35121,6 +35121,7 @@ }, /area/crew_quarters/hor) "bzN" = ( +/obj/machinery/modular_computer/console/preset/research, /turf/open/floor/plasteel/warnwhite{ dir = 6 }, @@ -47453,6 +47454,7 @@ /obj/machinery/power/terminal{ dir = 4 }, +/obj/machinery/ntnet_relay, /turf/open/floor/bluegrid{ name = "Mainframe Base"; initial_gas_mix = "n2=100;TEMP=80" @@ -49048,13 +49050,11 @@ /turf/open/floor/plating, /area/maintenance/aft) "cdk" = ( -/obj/machinery/computer/monitor{ - name = "primary power monitoring console" - }, /obj/structure/cable{ icon_state = "0-2"; d2 = 2 }, +/obj/machinery/modular_computer/console/preset/engineering, /turf/open/floor/plasteel, /area/engine/engineering) "cdl" = ( diff --git a/code/__DEFINES/clockcult.dm b/code/__DEFINES/clockcult.dm index 504dcb27e96..3bc8ff23a82 100644 --- a/code/__DEFINES/clockcult.dm +++ b/code/__DEFINES/clockcult.dm @@ -6,6 +6,7 @@ var/global/list/all_clockwork_objects = list() //All clockwork items, structures var/global/list/all_clockwork_mobs = list() //All clockwork SERVANTS (not creatures) in existence var/global/list/clockwork_component_cache = list("belligerent_eye" = 0, "vanguard_cogwheel" = 0, "guvax_capacitor" = 0, "replicant_alloy" = 0, "hierophant_ansible" = 0) //The pool of components that caches draw from var/global/ratvar_awakens = FALSE //If Ratvar has been summoned +var/global/clockwork_gateway_activated = FALSE //if a gateway to the celestial derelict has ever been successfully activated #define SCRIPTURE_PERIPHERAL "Peripheral" //Scripture tiers; peripherals should never be used #define SCRIPTURE_DRIVER "Driver" @@ -47,3 +48,11 @@ var/global/ratvar_awakens = FALSE //If Ratvar has been summoned #define GATEWAY_RATVAR_COMING 250 //when progress is at or above this, ratvar has entered and is coming through the gateway #define GATEWAY_RATVAR_ARRIVAL 300 //when progress is at or above this, game over ratvar's here everybody go home + +//Objective defines + +#define CLOCKCULT_GATEWAY "gateway" + +#define CLOCKCULT_ESCAPE "escape" + +#define CLOCKCULT_SILICONS "silicons" diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index b07c05179d9..f7fb200c757 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -152,6 +152,7 @@ #define CLICK_CD_MELEE 8 #define CLICK_CD_RANGE 4 +#define CLICK_CD_RAPID 2 #define CLICK_CD_CLICK_ABILITY 6 #define CLICK_CD_BREAKOUT 100 #define CLICK_CD_HANDCUFFED 10 @@ -498,4 +499,32 @@ var/global/list/ghost_others_options = list(GHOST_OTHERS_SIMPLE, GHOST_OTHERS_DE #define BUBBLEGUM_SCORE "Bubblegum Killed" #define DRAKE_SCORE "Drakes Killed" #define BOSS_SCORE "Bosses Killed" -#define TENDRIL_CLEAR_SCORE "Tendrils Killed" \ No newline at end of file +#define TENDRIL_CLEAR_SCORE "Tendrils Killed" + + + + // NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc) +#define NTNET_SOFTWAREDOWNLOAD 1 // Downloads of software from NTNet +#define NTNET_PEERTOPEER 2 // P2P transfers of files between devices +#define NTNET_COMMUNICATION 3 // Communication (messaging) +#define NTNET_SYSTEMCONTROL 4 // Control of various systems, RCon, air alarm control, etc. + +// NTNet transfer speeds, used when downloading/uploading a file/program. +#define NTNETSPEED_LOWSIGNAL 0.5 // GQ/s transfer speed when the device is wirelessly connected and on Low signal +#define NTNETSPEED_HIGHSIGNAL 1 // GQ/s transfer speed when the device is wirelessly connected and on High signal +#define NTNETSPEED_ETHERNET 2 // GQ/s transfer speed when the device is using wired connection + + +// Program bitflags +#define PROGRAM_ALL 7 +#define PROGRAM_CONSOLE 1 +#define PROGRAM_LAPTOP 2 +#define PROGRAM_TABLET 4 + +#define PROGRAM_STATE_KILLED 0 +#define PROGRAM_STATE_BACKGROUND 1 +#define PROGRAM_STATE_ACTIVE 2 + +// Caps for NTNet logging. Less than 10 would make logging useless anyway, more than 500 may make the log browser too laggy. Defaults to 100 unless user changes it. +#define MAX_NTNET_LOGS 300 +#define MIN_NTNET_LOGS 10 \ No newline at end of file diff --git a/code/__DEFINES/tick.dm b/code/__DEFINES/tick.dm index 04e333fc95c..03ba94a8b3a 100644 --- a/code/__DEFINES/tick.dm +++ b/code/__DEFINES/tick.dm @@ -1,7 +1,7 @@ -#define TICK_LIMIT_RUNNING 85 -#define TICK_LIMIT_TO_RUN 80 -#define TICK_LIMIT_MC 84 -#define TICK_LIMIT_MC_INIT 100 +#define TICK_LIMIT_RUNNING 80 +#define TICK_LIMIT_TO_RUN 78 +#define TICK_LIMIT_MC 70 +#define TICK_LIMIT_MC_INIT 98 #define TICK_CHECK ( world.tick_usage > CURRENT_TICKLIMIT ? stoplag() : 0 ) #define CHECK_TICK if (world.tick_usage > CURRENT_TICKLIMIT) stoplag() diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index a23204ac5d8..b6f7407e3fc 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -22,7 +22,7 @@ /proc/log_adminsay(text) if (config.log_adminchat) log_admin("ASAY: [text]") - + /proc/log_dsay(text) if (config.log_adminchat) log_admin("DSAY: [text]") @@ -76,3 +76,6 @@ //reusing the PDA option because I really don't think news comments are worth a config option diary << "\[[time_stamp()]]COMMENT: [text]" +/proc/log_chat(text) + if (config.log_pda) + diary << "\[[time_stamp()]]CHAT: [text]" \ No newline at end of file diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 8f1775261d5..b5014ec7c91 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -10,6 +10,7 @@ var/global/list/deliverybeacons = list() //list of all MULEbot delivery beacon var/global/list/deliverybeacontags = list() //list of all tags associated with delivery beacons. var/global/list/nuke_list = list() var/global/list/nuke_tiles = list() //list of all turfs that turn to animated red grids when a nuke is triggered +var/global/list/alarmdisplay = list() //list of all machines or programs that can display station alerts var/global/list/chemical_reactions_list //list of all /datum/chemical_reaction datums. Used during chemical reactions var/global/list/chemical_reagents_list //list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 02287efea2b..f4afbe8179e 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -287,7 +287,10 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." for(var/obj/structure/clockwork/massive/celestial_gateway/G in all_clockwork_objects) var/area/gate_area = get_area(G) desc += "Ark Location: [uppertext(gate_area.map_name)]
" - desc += "Seconds until Ratvar's arrival: [G.get_arrival_text(TRUE)]
" + if(G.ratvar_portal) + desc += "Seconds until Ratvar's arrival: [G.get_arrival_text(TRUE)]
" + else + desc += "Seconds until Proselytization: [G.get_arrival_text(TRUE)]
" if(unconverted_ai_exists) desc += "An unconverted AI exists!
" if(scripture_states[SCRIPTURE_REVENANT]) diff --git a/code/_onclick/hud/blob_overmind.dm b/code/_onclick/hud/blob_overmind.dm index 1a5a6252b98..566132808b4 100644 --- a/code/_onclick/hud/blob_overmind.dm +++ b/code/_onclick/hud/blob_overmind.dm @@ -54,7 +54,7 @@ /obj/screen/blob/Blobbernaut icon_state = "ui_blobbernaut" name = "Produce Blobbernaut (40)" - desc = "Produces a strong, smart blobbernaut from a factory blob for 40 points.
The factory blob used will become fragile and unable to produce spores." + desc = "Produces a strong, smart blobbernaut from a factory blob for 40 resources.
The factory blob used will become fragile and unable to produce spores." /obj/screen/blob/Blobbernaut/Click() if(isovermind(usr)) @@ -64,7 +64,7 @@ /obj/screen/blob/ResourceBlob icon_state = "ui_resource" name = "Produce Resource Blob (40)" - desc = "Produces a resource blob for 40 points.
Resource blobs will give you points every few seconds." + desc = "Produces a resource blob for 40 resources.
Resource blobs will give you resources every few seconds." /obj/screen/blob/ResourceBlob/Click() if(isovermind(usr)) @@ -73,8 +73,8 @@ /obj/screen/blob/NodeBlob icon_state = "ui_node" - name = "Produce Node Blob (60)" - desc = "Produces a node blob for 60 points.
Node blobs will expand and activate nearby resource and factory blobs." + name = "Produce Node Blob (50)" + desc = "Produces a node blob for 50 resources.
Node blobs will expand and activate nearby resource and factory blobs." /obj/screen/blob/NodeBlob/Click() if(isovermind(usr)) @@ -84,7 +84,7 @@ /obj/screen/blob/FactoryBlob icon_state = "ui_factory" name = "Produce Factory Blob (60)" - desc = "Produces a factory blob for 60 points.
Factory blobs will produce spores every few seconds." + desc = "Produces a factory blob for 60 resources.
Factory blobs will produce spores every few seconds." /obj/screen/blob/FactoryBlob/Click() if(isovermind(usr)) @@ -94,7 +94,7 @@ /obj/screen/blob/ReadaptChemical icon_state = "ui_chemswap" name = "Readapt Chemical (40)" - desc = "Randomly rerolls your chemical for 40 points." + desc = "Randomly rerolls your chemical for 40 resources." /obj/screen/blob/ReadaptChemical/MouseEntered(location,control,params) if(hud && hud.mymob && isovermind(hud.mymob)) @@ -115,7 +115,7 @@ /obj/screen/blob/RelocateCore icon_state = "ui_swap" name = "Relocate Core (80)" - desc = "Swaps a node and your core for 80 points." + desc = "Swaps a node and your core for 80 resources." /obj/screen/blob/RelocateCore/Click() if(isovermind(usr)) @@ -159,11 +159,11 @@ static_inventory += using using = new /obj/screen/blob/NodeBlob() - using.screen_loc = ui_lhand + using.screen_loc = ui_rhand static_inventory += using using = new /obj/screen/blob/FactoryBlob() - using.screen_loc = ui_rhand + using.screen_loc = ui_lhand static_inventory += using using = new /obj/screen/blob/ReadaptChemical() diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 045b87e3eb3..f3dcf37940c 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -208,7 +208,8 @@ ticker.mode.update_wiz_icons_removed(src) ticker.mode.update_cult_icons_removed(src) ticker.mode.update_rev_icons_removed(src) - gang_datum.remove_gang_hud(src) + if(gang_datum) + gang_datum.remove_gang_hud(src) //Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does. @@ -283,7 +284,7 @@ ) var/text = "" - if (istype(current, /mob/living/carbon/human) || istype(current, /mob/living/carbon/monkey)) + if(ishuman(current)) /** REVOLUTION ***/ text = "revolution" if (ticker.mode.config_tag=="revolution") @@ -362,100 +363,23 @@ sections["gang"] = text - - /** CULT ***/ - text = "cult" - if (ticker.mode.config_tag=="cult") + /** Abductors **/ + text = "Abductor" + if(ticker.mode.config_tag == "abductor") text = uppertext(text) text = "[text]: " - if (src in ticker.mode.cult) - text += "loyal|employee|CULTIST" - text += "
Give tome|amulet." -/* - if (objectives.len==0) - text += "
Objectives are empty! Set to sacrifice and escape or summon." -*/ - else if(isloyal(current)) - text += "LOYAL|employee|cultist" + if(src in ticker.mode.abductors) + text += "Abductor|human" + text += "|undress|equip" else - text += "loyal|EMPLOYEE|cultist" + text += "Abductor|human" - if(current && current.client && (ROLE_CULTIST in current.client.prefs.be_special)) + if(current && current.client && (ROLE_ABDUCTOR in current.client.prefs.be_special)) text += "|Enabled in Prefs" else text += "|Disabled in Prefs" - sections["cult"] = text - - /** CLOCKWORK CULT **/ - text = "clockwork cult" - if(ticker.mode.config_tag == "clockwork cult") - text = uppertext(text) - text = "[text]: " - if(src in ticker.mode.servants_of_ratvar) - text += "loyal|employee|SERVANT" - text += "
Give slab" - else if(isloyal(current)) - text += "LOYAL|employee|servant" - else - text += "loyal|EMPLOYEE|servant" - - if(current && current.client && (ROLE_SERVANT_OF_RATVAR in current.client.prefs.be_special)) - text += "|Enabled in Prefs" - else - text += "|Disabled in Prefs" - - sections["clockcult"] = text - - /** WIZARD ***/ - text = "wizard" - if (ticker.mode.config_tag=="wizard") - text = uppertext(text) - text = "[text]: " - if ((src in ticker.mode.wizards) || (src in ticker.mode.apprentices)) - text += "YES|no" - text += "
To lair, undress, dress up, let choose name." - if (objectives.len==0) - text += "
Objectives are empty! Randomize!" - else - text += "yes|NO" - - if(current && current.client && (ROLE_WIZARD in current.client.prefs.be_special)) - text += "|Enabled in Prefs" - else - text += "|Disabled in Prefs" - - sections["wizard"] = text - - /** CHANGELING ***/ - text = "changeling" - if (ticker.mode.config_tag=="changeling" || ticker.mode.config_tag=="traitorchan") - text = uppertext(text) - text = "[text]: " - if ((src in ticker.mode.changelings) && special_role) - text += "YES|no" - if (objectives.len==0) - text += "
Objectives are empty! Randomize!" - if(changeling && changeling.stored_profiles.len && (current.real_name != changeling.first_prof.name) ) - text += "
Transform to initial appearance." - else if(src in ticker.mode.changelings) //Station Aligned Changeling - text += "YES (but not an antag)|no" - if (objectives.len==0) - text += "
Objectives are empty! Randomize!" - if(changeling && changeling.stored_profiles.len && (current.real_name != changeling.first_prof.name) ) - text += "
Transform to initial appearance." - else - text += "yes|NO" -// var/datum/game_mode/changeling/changeling = ticker.mode -// if (istype(changeling) && changeling.changelingdeath) -// text += "
All the changelings are dead! Restart in [round((changeling.TIME_TO_GET_REVIVED-(world.time-changeling.changelingdeathtime))/10)] seconds." - - if(current && current.client && (ROLE_CHANGELING in current.client.prefs.be_special)) - text += "|Enabled in Prefs" - else - text += "|Disabled in Prefs" - - sections["changeling"] = text + sections["abductor"] = text /** NUCLEAR ***/ text = "nuclear" @@ -482,6 +406,71 @@ sections["nuclear"] = text + /** WIZARD ***/ + text = "wizard" + if (ticker.mode.config_tag=="wizard") + text = uppertext(text) + text = "[text]: " + if ((src in ticker.mode.wizards) || (src in ticker.mode.apprentices)) + text += "YES|no" + text += "
To lair, undress, dress up, let choose name." + if (objectives.len==0) + text += "
Objectives are empty! Randomize!" + else + text += "yes|NO" + + if(current && current.client && (ROLE_WIZARD in current.client.prefs.be_special)) + text += "|Enabled in Prefs" + else + text += "|Disabled in Prefs" + + sections["wizard"] = text + + /** CULT ***/ + text = "cult" + if (ticker.mode.config_tag=="cult") + text = uppertext(text) + text = "[text]: " + if (src in ticker.mode.cult) + text += "loyal|employee|CULTIST" + text += "
Give tome|amulet." + + else if(isloyal(current)) + text += "LOYAL|employee|cultist" + else if(is_convertable_to_cult(src)) + text += "loyal|EMPLOYEE|cultist" + else + text += "loyal|EMPLOYEE|cannot serve Nar-Sie" + + if(current && current.client && (ROLE_CULTIST in current.client.prefs.be_special)) + text += "|Enabled in Prefs" + else + text += "|Disabled in Prefs" + + sections["cult"] = text + + /** CLOCKWORK CULT **/ + text = "clockwork cult" + if(ticker.mode.config_tag == "clockwork cult") + text = uppertext(text) + text = "[text]: " + if(src in ticker.mode.servants_of_ratvar) + text += "loyal|employee|SERVANT" + text += "
Give slab" + else if(isloyal(current)) + text += "LOYAL|employee|servant" + else if(is_eligible_servant(current)) + text += "loyal|EMPLOYEE|servant" + else + text += "loyal|EMPLOYEE|cannot serve Ratvar" + + if(current && current.client && (ROLE_SERVANT_OF_RATVAR in current.client.prefs.be_special)) + text += "|Enabled in Prefs" + else + text += "|Disabled in Prefs" + + sections["clockcult"] = text + /** TRAITOR ***/ text = "traitor" if (ticker.mode.config_tag=="traitor" || ticker.mode.config_tag=="traitorchan") @@ -501,25 +490,6 @@ sections["traitor"] = text - /** Abductors **/ - - text = "Abductor" - if(ticker.mode.config_tag == "abductor") - text = uppertext(text) - text = "[text]: " - if(src in ticker.mode.abductors) - text += "Abductor|human" - text += "|undress|equip" - else - text += "Abductor|human" - - if(current && current.client && (ROLE_ABDUCTOR in current.client.prefs.be_special)) - text += "|Enabled in Prefs" - else - text += "|Disabled in Prefs" - - sections["abductor"] = text - /** HAND OF GOD **/ text = "hand of god" if(ticker.mode.config_tag == "handofgod") @@ -552,8 +522,36 @@ sections["follower"] = text - /** MONKEY ***/ - if (istype(current, /mob/living/carbon)) + if(ishuman(current) || ismonkey(current)) + + /** CHANGELING ***/ + text = "changeling" + if (ticker.mode.config_tag=="changeling" || ticker.mode.config_tag=="traitorchan") + text = uppertext(text) + text = "[text]: " + if ((src in ticker.mode.changelings) && special_role) + text += "YES|no" + if (objectives.len==0) + text += "
Objectives are empty! Randomize!" + if(changeling && changeling.stored_profiles.len && (current.real_name != changeling.first_prof.name) ) + text += "
Transform to initial appearance." + else if(src in ticker.mode.changelings) //Station Aligned Changeling + text += "YES (but not an antag)|no" + if (objectives.len==0) + text += "
Objectives are empty! Randomize!" + if(changeling && changeling.stored_profiles.len && (current.real_name != changeling.first_prof.name) ) + text += "
Transform to initial appearance." + else + text += "yes|NO" + + if(current && current.client && (ROLE_CHANGELING in current.client.prefs.be_special)) + text += "|Enabled in Prefs" + else + text += "|Disabled in Prefs" + + sections["changeling"] = text + + /** MONKEY ***/ text = "monkey" if (ticker.mode.config_tag=="monkey") text = uppertext(text) @@ -1227,7 +1225,7 @@ log_admin("[key_name(usr)] has de-sintouch'ed [current].") if("devil") if(!ishuman(current) && !iscyborg(current)) - usr << "This only works on humans!" + usr << "This only works on humans and cyborgs!" return ticker.mode.devils += src special_role = "devil" diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm index 1761efdda88..c2e372825a1 100644 --- a/code/game/area/Space_Station_13_areas.dm +++ b/code/game/area/Space_Station_13_areas.dm @@ -139,27 +139,34 @@ var/list/teleportlocs = list() /area/shuttle/mining name = "Mining Shuttle" + blob_allowed = FALSE /area/shuttle/labor name = "Labor Camp Shuttle" + blob_allowed = FALSE /area/shuttle/supply name = "Supply Shuttle" + blob_allowed = FALSE /area/shuttle/escape name = "Emergency Shuttle" /area/shuttle/transport name = "Transport Shuttle" + blob_allowed = FALSE /area/shuttle/syndicate name = "Syndicate Infiltrator" + blob_allowed = FALSE /area/shuttle/assault_pod name = "Steel Rain" + blob_allowed = FALSE /area/shuttle/abandoned name = "Abandoned Ship" + blob_allowed = FALSE /area/start name = "start area" @@ -658,6 +665,7 @@ var/list/teleportlocs = list() luminosity = 1 lighting_use_dynamic = DYNAMIC_LIGHTING_IFSTARLIGHT valid_territory = 0 + blob_allowed = FALSE /area/solar/auxport name = "Fore Port Solar Array" diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 5032ea6ec26..4b82ec57231 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -74,6 +74,11 @@ D.cancelAlarm("Power", src, source) else D.triggerAlarm("Power", src, cameras, source) + for(var/datum/computer_file/program/alarm_monitor/p in alarmdisplay) + if(state == 1) + p.cancelAlarm("Power", src, source) + else + p.triggerAlarm("Power", src, cameras, source) /area/proc/atmosalert(danger_level, obj/source) if(danger_level != atmosalm) @@ -89,6 +94,8 @@ a.triggerAlarm("Atmosphere", src, cameras, source) for(var/mob/living/simple_animal/drone/D in mob_list) D.triggerAlarm("Atmosphere", src, cameras, source) + for(var/datum/computer_file/program/alarm_monitor/p in alarmdisplay) + p.triggerAlarm("Atmosphere", src, cameras, source) else if (src.atmosalm == 2) for(var/mob/living/silicon/aiPlayer in player_list) @@ -97,6 +104,8 @@ a.cancelAlarm("Atmosphere", src, source) for(var/mob/living/simple_animal/drone/D in mob_list) D.cancelAlarm("Atmosphere", src, source) + for(var/datum/computer_file/program/alarm_monitor/p in alarmdisplay) + p.cancelAlarm("Atmosphere", src, source) src.atmosalm = danger_level return 1 @@ -128,6 +137,8 @@ aiPlayer.triggerAlarm("Fire", src, cameras, source) for (var/mob/living/simple_animal/drone/D in mob_list) D.triggerAlarm("Fire", src, cameras, source) + for(var/datum/computer_file/program/alarm_monitor/p in alarmdisplay) + p.triggerAlarm("Fire", src, cameras, source) /area/proc/firereset(obj/source) for(var/area/RA in related) @@ -150,6 +161,8 @@ a.cancelAlarm("Fire", src, source) for (var/mob/living/simple_animal/drone/D in mob_list) D.cancelAlarm("Fire", src, source) + for(var/datum/computer_file/program/alarm_monitor/p in alarmdisplay) + p.cancelAlarm("Fire", src, source) /area/proc/burglaralert(obj/trigger) if(always_unpowered == 1) //no burglar alarms in space/asteroid diff --git a/code/game/gamemodes/blob/blob.dm b/code/game/gamemodes/blob/blob.dm index 1d0da75bf4c..8b0a671a1b4 100644 --- a/code/game/gamemodes/blob/blob.dm +++ b/code/game/gamemodes/blob/blob.dm @@ -25,11 +25,12 @@ var/list/blobs_legit = list() //used for win-score calculations, contains only b Blobs: Consume the station and spread as far as you can.\n\ Crew: Fight back the blobs and minimize station damage." - var/burst = 0 + var/message_sent = FALSE var/cores_to_spawn = 1 var/players_per_core = 25 var/blob_point_rate = 3 + var/blob_base_starting_points = 80 var/blobwincount = 350 @@ -73,7 +74,8 @@ var/list/blobs_legit = list() //used for win-score calculations, contains only b /datum/game_mode/blob/post_setup() for(var/datum/mind/blob in blob_overminds) - var/mob/camera/blob/B = blob.current.become_overmind(1) + var/mob/camera/blob/B = blob.current.become_overmind(TRUE, round(blob_base_starting_points/blob_overminds.len)) + B.mind.name = B.name var/turf/T = pick(blobstart) B.loc = T B.base_point_rate = blob_point_rate @@ -91,6 +93,7 @@ var/list/blobs_legit = list() //used for win-score calculations, contains only b sleep(message_delay) send_intercept(1) + message_sent = TRUE sleep(24000) //40 minutes, plus burst_delay*3(minimum of 6 minutes, maximum of 8) diff --git a/code/game/gamemodes/blob/blob_finish.dm b/code/game/gamemodes/blob/blob_finish.dm index 5c0ad36b3ea..3f7167905a1 100644 --- a/code/game/gamemodes/blob/blob_finish.dm +++ b/code/game/gamemodes/blob/blob_finish.dm @@ -1,10 +1,14 @@ /datum/game_mode/blob/check_finished() if(blobwincount <= blobs_legit.len)//Blob took over return 1 - if(overminds.len) - return 0 + for(var/datum/mind/blob in blob_overminds) + if(isovermind(blob.current)) + var/mob/camera/blob/B = blob.current + if(B.blob_core || !B.placed) + return 0 if(!blob_cores.len) //blob is dead if(config.continuous["blob"]) + message_sent = FALSE //disable the win count at this point continuous_sanity_checked = 1 //Nonstandard definition of "alive" gets past the check otherwise SSshuttle.clearHostileEnvironment(src) return ..() @@ -18,23 +22,38 @@ if(blobwincount <= blobs_legit.len) feedback_set_details("round_end_result","win - blob took over") world << "The blob has taken over the station!" - world << "The entire station was eaten by the Blob" + world << "The entire station was eaten by the Blob!" log_game("Blob mode completed with a blob victory.") else if(station_was_nuked) feedback_set_details("round_end_result","halfwin - nuke") world << "Partial Win: The station has been destroyed!" - world << "Directive 7-12 has been successfully carried out preventing the Blob from spreading." + world << "Directive 7-12 has been successfully carried out, preventing the Blob from spreading." log_game("Blob mode completed with a tie (station destroyed).") else if(!blob_cores.len) feedback_set_details("round_end_result","loss - blob eliminated") world << "The staff has won!" - world << "The alien organism has been eradicated from the station" + world << "The alien organism has been eradicated from the station!" log_game("Blob mode completed with a crew victory.") ..() return 1 +/datum/game_mode/blob/printplayer(datum/mind/ply, fleecheck) + if((ply in blob_overminds)) + var/text = "
[ply.key] was [ply.name]" + if(isovermind(ply.current)) + var/mob/camera/blob/B = ply.current + text += "([B.blob_reagent_datum.name]) and" + if(B.blob_core) + text += " survived" + else + text += " was destroyed" + else + text += " and was destroyed" + return text + return ..() + /datum/game_mode/proc/auto_declare_completion_blob() if(istype(ticker.mode,/datum/game_mode/blob) ) var/datum/game_mode/blob/blob_mode = src diff --git a/code/game/gamemodes/blob/blobs/blob_mobs.dm b/code/game/gamemodes/blob/blobs/blob_mobs.dm index 67c404ce600..c0bfa64bf37 100644 --- a/code/game/gamemodes/blob/blobs/blob_mobs.dm +++ b/code/game/gamemodes/blob/blobs/blob_mobs.dm @@ -168,20 +168,23 @@ return ..() /mob/living/simple_animal/hostile/blob/blobspore/update_icons() - ..() + if(overmind) + color = overmind.blob_reagent_datum.complementary_color + else + color = initial(color) if(is_zombie) cut_overlays() overlays = human_overlays var/image/I = image('icons/mob/blob.dmi', icon_state = "blob_head") if(overmind) - I.color = overmind.blob_reagent_datum.color + I.color = overmind.blob_reagent_datum.complementary_color color = initial(color)//looks better. add_overlay(I) /mob/living/simple_animal/hostile/blob/blobspore/weak name = "fragile blob spore" - health = 20 - maxHealth = 20 + health = 15 + maxHealth = 15 melee_damage_lower = 1 melee_damage_upper = 2 death_cloud_size = 0 @@ -199,7 +202,6 @@ health = 200 maxHealth = 200 damage_coeff = list(BRUTE = 0.5, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) - next_move_modifier = 1.5 //slow-ass attack speed, 3 times higher than how fast the blob can attack melee_damage_lower = 20 melee_damage_upper = 20 obj_damage = 20 diff --git a/code/game/gamemodes/blob/blobs/core.dm b/code/game/gamemodes/blob/blobs/core.dm index e97f09e3a10..db52561020b 100644 --- a/code/game/gamemodes/blob/blobs/core.dm +++ b/code/game/gamemodes/blob/blobs/core.dm @@ -7,7 +7,6 @@ maxhealth = 400 explosion_block = 6 point_return = -1 - atmosblock = 1 health_regen = 0 //we regen in Life() instead of when pulsed var/core_regen = 2 var/overmind_get_delay = 0 //we don't want to constantly try to find an overmind, this var tracks when we'll try to get an overmind again @@ -50,7 +49,8 @@ return ..() /obj/effect/blob/core/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) - return + exposed_temperature *= 0.5 + ..() /obj/effect/blob/core/ex_act(severity, target) var/damage = 50 - 10 * severity //remember, the core takes half brute damage, so this is 20/15/10 damage based on severity diff --git a/code/game/gamemodes/blob/blobs/node.dm b/code/game/gamemodes/blob/blobs/node.dm index b6e8b24f59d..55214953f64 100644 --- a/code/game/gamemodes/blob/blobs/node.dm +++ b/code/game/gamemodes/blob/blobs/node.dm @@ -7,13 +7,12 @@ maxhealth = 200 health_regen = 3 point_return = 25 - atmosblock = 1 -/obj/effect/blob/node/New(loc, var/h = 100) +/obj/effect/blob/node/New(loc) blob_nodes += src START_PROCESSING(SSobj, src) - ..(loc, h) + ..(loc) /obj/effect/blob/node/scannerreport() return "Gradually expands and sustains nearby blob spores and blobbernauts." @@ -28,14 +27,15 @@ var/image/C = new('icons/mob/blob.dmi', "blob_node_overlay") src.add_overlay(C) -/obj/effect/blob/node/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) - return - /obj/effect/blob/node/Destroy() blob_nodes -= src STOP_PROCESSING(SSobj, src) return ..() +/obj/effect/blob/node/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) + exposed_temperature *= 0.75 + ..() + /obj/effect/blob/node/Life() Pulse_Area(overmind, 10, 3, 2) color = null diff --git a/code/game/gamemodes/blob/blobs/shield.dm b/code/game/gamemodes/blob/blobs/shield.dm index cd879363cf6..515e19cbd61 100644 --- a/code/game/gamemodes/blob/blobs/shield.dm +++ b/code/game/gamemodes/blob/blobs/shield.dm @@ -1,21 +1,37 @@ /obj/effect/blob/shield name = "strong blob" icon = 'icons/mob/blob.dmi' - icon_state = "blob_idle" + icon_state = "blob_shield" desc = "A solid wall of slightly twitching tendrils." health = 150 maxhealth = 150 - brute_resist = 0.1 + brute_resist = 0.25 explosion_block = 3 point_return = 4 atmosblock = 1 /obj/effect/blob/shield/scannerreport() - return "Will prevent the spread of atmospheric changes." + if(atmosblock) + return "Will prevent the spread of atmospheric changes." + return "N/A" /obj/effect/blob/shield/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) return /obj/effect/blob/shield/core point_return = 0 + +/obj/effect/blob/shield/update_icon() + ..() + if(health <= 75) + icon_state = "blob_shield_damaged" + name = "weakened strong blob" + desc = "A wall of twitching tendrils." + atmosblock = 0 + else + icon_state = initial(icon_state) + name = initial(name) + desc = initial(desc) + atmosblock = 1 + air_update_turf(1) diff --git a/code/game/gamemodes/blob/overmind.dm b/code/game/gamemodes/blob/overmind.dm index 3b712515d88..b5f7a58481c 100644 --- a/code/game/gamemodes/blob/overmind.dm +++ b/code/game/gamemodes/blob/overmind.dm @@ -1,12 +1,15 @@ /mob/camera/blob name = "Blob Overmind" real_name = "Blob Overmind" + desc = "The overmind. It controls the blob." icon = 'icons/mob/blob.dmi' icon_state = "marker" - + mouse_opacity = 1 + move_on_shuttle = 1 see_in_dark = 8 see_invisible = SEE_INVISIBLE_MINIMUM invisibility = INVISIBILITY_OBSERVER + layer = FLY_LAYER pass_flags = PASSBLOB faction = list("blob") @@ -26,7 +29,8 @@ var/manualplace_min_time = 600 //in deciseconds //a minute, to get bearings var/autoplace_max_time = 3600 //six minutes, as long as should be needed -/mob/camera/blob/New(loc, pre_placed = 0, mode_made = 0) +/mob/camera/blob/New(loc, pre_placed = 0, mode_made = 0, starting_points = 60) + blob_points = starting_points if(pre_placed) //we already have a core! manualplace_min_time = 0 autoplace_max_time = 0 @@ -42,10 +46,9 @@ name = new_name real_name = new_name last_attack = world.time - var/list/possible_reagents = list() - for(var/type in (subtypesof(/datum/reagent/blob))) - possible_reagents.Add(new type) - blob_reagent_datum = pick(possible_reagents) + var/datum/reagent/blob/BC = pick((subtypesof(/datum/reagent/blob))) + blob_reagent_datum = new BC + color = blob_reagent_datum.complementary_color if(blob_core) blob_core.update_icon() @@ -70,18 +73,19 @@ /mob/camera/blob/Destroy() for(var/BL in blobs) var/obj/effect/blob/B = BL - if(B.overmind == src) + if(B && B.overmind == src) B.overmind = null B.update_icon() //reset anything that was ours for(var/BLO in blob_mobs) var/mob/living/simple_animal/hostile/blob/BM = BLO - BM.overmind = null - BM.update_icons() + if(BM) + BM.overmind = null + BM.update_icons() overminds -= src if(ghostimage) ghost_darkness_images -= ghostimage qdel(ghostimage) - ghostimage = null; + ghostimage = null updateallghostimages() return ..() @@ -91,6 +95,12 @@ src << "You are the overmind!" blob_help() update_health_hud() + add_points(0) + +/mob/camera/blob/examine(mob/user) + ..() + if(blob_reagent_datum) + user << "Its chemical is [blob_reagent_datum.name]." /mob/camera/blob/update_health_hud() if(blob_core) @@ -100,9 +110,8 @@ B.hud_used.blobpwrdisplay.maptext = "
[round(blob_core.health)]
" /mob/camera/blob/proc/add_points(points) - if(points != 0) - blob_points = Clamp(blob_points + points, 0, max_blob_points) - hud_used.blobpwrdisplay.maptext = "
[round(src.blob_points)]
" + blob_points = Clamp(blob_points + points, 0, max_blob_points) + hud_used.blobpwrdisplay.maptext = "
[round(blob_points)]
" /mob/camera/blob/say(message) if (!message) @@ -150,6 +159,11 @@ if(blob_core) stat(null, "Core Health: [blob_core.health]") stat(null, "Power Stored: [blob_points]/[max_blob_points]") + if(ticker && istype(ticker.mode, /datum/game_mode/blob)) + var/datum/game_mode/blob/B = ticker.mode + stat(null, "Blobs to Win: [blobs_legit.len]/[B.blobwincount]") + else + stat(null, "Total Blobs: [blobs.len]") if(free_chem_rerolls) stat(null, "You have [free_chem_rerolls] Free Chemical Reroll\s Remaining") if(!placed) @@ -170,6 +184,3 @@ return 0 loc = NewLoc return 1 - -/mob/camera/blob/proc/can_attack() - return (world.time > (last_attack + CLICK_CD_RANGE)) diff --git a/code/game/gamemodes/blob/powers.dm b/code/game/gamemodes/blob/powers.dm index aa8485cf2fc..f44346002a7 100644 --- a/code/game/gamemodes/blob/powers.dm +++ b/code/game/gamemodes/blob/powers.dm @@ -67,12 +67,13 @@ set desc = "Move your camera to a selected node." if(blob_nodes.len) var/list/nodes = list() - for(var/i = 1; i <= blob_nodes.len; i++) - nodes["Blob Node #[i]"] = blob_nodes[i] + for(var/i in 1 to blob_nodes.len) + var/obj/effect/blob/node/B = blob_nodes[i] + nodes["Blob Node #[i] ([B.overmind ? "B.overmind.blob_reagent_datum.name":"No Chemical"]"] = B var/node_name = input(src, "Choose a node to jump to.", "Node Jump") in nodes var/obj/effect/blob/node/chosen_node = nodes[node_name] if(chosen_node) - src.loc = chosen_node.loc + loc = chosen_node.loc /mob/camera/blob/proc/createSpecial(price, blobType, nearEquals, needsNode, turf/T) if(!T) @@ -110,12 +111,12 @@ /mob/camera/blob/verb/create_shield_power() set category = "Blob" - set name = "Create Shield Blob (10)" + set name = "Create Shield Blob (15)" set desc = "Create a shield blob, which will block fire and is hard to kill." create_shield() /mob/camera/blob/proc/create_shield(turf/T) - createSpecial(10, /obj/effect/blob/shield, 0, 0, T) + createSpecial(15, /obj/effect/blob/shield, 0, 0, T) /mob/camera/blob/verb/create_resource() set category = "Blob" @@ -125,9 +126,9 @@ /mob/camera/blob/verb/create_node() set category = "Blob" - set name = "Create Node Blob (60)" + set name = "Create Node Blob (50)" set desc = "Create a node, which will power nearby factory and resource blobs." - createSpecial(60, /obj/effect/blob/node, 5, 0) + createSpecial(50, /obj/effect/blob/node, 5, 0) /mob/camera/blob/verb/create_factory() set category = "Blob" @@ -166,9 +167,8 @@ blobber.adjustHealth(blobber.maxHealth * 0.5) blob_mobs += blobber var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a [blob_reagent_datum.name] blobbernaut?", ROLE_BLOB, null, ROLE_BLOB, 50) //players must answer rapidly - var/client/C = null if(candidates.len) //if we got at least one candidate, they're a blobbernaut now. - C = pick(candidates) + var/client/C = pick(candidates) blobber.notransform = 0 blobber.key = C.key blobber << 'sound/effects/blobattack.ogg' @@ -190,11 +190,21 @@ if(!B) src << "You must be on a blob node!" return + if(!blob_core) + src << "You have no core and are about to die! May you rest in peace." + return + var/area/A = get_area(T) + if(istype(T, /turf/open/space) || A && !A.blob_allowed) + src << "You cannot relocate your core here!" + return if(!can_buy(80)) return - var/turf/old_turf = blob_core.loc - blob_core.loc = T - B.loc = old_turf + var/turf/old_turf = get_turf(blob_core) + var/olddir = blob_core.dir + blob_core.forceMove(T) + blob_core.setDir(B.dir) + B.forceMove(old_turf) + B.setDir(olddir) /mob/camera/blob/verb/revert() set category = "Blob" @@ -221,21 +231,22 @@ /mob/camera/blob/verb/expand_blob_power() set category = "Blob" - set name = "Expand/Attack Blob (5)" + set name = "Expand/Attack Blob (4)" set desc = "Attempts to create a new blob in this tile. If the tile isn't clear, instead attacks it, damaging mobs and objects." var/turf/T = get_turf(src) expand_blob(T) /mob/camera/blob/proc/expand_blob(turf/T) - if(!can_attack()) + if(world.time > last_attack) return - var/obj/effect/blob/OB = locate() in circlerange(T, 1) - if(!OB) + var/list/possibleblobs = list() + for(var/obj/effect/blob/AB in range(T, 1)) + possibleblobs += AB + if(!possibleblobs.len) src << "There is no blob adjacent to the target tile!" return - if(can_buy(5)) + if(can_buy(4)) var/attacksuccess = FALSE - last_attack = world.time for(var/mob/living/L in T) if("blob" in L.faction) //no friendly/dead fire continue @@ -250,10 +261,31 @@ B.blob_attack_animation(T, src) else src << "There is a blob there!" - add_points(5) //otherwise, refund all of the cost - return + add_points(4) //otherwise, refund all of the cost else - OB.expand(T, src) + var/list/cardinalblobs = list() + var/list/diagonalblobs = list() + for(var/I in possibleblobs) + var/obj/effect/blob/IB = I + if(get_dir(IB, T) in cardinal) + cardinalblobs += IB + else + diagonalblobs += IB + var/obj/effect/blob/OB + if(cardinalblobs.len) + OB = pick(cardinalblobs) + OB.expand(T, src) + else + OB = pick(diagonalblobs) + if(attacksuccess) + OB.blob_attack_animation(T, src) + playsound(OB, 'sound/effects/splat.ogg', 50, 1) + else + add_points(4) //if we're attacking diagonally and didn't hit anything, refund + if(attacksuccess) + last_attack = world.time + CLICK_CD_MELEE + else + last_attack = world.time + CLICK_CD_RAPID /mob/camera/blob/verb/rally_spores_power() set category = "Blob" @@ -298,6 +330,7 @@ /mob/camera/blob/proc/set_chemical() var/datum/reagent/blob/BC = pick((subtypesof(/datum/reagent/blob) - blob_reagent_datum.type)) blob_reagent_datum = new BC + color = blob_reagent_datum.complementary_color for(var/BL in blobs) var/obj/effect/blob/B = BL B.update_icon() @@ -308,6 +341,8 @@ BM << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.shortdesc ? "[blob_reagent_datum.shortdesc]" : "[blob_reagent_datum.description]"]" src << "Your reagent is now: [blob_reagent_datum.name]!" src << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.description]" + if(blob_reagent_datum.effectdesc) + src << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.effectdesc]" /mob/camera/blob/verb/blob_help() set category = "Blob" @@ -316,6 +351,8 @@ src << "As the overmind, you can control the blob!" src << "Your blob reagent is: [blob_reagent_datum.name]!" src << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.description]" + if(blob_reagent_datum.effectdesc) + src << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.effectdesc]" src << "You can expand, which will attack people, damage objects, or place a Normal Blob if the tile is clear." src << "Normal Blobs will expand your reach and can be upgraded into special blobs that perform certain functions." src << "You can upgrade normal blobs into the following types of blob:" @@ -329,4 +366,4 @@ src << "Attempting to talk will send a message to all other overminds, allowing you to coordinate with them." if(!placed && autoplace_max_time <= world.time) src << "You will automatically place your blob core in [round((autoplace_max_time - world.time)/600, 0.5)] minutes." - src << "You [manualplace_min_time ? "will be able to":"can"] manually place your blob core by pressing the button in the bottom right corner of the screen." + src << "You [manualplace_min_time ? "will be able to":"can"] manually place your blob core by pressing the Place Blob Core button in the bottom right corner of the screen." diff --git a/code/game/gamemodes/blob/theblob.dm b/code/game/gamemodes/blob/theblob.dm index 29c3d15b625..eb72803f497 100644 --- a/code/game/gamemodes/blob/theblob.dm +++ b/code/game/gamemodes/blob/theblob.dm @@ -7,12 +7,13 @@ density = 0 //this being 0 causes two bugs, being able to attack blob tiles behind other blobs and being unable to move on blob tiles in no gravity, but turning it to 1 causes the blob mobs to be unable to path through blobs, which is probably worse. opacity = 0 anchored = 1 - explosion_block = 1 + layer = BELOW_MOB_LAYER var/point_return = 0 //How many points the blob gets back when it removes a blob of that type. If less than 0, blob cannot be removed. var/health = 30 var/maxhealth = 30 var/health_regen = 2 //how much health this blob regens when pulsed - var/pulse_timestamp = 0 //we got pulsed/healed when? + var/pulse_timestamp = 0 //we got pulsed when? + var/heal_timestamp = 0 //we got healed when? var/brute_resist = 0.5 //multiplies brute damage by this var/fire_resist = 1 //multiplies burn damage by this var/atmosblock = 0 //if the blob blocks atmos and heat spread @@ -24,8 +25,8 @@ if(Ablob.blob_allowed) //Is this area allowed for winning as blob? blobs_legit += src blobs += src //Keep track of the blob in the normal list either way - src.setDir(pick(1, 2, 4, 8)) - src.update_icon() + setDir(pick(cardinal)) + update_icon() ..(loc) ConsumeTile() if(atmosblock) @@ -84,46 +85,56 @@ if(overmind) overmind.blob_reagent_datum.death_reaction(src, cause) qdel(src) //we dead now - return - return /obj/effect/blob/update_icon() //Updates color based on overmind color if we have an overmind. if(overmind) color = overmind.blob_reagent_datum.color else color = null - return - /obj/effect/blob/process() Life() - return /obj/effect/blob/proc/Life() return /obj/effect/blob/proc/Pulse_Area(pulsing_overmind = overmind, claim_range = 10, pulse_range = 3, expand_range = 2) src.Be_Pulsed() - if(claim_range) - for(var/obj/effect/blob/B in urange(claim_range, src, 1)) - if(!B.overmind && !istype(B, /obj/effect/blob/core) && prob(30)) - B.overmind = pulsing_overmind //reclaim unclaimed, non-core blobs. - B.update_icon() - if(pulse_range) - for(var/obj/effect/blob/B in orange(pulse_range, src)) + var/expanded = FALSE + if(prob(70) && expand()) + expanded = TRUE + var/list/blobs_to_affect = list() + for(var/obj/effect/blob/B in urange(claim_range, src, 1)) + blobs_to_affect += B + shuffle(blobs_to_affect) + for(var/L in blobs_to_affect) + var/obj/effect/blob/B = L + if(!B.overmind && !istype(B, /obj/effect/blob/core) && prob(30)) + B.overmind = pulsing_overmind //reclaim unclaimed, non-core blobs. + B.update_icon() + var/distance = get_dist(get_turf(src), get_turf(B)) + var/expand_probablity = max(20 - distance * 8, 1) + if(B.Adjacent(src)) + expand_probablity = 20 + if(distance <= expand_range) + var/can_expand = TRUE + if(blobs_to_affect.len >= 120 && B.heal_timestamp > world.time) + can_expand = FALSE + if(can_expand && B.pulse_timestamp <= world.time && prob(expand_probablity)) + var/obj/effect/blob/newB = B.expand(null, null, !expanded) //expansion falls off with range but is faster near the blob causing the expansion + if(newB) + if(expanded) + qdel(newB) + expanded = TRUE + if(distance <= pulse_range) B.Be_Pulsed() - if(expand_range) - if(prob(85)) - src.expand() - for(var/obj/effect/blob/B in orange(expand_range, src)) - if(prob(max(13 - get_dist(get_turf(src), get_turf(B)) * 4, 1))) //expand falls off with range but is faster near the blob causing the expansion - B.expand() - return /obj/effect/blob/proc/Be_Pulsed() if(pulse_timestamp <= world.time) ConsumeTile() - health = min(maxhealth, health+health_regen) + if(heal_timestamp <= world.time) + health = min(maxhealth, health+health_regen) + heal_timestamp = world.time + 20 update_icon() pulse_timestamp = world.time + 10 return 1 //we did it, we were pulsed! @@ -137,6 +148,7 @@ /obj/effect/blob/proc/blob_attack_animation(atom/A = null, controller) //visually attacks an atom var/obj/effect/overlay/temp/blob/O = PoolOrNew(/obj/effect/overlay/temp/blob, src.loc) + O.setDir(dir) if(controller) var/mob/camera/blob/BO = controller O.color = BO.blob_reagent_datum.color @@ -311,6 +323,7 @@ B.overmind = controller B.creation_action() B.update_icon() + B.setDir(dir) qdel(src) return B @@ -342,13 +355,13 @@ brute_resist = 0.25 /obj/effect/blob/normal/scannerreport() - if(health <= 10) + if(health <= 15) return "Currently weak to brute damage." return "N/A" /obj/effect/blob/normal/update_icon() ..() - if(health <= 10) + if(health <= 15) icon_state = "blob_damaged" name = "fragile blob" desc = "A thin lattice of slightly twitching tendrils." diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm index af87096c7a1..63397149602 100644 --- a/code/game/gamemodes/clock_cult/clock_cult.dm +++ b/code/game/gamemodes/clock_cult/clock_cult.dm @@ -16,7 +16,6 @@ Components, which are fallen fragments of Ratvar's body as he rusts in Reebe, ar Game-wise, clockwork slabs will generate components over time, with more powerful components being slower. This file's folder contains: - __clock_defines.dm: Defined variables clock_cult.dm: Core gamemode files. clock_items.dm: Items (excluding the proselytizer). clock_machines.dm: Machinery like the mending motor. @@ -27,6 +26,7 @@ This file's folder contains: clock_structures.dm: Structures and effects clock_unsorted.dm: Anything else with no place to be + clockcult defines are in __DEFINES/clockcult.dm */ /////////// @@ -183,7 +183,7 @@ This file's folder contains: var/list/servants_of_ratvar = list() //The Enlightened servants of Ratvar var/required_escapees = 0 //How many servants need to escape, if applicable var/required_silicon_converts = 0 //How many robotic lifeforms need to be converted, if applicable - var/clockwork_objective = "gateway" //The objective that the servants must fulfill + var/clockwork_objective = CLOCKCULT_GATEWAY //The objective that the servants must fulfill var/clockwork_explanation = "Construct a Gateway to the Celestial Derelict and free Ratvar." //The description of the current objective /datum/game_mode/clockwork_cult @@ -237,20 +237,20 @@ This file's folder contains: return 1 /datum/game_mode/clockwork_cult/proc/forge_clock_objectives() //Determine what objective that Ratvar's servants will fulfill - var/list/possible_objectives = list("escape", "gateway") + var/list/possible_objectives = list(CLOCKCULT_ESCAPE, CLOCKCULT_GATEWAY) var/silicons_possible = FALSE for(var/mob/living/silicon/S in living_mob_list) silicons_possible = TRUE if(silicons_possible) - possible_objectives += "silicons" + possible_objectives += CLOCKCULT_SILICONS clockwork_objective = pick(possible_objectives) switch(clockwork_objective) - if("escape") + if(CLOCKCULT_ESCAPE) required_escapees = round(max(1, roundstart_player_count / 3)) //33% of the player count must be cultists clockwork_explanation = "Ensure that [required_escapees] servants of Ratvar escape from [station_name()]." - if("gateway") + if(CLOCKCULT_GATEWAY) clockwork_explanation = "Construct a Gateway to the Celestial Derelict and free Ratvar." - if("silicons") + if(CLOCKCULT_SILICONS) clockwork_explanation = "Ensure that all active silicon-based lifeforms on [station_name()] are servants of Ratvar by the end of the shift." return 1 @@ -291,16 +291,16 @@ This file's folder contains: /datum/game_mode/clockwork_cult/proc/check_clockwork_victory() switch(clockwork_objective) - if("escape") + if(CLOCKCULT_ESCAPE) var/surviving_servants = 0 for(var/datum/mind/M in servants_of_ratvar) if(M.current && M.current.stat != DEAD && (M.current.onCentcom() || M.current.onSyndieBase())) surviving_servants++ - clockwork_explanation = "Ensure that [required_escapees] servant(s) of Ratvar escape from [station_name()]. [surviving_servants] managed to escape!" + clockwork_explanation = "Ensure that [required_escapees] servant(s) of Ratvar escape from [station_name()].
[surviving_servants] managed to escape!" if(surviving_servants >= required_escapees) return TRUE return FALSE - if("silicons") + if(CLOCKCULT_SILICONS) var/total_silicons = 0 var/valid_silicons = 0 var/successful = TRUE @@ -314,10 +314,10 @@ This file's folder contains: valid_silicons++ if(valid_silicons < total_silicons) successful = FALSE - clockwork_explanation = "Ensure that all active silicon-based lifeforms on [station_name()] are servants of Ratvar by the end of the shift. \ + clockwork_explanation = "Ensure that all active silicon-based lifeforms on [station_name()] are servants of Ratvar by the end of the shift.
\ [valid_silicons]/[total_silicons] silicons were killed or converted!" return successful - if("gateway") + if(CLOCKCULT_GATEWAY) return ratvar_awakens return FALSE //This shouldn't ever be reached, but just in case it is @@ -334,7 +334,7 @@ This file's folder contains: feedback_set_details("round_end_result", "win - servants completed their objective ([clockwork_objective])") else var/half_victory = FALSE - if(clockwork_objective == "gateway") + if(clockwork_objective == CLOCKCULT_GATEWAY) var/obj/structure/clockwork/massive/celestial_gateway/G = locate() in all_clockwork_objects if(G) half_victory = TRUE diff --git a/code/game/gamemodes/clock_cult/clock_ratvar.dm b/code/game/gamemodes/clock_cult/clock_ratvar.dm index 23481a508a9..64ef6c515cd 100644 --- a/code/game/gamemodes/clock_cult/clock_ratvar.dm +++ b/code/game/gamemodes/clock_cult/clock_ratvar.dm @@ -13,6 +13,9 @@ poi_list -= src return ..() +/obj/structure/clockwork/massive/singularity_pull(S, current_size) + return + /obj/structure/clockwork/massive/celestial_gateway //The gateway to Reebe, from which Ratvar emerges name = "Gateway to the Celestial Derelict" desc = "A massive, thrumming rip in spacetime." @@ -29,6 +32,7 @@ var/first_sound_played = FALSE var/second_sound_played = FALSE var/third_sound_played = FALSE + var/ratvar_portal = TRUE //if the gateway actually summons ratvar or just produces a hugeass conversion burst var/obj/effect/clockwork/overlay/gateway_glow/glow var/obj/effect/countdown/clockworkgate/countdown @@ -120,23 +124,48 @@ sleep(125) make_glow() animate(glow, transform = matrix() * 3, alpha = 0, time = 5) - sleep(5) - new/obj/structure/clockwork/massive/ratvar(get_turf(src)) - qdel(src) + var/turf/startpoint = get_turf(src) + sleep(3) + QDEL_IN(src, 3) + clockwork_gateway_activated = TRUE + if(ratvar_portal) + new/obj/structure/clockwork/massive/ratvar(startpoint) + else + world << "\"[text2ratvar("Behold")]!\"\n\"[text2ratvar("See Engine's mercy")]!\"\n\ + \"[text2ratvar("Observe Engine's design skills")]!\"\n\"[text2ratvar("Behold Engine's light")]!!\"\n\ + \"[text2ratvar("Gaze upon Engine's power")]!\"" + world << 'sound/magic/clockwork/invoke_general.ogg' + var/x0 = startpoint.x + var/y0 = startpoint.y + for(var/I in spiral_range_turfs(255, startpoint)) + var/turf/T = I + if(!T) + continue + var/dist = cheap_hypotenuse(T.x, T.y, x0, y0) + if(dist < 60) + dist = TRUE + else + dist = FALSE + T.ratvar_act(dist) + CHECK_TICK + for(var/I in all_clockwork_mobs) + var/mob/M = I + if(M.stat == CONSCIOUS) + clockwork_say(M, text2ratvar(pick("Purge all untruths and honor Engine!", "All glory to Engine's light!", "Engine's power is unmatched!"))) /obj/structure/clockwork/massive/celestial_gateway/examine(mob/user) icon_state = "spatial_gateway" //cheat wildly by pretending to have an icon ..() icon_state = initial(icon_state) if(is_servant_of_ratvar(user) || isobserver(user)) - user << "Seconds until Ratvar's arrival: [get_arrival_text(TRUE)]" + user << "Seconds until [ratvar_portal ? "Ratvar's arrival":"Proselytization"]: [get_arrival_text(TRUE)]" switch(progress_in_seconds) if(-INFINITY to GATEWAY_REEBE_FOUND) user << "It's still opening." if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING) user << "It's reached the Celestial Derelict and is drawing power from it." if(GATEWAY_RATVAR_COMING to INFINITY) - user << "Ratvar is coming through the gateway!" + user << "[ratvar_portal ? "Ratvar is coming through the gateway":"The gateway is glowing with massed power"]!" else switch(progress_in_seconds) if(-INFINITY to GATEWAY_REEBE_FOUND) @@ -144,7 +173,7 @@ if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING) user << "It seems to be leading somewhere." if(GATEWAY_RATVAR_COMING to INFINITY) - user << "Something is coming through!" + user << "[ratvar_portal ? "Something is coming through":"It's glowing brightly"]!" /obj/effect/clockwork/overlay/gateway_glow //the actual appearance of the Gateway to the Celestial Derelict; an object so the edges of the gate can be clicked through. icon = 'icons/effects/96x96.dmi' @@ -170,11 +199,11 @@ /obj/structure/clockwork/massive/ratvar/New() ..() - ratvar_awakens = TRUE + ratvar_awakens++ for(var/obj/item/clockwork/ratvarian_spear/R in all_clockwork_objects) R.update_force() START_PROCESSING(SSobj, src) - world << "\"[text2ratvar("ONCE AGAIN MY LIGHT SHALL SHINE ACROSS THIS PATHETIC REALM")]!!\"" + world << "\"[text2ratvar("ONCE AGAIN MY LIGHT SHALL SHINE ACROSS THIS PATHETIC REALM")]!!\"" world << 'sound/effects/ratvar_reveal.ogg' var/image/alert_overlay = image('icons/effects/clockwork_effects.dmi', "ratvar_alert") var/area/A = get_area(src) @@ -183,7 +212,7 @@ /obj/structure/clockwork/massive/ratvar/Destroy() - ratvar_awakens = FALSE + ratvar_awakens-- for(var/obj/item/clockwork/ratvarian_spear/R in all_clockwork_objects) R.update_force() STOP_PROCESSING(SSobj, src) @@ -201,8 +230,9 @@ /obj/structure/clockwork/massive/ratvar/Bump(atom/A) - forceMove(get_turf(A)) - A.ratvar_act() + var/turf/T = get_turf(A) + forceMove(T) + T.ratvar_act() /obj/structure/clockwork/massive/ratvar/Process_Spacemove() @@ -212,8 +242,12 @@ /obj/structure/clockwork/massive/ratvar/process() if(clashing) //I'm a bit occupied right now, thanks return - for(var/atom/A in range(proselytize_range, src)) - A.ratvar_act() + for(var/I in circlerangeturfs(src, proselytize_range)) + var/turf/T = I + T.ratvar_act() + for(var/I in circleviewturfs(src, round(proselytize_range * 0.5))) + var/turf/T = I + T.ratvar_act(1) var/dir_to_step_in = pick(cardinal) if(!prey) for(var/obj/singularity/narsie/N in poi_list) diff --git a/code/game/gamemodes/clock_cult/clock_scripture.dm b/code/game/gamemodes/clock_cult/clock_scripture.dm index e447b813e77..6d59ad2de81 100644 --- a/code/game/gamemodes/clock_cult/clock_scripture.dm +++ b/code/game/gamemodes/clock_cult/clock_scripture.dm @@ -1226,7 +1226,7 @@ Judgement: 10 servants, 100 CV, and any existing AIs are converted or destroyed /datum/clockwork_scripture/ark_of_the_clockwork_justiciar //Ark of the Clockwork Justiciar: Creates a Gateway to the Celestial Derelict. - descname = "Summon Ratvar" + descname = "Win Condition" name = "Ark of the Clockwork Justiciar" desc = "Pulls from the power of all of Ratvar's servants and generals to construct a massive machine used to tear apart a rift in spacetime to the Celestial Derelict. This gateway will \ call forth Ratvar from his exile after some time." @@ -1236,55 +1236,83 @@ Judgement: 10 servants, 100 CV, and any existing AIs are converted or destroyed channel_time = 150 required_components = list("belligerent_eye" = 10, "vanguard_cogwheel" = 10, "guvax_capacitor" = 10, "replicant_alloy" = 10, "hierophant_ansible" = 10) consumed_components = list("belligerent_eye" = 10, "vanguard_cogwheel" = 10, "guvax_capacitor" = 10, "replicant_alloy" = 10, "hierophant_ansible" = 10) - invokers_required = 4 + invokers_required = 5 multiple_invokers_used = TRUE - usage_tip = "The gateway is completely vulnerable to attack during its five-minute duration. In addition to preventing shuttle departure, it will periodically give indication of its general \ - position to everyone on the station as well as being loud enough to be heard throughout the entire sector. Defend it with your life!" + usage_tip = "The gateway is completely vulnerable to attack during its five-minute duration. It will periodically give indication of its general position to everyone on the station \ + as well as being loud enough to be heard throughout the entire sector. Defend it with your life!" tier = SCRIPTURE_JUDGEMENT +/datum/clockwork_scripture/ark_of_the_clockwork_justiciar/New() + if(ticker && ticker.mode && ticker.mode.clockwork_objective != CLOCKCULT_GATEWAY) + invocations = list("ARMORER! FRIGHT! AMPERAGE! VANGUARD! I CALL UPON YOU!!", \ + "THIS STATION WILL BE A BEACON OF HOPE IN THE DARKNESS OF SPACE!!", \ + "HELP US MAKE THIS SHOW ENGINE'S GLORY!!") + ..() + /datum/clockwork_scripture/ark_of_the_clockwork_justiciar/check_special_requirements() - if(!slab.no_cost && ratvar_awakens) - invoker << "\"I am already here, idiot.\"" - return 0 - for(var/obj/structure/clockwork/massive/celestial_gateway/G in all_clockwork_objects) - var/area/gate_area = get_area(G) - invoker << "There is already a gateway at [gate_area.map_name]!" - return 0 - var/area/A = get_area(invoker) - if(!slab.no_cost && (invoker.z != ZLEVEL_STATION || istype(A, /area/shuttle))) - invoker << "You must be on the station to activate the Ark!" - return 0 - if(!slab.no_cost && ticker.mode.clockwork_objective != "gateway") - invoker << "As painful as it is, Ratvar's will is not to be freed!" - return 0 + if(!slab.no_cost) + if(ratvar_awakens) + invoker << "\"I am already here, idiot.\"" + return 0 + for(var/obj/structure/clockwork/massive/celestial_gateway/G in all_clockwork_objects) + var/area/gate_area = get_area(G) + invoker << "There is already a gateway at [gate_area.map_name]!" + return 0 + var/area/A = get_area(invoker) + var/turf/T = get_turf(invoker) + if(!T || T.z != ZLEVEL_STATION || istype(A, /area/shuttle)) + invoker << "You must be on the station to activate the Ark!" + return 0 + if(clockwork_gateway_activated) + if(ticker && ticker.mode && ticker.mode.clockwork_objective != CLOCKCULT_GATEWAY) + invoker << "\"Look upon his works. Is it not glorious?\"" + else + invoker << "Ratvar's recent banishment renders him too weak to be wrung forth from Reebe!" + return 0 return 1 /datum/clockwork_scripture/ark_of_the_clockwork_justiciar/scripture_effects() var/turf/T = get_turf(invoker) new/obj/effect/clockwork/general_marker/inathneq(T) - T.visible_message("\"[text2ratvar("Engine, come forth and show your servants your mercy!")]\"") + if(ticker && ticker.mode && ticker.mode.clockwork_objective == CLOCKCULT_GATEWAY) + T.visible_message("\"[text2ratvar("Engine, come forth and show your servants your mercy!")]\"") + else + T.visible_message("\"[text2ratvar("We will show all the mercy of Engine!")]\"") playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 30, 0) sleep(10) if(!check_special_requirements()) return 0 new/obj/effect/clockwork/general_marker/sevtug(T) - T.visible_message("\"[text2ratvar("Engine, come forth and show this station your decorating skills!")]\"") + if(ticker && ticker.mode && ticker.mode.clockwork_objective == CLOCKCULT_GATEWAY) + T.visible_message("\"[text2ratvar("Engine, come forth and show this station your decorating skills!")]\"") + else + T.visible_message("\"[text2ratvar("We will show all Engine's decorating skills.")]\"") playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 45, 0) sleep(10) if(!check_special_requirements()) return 0 new/obj/effect/clockwork/general_marker/nezbere(T) - T.visible_message("\"[text2ratvar("Engine, come forth and shine your light across this realm!")]\"") + if(ticker && ticker.mode && ticker.mode.clockwork_objective == CLOCKCULT_GATEWAY) + T.visible_message("\"[text2ratvar("Engine, come forth and shine your light across this realm!!")]\"") + else + T.visible_message("\"[text2ratvar("We will show all Engine's light!!")]\"") playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 60, 0) sleep(10) if(!check_special_requirements()) return 0 new/obj/effect/clockwork/general_marker/nzcrentr(T) - T.visible_message("\"[text2ratvar("Engine, come forth.")]\"") + if(ticker && ticker.mode && ticker.mode.clockwork_objective == CLOCKCULT_GATEWAY) + T.visible_message("\"[text2ratvar("Engine, come forth.")]\"") + else + T.visible_message("\"[text2ratvar("We will show all Engine's power!")]\"") playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 75, 0) sleep(10) if(check_special_requirements()) - new/obj/structure/clockwork/massive/celestial_gateway(T) + var/obj/structure/clockwork/massive/celestial_gateway/CG = new/obj/structure/clockwork/massive/celestial_gateway(T) + if(ticker && ticker.mode && ticker.mode.clockwork_objective != CLOCKCULT_GATEWAY) + CG.ratvar_portal = FALSE + hierophant_message("This newly constructed gateway will not free Ratvar, \ + and will instead simply proselytize and convert everything and everyone on the station.", TRUE) playsound(T, 'sound/magic/clockwork/invoke_general.ogg', 100, 0) var/list/open_turfs = list() for(var/turf/open/OT in orange(1, T)) diff --git a/code/game/gamemodes/clock_cult/clock_structures.dm b/code/game/gamemodes/clock_cult/clock_structures.dm index e0fc254e1ea..cddd9cb68af 100644 --- a/code/game/gamemodes/clock_cult/clock_structures.dm +++ b/code/game/gamemodes/clock_cult/clock_structures.dm @@ -653,6 +653,9 @@ /obj/effect/clockwork/overlay/ex_act() return FALSE +/obj/effect/clockwork/overlay/singularity_pull(S, current_size) + return + /obj/effect/clockwork/overlay/Destroy() if(linked) linked = null diff --git a/code/game/gamemodes/miniantags/morph/morph.dm b/code/game/gamemodes/miniantags/morph/morph.dm index 82e2a81be53..1d98760553f 100644 --- a/code/game/gamemodes/miniantags/morph/morph.dm +++ b/code/game/gamemodes/miniantags/morph/morph.dm @@ -212,7 +212,7 @@ if(!candidates.len) return NOT_ENOUGH_PLAYERS - var/mob/dead/selected = popleft(candidates) + var/mob/dead/selected = pick_n_take(candidates) var/datum/mind/player_mind = new /datum/mind(selected.key) player_mind.active = 1 diff --git a/code/game/gamemodes/miniantags/revenant/revenant.dm b/code/game/gamemodes/miniantags/revenant/revenant.dm index e903d7b1c4a..197fda73c5d 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant.dm @@ -60,6 +60,7 @@ var/list/drained_mobs = list() //Cannot harvest the same mob twice var/perfectsouls = 0 //How many perfect, regen-cap increasing souls the revenant has. //TODO, add objective for getting a perfect soul(s?) var/image/ghostimage = null //Visible to ghost with darkness off + var/generated_objectives_and_spells = FALSE /mob/living/simple_animal/revenant/New() ..() @@ -68,28 +69,31 @@ ghost_darkness_images |= ghostimage updateallghostimages() - spawn(5) - if(src.mind) - src.mind.remove_all_antag() - src.mind.wipe_memory() - src << 'sound/effects/ghost.ogg' - src << "
" - src << "You are a revenant." - src << "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant." - src << "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds." - src << "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable." - src << "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities." - src << "You do not remember anything of your past lives, nor will you remember anything about this one after your death." - src << "Be sure to read the wiki page at https://tgstation13.org/wiki/Revenant to learn more." - var/datum/objective/revenant/objective = new - objective.owner = src.mind - src.mind.objectives += objective - src << "Objective #1: [objective.explanation_text]" - var/datum/objective/revenantFluff/objective2 = new - objective2.owner = src.mind - src.mind.objectives += objective2 - src << "Objective #2: [objective2.explanation_text]" - ticker.mode.traitors |= src.mind //Necessary for announcing +/mob/living/simple_animal/revenant/Login() + ..() + src << "You are a revenant." + src << "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant." + src << "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds." + src << "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable." + src << "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities." + src << "You do not remember anything of your past lives, nor will you remember anything about this one after your death." + src << "Be sure to read the wiki page at https://tgstation13.org/wiki/Revenant to learn more." + if(!generated_objectives_and_spells) + generated_objectives_and_spells = TRUE + mind.remove_all_antag() + mind.wipe_memory() + src << 'sound/effects/ghost.ogg' + var/datum/objective/revenant/objective = new + objective.owner = mind + mind.objectives += objective + src << "Objective #1: [objective.explanation_text]" + var/datum/objective/revenantFluff/objective2 = new + objective2.owner = mind + mind.objectives += objective2 + src << "Objective #2: [objective2.explanation_text]" + mind.assigned_role = "revenant" + mind.special_role = "Revenant" + ticker.mode.traitors |= mind //Necessary for announcing AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(null)) AddSpell(new /obj/effect/proc_holder/spell/targeted/revenant_transmit(null)) AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/defile(null)) @@ -97,7 +101,6 @@ AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/blight(null)) AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction(null)) - //Life, Stat, Hud Updates, and Say /mob/living/simple_animal/revenant/Life() if(revealed && essence <= 0) diff --git a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm index f73ac040966..8bfb458ff5b 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm @@ -10,15 +10,15 @@ /datum/round_event/ghost_role/revenant - var/force_spawn + var/ignore_mobcheck = FALSE role_name = "revenant" -/datum/round_event/ghost_role/revenant/New(my_force_spawn = FALSE) +/datum/round_event/ghost_role/revenant/New(my_processing = TRUE, new_ignore_mobcheck = FALSE) ..() - force_spawn = my_force_spawn + ignore_mobcheck = new_ignore_mobcheck /datum/round_event/ghost_role/revenant/spawn_role() - if(!force_spawn) + if(!ignore_mobcheck) var/deadMobs = 0 for(var/mob/M in dead_mob_list) deadMobs++ @@ -26,14 +26,12 @@ message_admins("Event attempted to spawn a revenant, but there were only [deadMobs]/[REVENANT_SPAWN_THRESHOLD] dead mobs.") return WAITING_FOR_SOMETHING - var/list/mob/dead/observer/candidates = get_candidates("revenant", null, ROLE_REVENANT) + var/list/candidates = get_candidates("revenant", null, ROLE_REVENANT) if(!candidates.len) return NOT_ENOUGH_PLAYERS - var/mob/dead/observer/selected = popleft(candidates) + var/mob/dead/observer/selected = pick_n_take(candidates) - var/datum/mind/player_mind = new /datum/mind(selected.key) - player_mind.active = 1 var/list/spawn_locs = list() for(var/obj/effect/landmark/L in landmarks_list) @@ -48,16 +46,13 @@ if("carpspawn") spawn_locs += L.loc if(!spawn_locs) //If we can't find either, just spawn the revenant at the player's location - spawn_locs += get_turf(player_mind.current) + spawn_locs += get_turf(selected) if(!spawn_locs) //If we can't find THAT, then just give up and cry return MAP_ERROR var/mob/living/simple_animal/revenant/revvie = new /mob/living/simple_animal/revenant/(pick(spawn_locs)) - player_mind.transfer_to(revvie) - player_mind.assigned_role = "revenant" - player_mind.special_role = "Revenant" - ticker.mode.traitors |= player_mind - message_admins("[player_mind.key] has been made into a revenant by an event.") - log_game("[player_mind.key] was spawned as a revenant by an event.") + revvie.key = selected.key + message_admins("[revvie.key] has been made into a revenant by an event.") + log_game("[revvie.key] was spawned as a revenant by an event.") spawned_mobs += revvie return SUCCESSFUL_SPAWN diff --git a/code/game/gamemodes/miniantags/slaughter/slaughterevent.dm b/code/game/gamemodes/miniantags/slaughter/slaughterevent.dm index 569216b7411..1d560ed975a 100644 --- a/code/game/gamemodes/miniantags/slaughter/slaughterevent.dm +++ b/code/game/gamemodes/miniantags/slaughter/slaughterevent.dm @@ -17,7 +17,7 @@ if(!candidates.len) return NOT_ENOUGH_PLAYERS - var/mob/dead/selected = popleft(candidates) + var/mob/dead/selected = pick_n_take(candidates) var/datum/mind/player_mind = new /datum/mind(selected.key) player_mind.active = 1 diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index 3f6bd166d70..577c4cd0b2f 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -89,14 +89,6 @@ open_machine() ..(severity) -/obj/machinery/sleeper/blob_act(obj/effect/blob/B) - if(prob(75)) - var/turf/T = get_turf(src) - for(var/atom/movable/A in src) - A.forceMove(T) - A.blob_act(B) - qdel(src) - /obj/machinery/sleeper/MouseDrop_T(mob/target, mob/user) if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) return diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 1a786e2591b..7c935e85a5d 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -106,6 +106,10 @@ ..() +/obj/machinery/camera/blob_act(obj/effect/blob/B) + if(B && B.loc == loc) + take_damage(health, BRUTE, 0) + /obj/machinery/camera/ex_act(severity, target) if(src.invuln) return diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm index 45b62caf01d..866daf1fbe1 100644 --- a/code/game/machinery/computer/computer.dm +++ b/code/game/machinery/computer/computer.dm @@ -55,7 +55,7 @@ take_damage(rand(10,30), BRUTE, 0) /obj/machinery/computer/ratvar_act() - if(!clockwork && prob(20)) + if(!clockwork) clockwork = TRUE icon_screen = "ratvar[rand(1, 4)]" icon_keyboard = "ratvar_key[rand(1, 6)]" diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 276380f9e27..224d5e23110 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -156,12 +156,11 @@ var/list/airlock_overlays = list() qdel(src) /obj/machinery/door/airlock/ratvar_act() //Airlocks become pinion airlocks that only allow servants - if(prob(20)) - if(glass) - new/obj/machinery/door/airlock/clockwork/brass(get_turf(src)) - else - new/obj/machinery/door/airlock/clockwork(get_turf(src)) - qdel(src) + if(glass) + new/obj/machinery/door/airlock/clockwork/brass(get_turf(src)) + else + new/obj/machinery/door/airlock/clockwork(get_turf(src)) + qdel(src) /obj/machinery/door/airlock/Destroy() qdel(wires) diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm index 769d08cb838..8ad89e2f465 100644 --- a/code/game/machinery/doors/airlock_types.dm +++ b/code/game/machinery/doors/airlock_types.dm @@ -352,13 +352,6 @@ /obj/machinery/door/airlock/cult/narsie_act() return -/obj/machinery/door/airlock/cult/ratvar_act() - ..() - if(src) - var/previouscolor = color - color = "#FAE48C" - animate(src, color = previouscolor, time = 8) - /obj/machinery/door/airlock/cult/friendly friendly = TRUE diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index e3705a2df0d..351107b7ded 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -182,7 +182,7 @@ obj/machinery/door/proc/try_to_crowbar(obj/item/I, mob/user) /obj/machinery/door/blob_act(obj/effect/blob/B) - if(prob(40)) + if(prob(60)) qdel(src) /obj/machinery/door/emp_act(severity) diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 2babb9a44c1..a85e3bd2bd6 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -196,9 +196,8 @@ color = "#7D1919" /obj/machinery/door/window/ratvar_act() - if(prob(20)) - new/obj/machinery/door/window/clockwork(src.loc, dir) - qdel(src) + new/obj/machinery/door/window/clockwork(src.loc, dir) + qdel(src) /obj/machinery/door/window/bullet_act(obj/item/projectile/P) . = ..() diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 80ffa357e49..f66d93c8a15 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -189,9 +189,7 @@ Class Procs: update_icon() /obj/machinery/blob_act(obj/effect/blob/B) - if(!density) - qdel(src) - if(prob(75)) + if(density && prob(75)) qdel(src) /obj/machinery/proc/auto_use_power() diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 2a4b37d4fd4..c7afad3cb6f 100644 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -8,6 +8,7 @@ idle_power_usage = 4 active_power_usage = 250 var/obj/item/weapon/charging = null + var/list/allowed_devices = list(/obj/item/weapon/gun/energy,/obj/item/weapon/melee/baton,/obj/item/ammo_box/magazine/recharge,/obj/item/weapon/computer_hardware/battery_module,/obj/item/laptop,/obj/item/modular_computer/) var/recharge_coeff = 1 /obj/machinery/recharger/New() @@ -36,7 +37,11 @@ playsound(loc, 'sound/items/Ratchet.ogg', 75, 1) return - if(istype(G, /obj/item/weapon/gun/energy) || istype(G, /obj/item/weapon/melee/baton) || istype(G, /obj/item/ammo_box/magazine/recharge)) + var/allowed = 0 + for (var/allowed_type in allowed_devices) + if (istype(G, allowed_type)) allowed = 1 + + if(allowed) if(anchored) if(charging || panel_open) return 1 @@ -127,6 +132,34 @@ use_power(200 * recharge_coeff) using_power = 1 + if(istype(charging, /obj/item/laptop)) + var/obj/item/laptop/L = charging + if(L.stored_computer.cpu.battery_module) + var/obj/item/weapon/computer_hardware/battery_module/B = L.stored_computer.cpu.battery_module + if(B.battery) + if(B.battery.charge < B.battery.maxcharge) + B.battery.give(B.battery.chargerate * recharge_coeff) + use_power(200 * recharge_coeff) + using_power = 1 + + if(istype(charging, /obj/item/weapon/computer_hardware/battery_module)) + var/obj/item/weapon/computer_hardware/battery_module/B = charging + if(B.battery) + if(B.battery.charge < B.battery.maxcharge) + B.battery.give(B.battery.chargerate * recharge_coeff) + use_power(200 * recharge_coeff) + using_power = 1 + + if(istype(charging, /obj/item/modular_computer)) + var/obj/item/modular_computer/C = charging + if(C.battery_module) + var/obj/item/weapon/computer_hardware/battery_module/B = C.battery_module + if(B.battery) + if(B.battery.charge < B.battery.maxcharge) + B.battery.give(B.battery.chargerate * recharge_coeff) + use_power(200 * recharge_coeff) + using_power = 1 + update_icon(using_power) /obj/machinery/recharger/power_change() diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 442f35adf38..a9fc34690a5 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -118,7 +118,8 @@ var/global/image/fire_overlay = image("icon" = 'icons/effects/fire.dmi', "icon_s return ..() /obj/item/blob_act(obj/effect/blob/B) - qdel(src) + if(B && B.loc == loc) + qdel(src) /obj/item/ex_act(severity, target) if(severity == 1 || target == src) diff --git a/code/game/objects/items/devices/modificationkit.dm b/code/game/objects/items/devices/modificationkit.dm deleted file mode 100644 index cd25b38140a..00000000000 --- a/code/game/objects/items/devices/modificationkit.dm +++ /dev/null @@ -1,35 +0,0 @@ -/obj/item/modkit - name = "modification kit" - desc = "A one-use kit, which enables kinetic accelerators to retain their \ - charge when away from a bioelectric source, renders them immune to \ - interference with other accelerators, as well as allowing less \ - dextrous races to use the tool." - icon = 'icons/obj/objects.dmi' - icon_state = "modkit" - origin_tech = "programming=2;materials=2;magnets=4" - var/uses = 1 - -/obj/item/modkit/afterattack(obj/item/weapon/gun/energy/kinetic_accelerator/C, mob/user) - ..() - if(!uses) - qdel(src) - return - if(!istype(C)) - user << "This kit can only modify kinetic \ - accelerators!" - return ..() - // RIP the 'improved improved improved improved kinetic accelerator - if(C.holds_charge && C.unique_frequency) - user << "This kinetic accelerator already has \ - these upgrades." - return ..() - - user <<"You modify the [C], adjusting the trigger \ - guard and internal capacitor." - C.name = "improved [C.name]" - C.holds_charge = TRUE - C.unique_frequency = TRUE - C.trigger_guard = TRIGGER_GUARD_ALLOW_ALL - uses-- - if(!uses) - qdel(src) diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 95a6e9909f7..7b46cd30877 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -30,6 +30,7 @@ var/global/list/datum/stack_recipe/metal_recipes = list ( \ new/datum/stack_recipe("wall girders", /obj/structure/girder, 2, time = 40, one_per_turf = 1, on_floor = 1), \ null, \ new/datum/stack_recipe("computer frame", /obj/structure/frame/computer, 5, time = 25, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("modular console", /obj/machinery/modular_computer/console/buildable/, 10, time = 25, one_per_turf = 1, on_floor = 1), \ new/datum/stack_recipe("machine frame", /obj/structure/frame/machine, 5, time = 25, one_per_turf = 1, on_floor = 1), \ new/datum/stack_recipe("airlock assembly", /obj/structure/door_assembly, 4, time = 50, one_per_turf = 1, on_floor = 1), \ new/datum/stack_recipe("firelock frame", /obj/structure/firelock_frame, 3, time = 50, one_per_turf = 1, on_floor = 1), \ diff --git a/code/game/objects/items/weapons/dna_injector.dm b/code/game/objects/items/weapons/dna_injector.dm index d46f409f1c4..818bef45e37 100644 --- a/code/game/objects/items/weapons/dna_injector.dm +++ b/code/game/objects/items/weapons/dna_injector.dm @@ -152,7 +152,7 @@ add_mutations_static = list(CLOWNMUT) /obj/item/weapon/dnainjector/anticlumsy - name = "\improper DNA injector (Anti-Clumy)" + name = "\improper DNA injector (Anti-Clumsy)" desc = "Apply this for Security Clown." remove_mutations_static = list(CLOWNMUT) diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index 6008deb974f..3763799c961 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -13,7 +13,8 @@ bang(get_turf(M), M) for(var/obj/effect/blob/B in get_hear(8,flashbang_turf)) //Blob damage here - var/damage = round(40/(get_dist(B,get_turf(src))+1)) + var/distance = get_dist(B, get_turf(src)) + var/damage = round(100/(distance*distance)+1) B.take_damage(damage, BURN) qdel(src) diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm index 9441324e7b2..9216f54280d 100644 --- a/code/game/objects/items/weapons/storage/bags.dm +++ b/code/game/objects/items/weapons/storage/bags.dm @@ -108,8 +108,8 @@ name = "plant bag" icon = 'icons/obj/hydroponics/equipment.dmi' icon_state = "plantbag" - storage_slots = 50; //the number of plant pieces it can carry. - max_combined_w_class = 200 //Doesn't matter what this is, so long as it's more or equal to storage_slots * plants.w_class + storage_slots = 100; //the number of plant pieces it can carry. + max_combined_w_class = 100 //Doesn't matter what this is, so long as it's more or equal to storage_slots * plants.w_class max_w_class = 3 w_class = 1 can_hold = list(/obj/item/weapon/reagent_containers/food/snacks/grown,/obj/item/seeds,/obj/item/weapon/grown) diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index 783f978ee6c..d7cf7820c95 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -183,7 +183,10 @@ /obj/item/weapon/reagent_containers/pill, /obj/item/weapon/storage/pill_bottle, /obj/item/weapon/ore, - /obj/item/weapon/reagent_containers/food/drinks + /obj/item/weapon/reagent_containers/food/drinks, + /obj/item/organ/hivelord_core, + /obj/item/device/wormhole_jaunter + ) diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm index 09690214746..e1865d8afd3 100644 --- a/code/game/objects/items/weapons/storage/boxes.dm +++ b/code/game/objects/items/weapons/storage/boxes.dm @@ -70,7 +70,7 @@ /obj/item/weapon/storage/box/survival_mining/New() ..() - new /obj/item/clothing/mask/breath(src) + new /obj/item/clothing/mask/gas/explorer(src) new /obj/item/weapon/tank/internals/emergency_oxygen/engi(src) new /obj/item/weapon/crowbar/red(src) new /obj/item/weapon/reagent_containers/hypospray/medipen(src) diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm index b65525eb552..9e5801e1667 100644 --- a/code/game/objects/items/weapons/tanks/tanks.dm +++ b/code/game/objects/items/weapons/tanks/tanks.dm @@ -92,12 +92,12 @@ user << "It feels [descriptive]." /obj/item/weapon/tank/blob_act(obj/effect/blob/B) - if(prob(50)) - var/turf/location = src.loc - if (!( istype(location, /turf) )) + if(B && B.loc == loc) + var/turf/location = get_turf(src) + if(!location) qdel(src) - if(src.air_contents) + if(air_contents) location.assume_air(air_contents) qdel(src) diff --git a/code/game/objects/items/weapons/tools.dm b/code/game/objects/items/weapons/tools.dm index c5c4aa37f63..d57c948c746 100644 --- a/code/game/objects/items/weapons/tools.dm +++ b/code/game/objects/items/weapons/tools.dm @@ -218,7 +218,6 @@ w_class = 2 - materials = list(MAT_METAL=70, MAT_GLASS=30) origin_tech = "engineering=1;plasmatech=1" var/welding = 0 //Whether or not the welding tool is off(0), on(1) or currently welding(2) @@ -332,8 +331,6 @@ if(welding) SetLuminosity(0) user.AddLuminosity(light_intensity) - else - user.AddLuminosity(-light_intensity) update_icon() @@ -396,9 +393,8 @@ obj/item/weapon/weldingtool/proc/switched_on(mob/user) //Switches the welder off obj/item/weapon/weldingtool/proc/switched_off(mob/user) welding = 0 - var/mob/Player = loc - if(ismob(loc)) //If player is holding the welder - Player.AddLuminosity(-light_intensity) + if(user == loc) //If player is holding the welder + user.AddLuminosity(-light_intensity) SetLuminosity(0) else SetLuminosity(0) diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm index 160b5485ff2..544613b54ea 100644 --- a/code/game/objects/items/weapons/twohanded.dm +++ b/code/game/objects/items/weapons/twohanded.dm @@ -83,13 +83,6 @@ user.put_in_inactive_hand(O) return -/obj/item/weapon/twohanded/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0) - //Cannot equip wielded items. - if(wielded) - M << "Unwield the [name] first!" - return 0 - return ..() - /obj/item/weapon/twohanded/dropped(mob/user) ..() //handles unwielding a twohanded weapon when dropped as well as clearing up the offhand @@ -109,6 +102,11 @@ else //Trying to wield it wield(user) +/obj/item/weapon/twohanded/equip_to_best_slot(mob/M) + if(..()) + unwield(M) + return + ///////////OFFHAND/////////////// /obj/item/weapon/twohanded/offhand name = "offhand" @@ -131,7 +129,7 @@ return /obj/item/weapon/twohanded/required/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0) - if(wielded) + if(wielded && !slot_flags) M << "\The [src] is too cumbersome to carry with anything but your hands!" return 0 return ..() @@ -143,9 +141,17 @@ if(H != null) user << "\The [src] is too cumbersome to carry in one hand!" return - wield(user) + if(src.loc != user) + wield(user) ..() +/obj/item/weapon/twohanded/required/equipped(mob/user, slot) + ..() + if(slot == slot_l_hand || slot == slot_r_hand) + wield(user) + else + unwield(user) + /obj/item/weapon/twohanded/ diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 7b7e4bcfc6e..2c9ebb0f9ba 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -16,9 +16,7 @@ cameranet.updateVisibility(src) /obj/structure/blob_act(obj/effect/blob/B) - if(!density) - qdel(src) - if(prob(50)) + if(density && prob(50)) qdel(src) /obj/structure/Destroy() diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 398474484ea..72889a0a672 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -208,10 +208,6 @@ visible_message("[user] destroys \the [src].") qdel(src) -/obj/structure/closet/blob_act(obj/effect/blob/B) - if(prob(75)) - qdel(src) - /obj/structure/closet/attackby(obj/item/weapon/W, mob/user, params) if(user in src) return diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index 0dd4998ac3e..acbb40fd495 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -49,10 +49,7 @@ showpiece = null /obj/structure/displaycase/blob_act(obj/effect/blob/B) - if (prob(75)) - new /obj/item/weapon/shard( src.loc ) - dump() - qdel(src) + take_damage(30) /obj/structure/displaycase/hitby(atom/movable/AM) ..() diff --git a/code/game/objects/structures/fireaxe.dm b/code/game/objects/structures/fireaxe.dm index a7b1172c922..91c748c0e0c 100644 --- a/code/game/objects/structures/fireaxe.dm +++ b/code/game/objects/structures/fireaxe.dm @@ -84,7 +84,7 @@ /obj/structure/fireaxecabinet/blob_act(obj/effect/blob/B) if(fireaxe) - fireaxe.loc = src.loc + fireaxe.forceMove(loc) qdel(src) /obj/structure/fireaxecabinet/attack_hand(mob/user) diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index a3f753c6de1..49015bdfc2e 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -25,15 +25,15 @@ take_damage(rand(5,10), BRUTE, 0) /obj/structure/grille/ratvar_act() - if(prob(20)) - if(destroyed) - new /obj/structure/grille/ratvar/broken(src.loc) - else - new /obj/structure/grille/ratvar(src.loc) - qdel(src) + if(destroyed) + new /obj/structure/grille/ratvar/broken(src.loc) + else + new /obj/structure/grille/ratvar(src.loc) + qdel(src) /obj/structure/grille/blob_act(obj/effect/blob/B) - qdel(src) + if(!destroyed) + Break() /obj/structure/grille/Bumped(atom/user) if(ismob(user)) diff --git a/code/game/objects/structures/signs.dm b/code/game/objects/structures/signs.dm index 793e57bb27c..d46d2b35b74 100644 --- a/code/game/objects/structures/signs.dm +++ b/code/game/objects/structures/signs.dm @@ -15,7 +15,6 @@ /obj/structure/sign/blob_act(obj/effect/blob/B) qdel(src) - return /obj/structure/sign/attackby(obj/item/O, mob/user, params) if(istype(O, /obj/item/weapon/wrench)) diff --git a/code/game/objects/structures/table_frames.dm b/code/game/objects/structures/table_frames.dm index 1d5fab163d8..5f165715ed1 100644 --- a/code/game/objects/structures/table_frames.dm +++ b/code/game/objects/structures/table_frames.dm @@ -79,9 +79,8 @@ qdel(src) /obj/structure/table_frame/ratvar_act() - if(prob(20)) - new /obj/structure/table_frame/brass(src.loc) - qdel(src) + new /obj/structure/table_frame/brass(src.loc) + qdel(src) /* * Wooden Frames diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 4d8bb9c3702..d1ff3fbe443 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -54,16 +54,14 @@ take_damage(rand(40,80), BRUTE, 0) /obj/structure/table/blob_act(obj/effect/blob/B) - if(prob(75)) - qdel(src) + take_damage(rand(75,150), BRUTE, 0) /obj/structure/table/narsie_act() if(prob(20)) new /obj/structure/table/wood(src.loc) /obj/structure/table/ratvar_act() - if(prob(20)) - new /obj/structure/table/reinforced/brass(src.loc) + new /obj/structure/table/reinforced/brass(src.loc) /obj/structure/table/mech_melee_attack(obj/mecha/M) playsound(src.loc, 'sound/weapons/punch4.ogg', 50, 1) @@ -463,10 +461,7 @@ take_damage(rand(5,25), BRUTE, 0) /obj/structure/rack/blob_act(obj/effect/blob/B) - if(prob(75)) - qdel(src) - else - rack_destroy() + rack_destroy() /obj/structure/rack/mech_melee_attack(obj/mecha/M) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 8c89c8f8a0b..085a4444ddd 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -66,7 +66,7 @@ take_damage(rand(25,75), BRUTE, 0) /obj/structure/window/blob_act(obj/effect/blob/B) - shatter() + take_damage(rand(75,150), BRUTE, 0) /obj/structure/window/narsie_act() color = NARSIE_WINDOW_COLOUR @@ -74,12 +74,11 @@ shard.color = NARSIE_WINDOW_COLOUR /obj/structure/window/ratvar_act() - if(prob(20)) - if(!fulltile) - new/obj/structure/window/reinforced/clockwork(get_turf(src), dir) - else - new/obj/structure/window/reinforced/clockwork/fulltile(get_turf(src)) - qdel(src) + if(!fulltile) + new/obj/structure/window/reinforced/clockwork(get_turf(src), dir) + else + new/obj/structure/window/reinforced/clockwork/fulltile(get_turf(src)) + qdel(src) /obj/structure/window/singularity_pull(S, current_size) if(current_size >= STAGE_FIVE) diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm index b41861c5448..3766ed75826 100644 --- a/code/game/turfs/simulated/floor.dm +++ b/code/game/turfs/simulated/floor.dm @@ -170,9 +170,14 @@ var/list/icons_to_ignore_at_floor_init = list("damaged1","damaged2","damaged3"," if(prob(20)) ChangeTurf(/turf/open/floor/engine/cult) -/turf/open/floor/ratvar_act() - if(prob(20)) +/turf/open/floor/ratvar_act(force) + var/converted = (prob(40) || force) + if(converted) ChangeTurf(/turf/open/floor/clockwork) + for(var/I in src) + var/atom/A = I + if(ismob(A) || converted) + A.ratvar_act() /turf/open/floor/initialize() ..() diff --git a/code/game/turfs/simulated/floor/misc_floor.dm b/code/game/turfs/simulated/floor/misc_floor.dm index aa00c9eb8a9..86f5d417620 100644 --- a/code/game/turfs/simulated/floor/misc_floor.dm +++ b/code/game/turfs/simulated/floor/misc_floor.dm @@ -187,7 +187,8 @@ return ..() /turf/open/floor/clockwork/ratvar_act() - return 0 + for(var/mob/M in src) + M.ratvar_act() /turf/open/floor/clockwork/narsie_act() ..() diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm index 449eda6d469..9b5f0026e1a 100644 --- a/code/game/turfs/simulated/walls.dm +++ b/code/game/turfs/simulated/walls.dm @@ -256,9 +256,14 @@ if(prob(20)) ChangeTurf(/turf/closed/wall/mineral/cult) -/turf/closed/wall/ratvar_act() - if(prob(20)) +/turf/closed/wall/ratvar_act(force) + var/converted = (prob(40) || force) + if(converted) ChangeTurf(/turf/closed/wall/clockwork) + for(var/I in src) + var/atom/A = I + if(ismob(A) || converted) + A.ratvar_act() /turf/closed/wall/storage_contents_dump_act(obj/item/weapon/storage/src_object, mob/user) return 0 diff --git a/code/game/turfs/simulated/walls_misc.dm b/code/game/turfs/simulated/walls_misc.dm index 484f2fdc523..d407cbfcf1c 100644 --- a/code/game/turfs/simulated/walls_misc.dm +++ b/code/game/turfs/simulated/walls_misc.dm @@ -94,7 +94,8 @@ return ..() /turf/closed/wall/clockwork/ratvar_act() - return 0 + for(var/mob/M in src) + M.ratvar_act() /turf/closed/wall/clockwork/narsie_act() ..() diff --git a/code/game/turfs/space/transit.dm b/code/game/turfs/space/transit.dm index 40a4ab0f161..1912139d8bf 100644 --- a/code/game/turfs/space/transit.dm +++ b/code/game/turfs/space/transit.dm @@ -22,7 +22,7 @@ throw_atom(AM) /turf/open/space/transit/proc/throw_atom(atom/movable/AM) - if(!AM) + if(!AM || istype(AM, /obj/docking_port)) return var/max = world.maxx-TRANSITIONEDGE var/min = 1+TRANSITIONEDGE diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index cf46254a2f5..020a7d27e14 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -284,6 +284,9 @@ if(A.level >= affecting_level) A.ex_act(severity, target) +/turf/ratvar_act() + for(var/mob/M in src) + M.ratvar_act() /turf/proc/add_blueprints(atom/movable/AM) var/image/I = new diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 8adf647f56b..9673701d8b6 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -161,6 +161,9 @@ var/list/admin_ranks = list() //list of all admin_rank datums C.holder = null admins.Cut() load_admin_ranks() + //Clear profile access + for(var/A in world.GetConfig("admin")) + world.SetConfig("APP/admin", A, null) var/list/rank_names = list() for(var/datum/admin_rank/R in admin_ranks) @@ -189,6 +192,8 @@ var/list/admin_ranks = list() //list of all admin_rank datums var/datum/admins/D = new(rank_names[rank], ckey) //create the admin datum and store it for later use if(!D) continue //will occur if an invalid rank is provided + if(D.rank.rights & R_DEBUG) //grant profile access + world.SetConfig("APP/admin", ckey, "role=admin") D.associate(directory[ckey]) //find the client for a ckey if they are connected and associate them with the new admin datum else establish_db_connection() @@ -214,6 +219,8 @@ var/list/admin_ranks = list() //list of all admin_rank datums var/datum/admins/D = new(rank_names[rank], ckey) //create the admin datum and store it for later use if(!D) continue //will occur if an invalid rank is provided + if(D.rank.rights & R_DEBUG) //grant profile access + world.SetConfig("APP/admin", ckey, "role=admin") D.associate(directory[ckey]) //find the client for a ckey if they are connected and associate them with the new admin datum #ifdef TESTING @@ -369,4 +376,4 @@ var/list/admin_ranks = list() //list of all admin_rank datums var/sql_admin_rank = sanitizeSQL(newrank) var/DBQuery/query_update = dbcon.NewQuery("UPDATE [format_table_name("player")] SET lastadminrank = '[sql_admin_rank]' WHERE ckey = '[sql_ckey]'") - query_update.Execute() \ No newline at end of file + query_update.Execute() diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 5b3d1869eff..4a06db2e93a 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -92,10 +92,10 @@ message_admins("[key_name_admin(usr)] tried to create a death squad. Unfortunately, there were not enough candidates available.") log_admin("[key_name(usr)] failed to create a death squad.") if("blob") - var/strength = input("Set Blob Strength (1=Weak, 2=Strong, 3=Full)","Set Strength",1) as num - message_admins("[key_name(usr)] spawned a blob with strength [strength].") - log_admin("[key_name(usr)] spawned a blob with strength [strength].") - new/datum/round_event/blob(strength) + var/strength = input("Set Blob Resource Gain Rate","Set Resource Rate",1) as num + message_admins("[key_name(usr)] spawned a blob with base resource gain [strength].") + log_admin("[key_name(usr)] spawned a blob with base resource gain [strength].") + new/datum/round_event/ghost_role/blob(TRUE, strength) if("gangs") if(src.makeGangsters()) message_admins("[key_name(usr)] created gangs.") diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index f2ee15cd98e..b7dc53adae1 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -261,7 +261,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that log_admin("[key_name(src)] has blobized [M.key].") var/mob/living/carbon/human/H = M spawn(0) - var/mob/camera/blob/B = H.become_overmind() + var/mob/camera/blob/B = H.become_overmind(FALSE) B.place_blob_core(B.base_point_rate, -1) //place them wherever they are else diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm index d5d7a558645..c4034ab2e26 100644 --- a/code/modules/admin/verbs/one_click_antag.dm +++ b/code/modules/admin/verbs/one_click_antag.dm @@ -134,7 +134,7 @@ var/list/mob/dead/observer/candidates = pollCandidates("Do you wish to be considered for the position of a Wizard Foundation 'diplomat'?", "wizard", null) - var/mob/dead/observer/selected = popleft(candidates) + var/mob/dead/observer/selected = pick_n_take(candidates) var/mob/living/carbon/human/new_character = makeBody(selected) new_character.mind.make_Wizard() @@ -552,5 +552,5 @@ return 1 /datum/admins/proc/makeRevenant() - new /datum/round_event/ghost_role/revenant + new /datum/round_event/ghost_role/revenant(TRUE, TRUE) return 1 diff --git a/code/modules/cargo/exports/seeds.dm b/code/modules/cargo/exports/seeds.dm index 949f1bc0326..2f8da4d3537 100644 --- a/code/modules/cargo/exports/seeds.dm +++ b/code/modules/cargo/exports/seeds.dm @@ -31,6 +31,8 @@ if(!cost) return 0 - var/potDiff = max(S.potency - discoveredPlants[S.type], 0) - - return round(..() * potDiff) \ No newline at end of file + var/potDiff = (S.potency - discoveredPlants[S.type]) + if(potDiff < 1) + return round(S.rarity * S.potency / 10) + else + return round(..() * potDiff) diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm index fd54eaefc8d..8a1bf3eca03 100644 --- a/code/modules/client/asset_cache.dm +++ b/code/modules/client/asset_cache.dm @@ -179,6 +179,36 @@ You can set verify to TRUE if you want send() to sleep until the client has the "tgui.js" = 'tgui/assets/tgui.js' ) +/datum/asset/simple/headers + assets = list( + "alarm_green.gif" = 'icons/program_icons/alarm_green.gif', + "alarm_red.gif" = 'icons/program_icons/alarm_red.gif', + "batt_5.gif" = 'icons/program_icons/batt_5.gif', + "batt_20.gif" = 'icons/program_icons/batt_20.gif', + "batt_40.gif" = 'icons/program_icons/batt_40.gif', + "batt_60.gif" = 'icons/program_icons/batt_60.gif', + "batt_80.gif" = 'icons/program_icons/batt_80.gif', + "batt_100.gif" = 'icons/program_icons/batt_100.gif', + "charging.gif" = 'icons/program_icons/charging.gif', + "downloader_finished.gif" = 'icons/program_icons/downloader_finished.gif', + "downloader_running.gif" = 'icons/program_icons/downloader_running.gif', + "ntnrc_idle.gif" = 'icons/program_icons/ntnrc_idle.gif', + "ntnrc_new.gif" = 'icons/program_icons/ntnrc_new.gif', + "power_norm.gif" = 'icons/program_icons/power_norm.gif', + "power_warn.gif" = 'icons/program_icons/power_warn.gif', + "sig_high.gif" = 'icons/program_icons/sig_high.gif', + "sig_low.gif" = 'icons/program_icons/sig_low.gif', + "sig_lan.gif" = 'icons/program_icons/sig_lan.gif', + "sig_none.gif" = 'icons/program_icons/sig_none.gif', + ) + + + + + + + + /datum/asset/simple/pda assets = list( "pda_atmos.png" = 'icons/pda_icons/pda_atmos.png', diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index 9f0180ea777..a30537fab72 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -77,7 +77,12 @@ chameleon_blacklist += target.type var/list/temp_list = typesof(chameleon_type) for(var/V in temp_list - (chameleon_blacklist)) - chameleon_list += V + if(istype(V, /obj/item)) + var/obj/item/I = V + if(initial(I.flags) & ABSTRACT) + continue + else + chameleon_list += I /datum/action/item_action/chameleon/change/proc/select_look(mob/user) var/list/item_names = list() @@ -86,7 +91,7 @@ var/obj/item/I = U item_names += initial(I.name) var/picked_name - picked_name = input("Select [chameleon_name] to change it to", "Chameleon [chameleon_name]", picked_name) in item_names + picked_name = input("Select [chameleon_name] to change into", "Chameleon [chameleon_name]", picked_name) in item_names if(!picked_name) return for(var/V in chameleon_list) diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index fbcb89bcff4..29a12bb8700 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -494,6 +494,32 @@ armor = list(melee = 45, bullet = 25, laser = 30, energy = 10, bomb = 25, bio = 100, rad = 50) helmettype = /obj/item/clothing/head/helmet/space/hardsuit/security/hos + //Clown +/obj/item/clothing/head/helmet/space/hardsuit/clown + name = "cosmohonk hardsuit helmet" + desc = "A special helmet designed for work in a hazardous, low-humor environment. Has radiation shielding." + icon_state = "hardsuit0-clown" + item_state = "hardsuit0-clown" + armor = list(melee = 30, bullet = 5, laser = 10, energy = 5, bomb = 10, bio = 100, rad = 75) + item_color = "clown" + +/obj/item/clothing/suit/space/hardsuit/clown + name = "cosmohonk hardsuit" + desc = "A special suit that protects against hazardous, low humor environments. Has radiation shielding. Only a true clown can wear it." + icon_state = "hardsuit-clown" + item_state = "clown_hardsuit" + armor = list(melee = 30, bullet = 5, laser = 10, energy = 5, bomb = 10, bio = 100, rad = 75) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/clown + pockets = /obj/item/weapon/storage/internal/pocket + +/obj/item/clothing/suit/space/hardsuit/clown/mob_can_equip(mob/M, slot) + if(!..() || !ishuman(M)) + return FALSE + var/mob/living/carbon/human/H = M + if(H.mind.assigned_role == "Clown") + return TRUE + else + return FALSE /////////////SHIELDED////////////////////////////////// diff --git a/code/modules/events/abductor.dm b/code/modules/events/abductor.dm index 19ccda1472b..7b115409138 100644 --- a/code/modules/events/abductor.dm +++ b/code/modules/events/abductor.dm @@ -28,8 +28,8 @@ else temp = new - var/agent_mind = popleft(candidates) - var/scientist_mind = popleft(candidates) + var/agent_mind = pick_n_take(candidates) + var/scientist_mind = pick_n_take(candidates) var/mob/living/carbon/human/agent = makeBody(agent_mind) var/mob/living/carbon/human/scientist = makeBody(scientist_mind) diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm index ff6b63b7c8a..92f7e86f283 100644 --- a/code/modules/events/alien_infestation.dm +++ b/code/modules/events/alien_infestation.dm @@ -57,7 +57,7 @@ while(spawncount > 0 && vents.len && candidates.len) var/obj/vent = pick_n_take(vents) - var/client/C = popleft(candidates) + var/client/C = pick_n_take(candidates) var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) new_xeno.key = C.key diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm index ab361a56b39..1f488dc18c3 100644 --- a/code/modules/events/blob.dm +++ b/code/modules/events/blob.dm @@ -1,6 +1,6 @@ /datum/round_event_control/blob name = "Blob" - typepath = /datum/round_event/blob + typepath = /datum/round_event/ghost_role/blob weight = 5 max_occurrences = 1 @@ -9,22 +9,29 @@ gamemode_blacklist = list("blob") //Just in case a blob survives that long -/datum/round_event/blob +/datum/round_event/ghost_role/blob announceWhen = 12 - endWhen = 120 var/new_rate = 2 -/datum/round_event/blob/New(var/strength) +/datum/round_event/ghost_role/blob/New(my_processing = TRUE, set_point_rate) ..() - if(strength) - new_rate = strength + if(set_point_rate) + new_rate = set_point_rate -/datum/round_event/blob/announce() +/datum/round_event/ghost_role/blob/announce() priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/AI/outbreak5.ogg') -/datum/round_event/blob/start() - var/turf/T = pick(blobstart) - if(!T) - return kill() - new/obj/effect/blob/core(T, null, new_rate) +/datum/round_event/ghost_role/blob/spawn_role() + if(!blobstart.len) + return MAP_ERROR + var/list/candidates = get_candidates("blob", null, ROLE_BLOB) + if(!candidates.len) + return NOT_ENOUGH_PLAYERS + var/mob/dead/observer/new_blob = pick(candidates) + var/obj/effect/blob/core/BC = new/obj/effect/blob/core(pick(blobstart), new_blob.client, new_rate) + BC.overmind.blob_points = min(20 + player_list.len, BC.overmind.max_blob_points) + spawned_mobs += BC.overmind + message_admins("[BC.overmind.key] has been made into a blob overmind by an event.") + log_game("[BC.overmind.key] was spawned as a blob overmind by an event.") + return SUCCESSFUL_SPAWN diff --git a/code/modules/events/devil.dm b/code/modules/events/devil.dm index 4f683a8f6f2..066599cf351 100644 --- a/code/modules/events/devil.dm +++ b/code/modules/events/devil.dm @@ -24,7 +24,7 @@ if(!candidates.len) return NOT_ENOUGH_PLAYERS - var/mob/dead/selected_candidate = popleft(candidates) + var/mob/dead/selected_candidate = pick_n_take(candidates) var/key = selected_candidate.key var/datum/mind/Mind = create_devil_mind(key) diff --git a/code/modules/events/operative.dm b/code/modules/events/operative.dm index 6a33688f659..1c026f81102 100644 --- a/code/modules/events/operative.dm +++ b/code/modules/events/operative.dm @@ -13,7 +13,7 @@ if(!candidates.len) return NOT_ENOUGH_PLAYERS - var/mob/dead/selected = popleft(candidates) + var/mob/dead/selected = pick_n_take(candidates) var/list/spawn_locs = list() for(var/obj/effect/landmark/L in landmarks_list) diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm index 5e59e0da172..4b6a96add86 100644 --- a/code/modules/jobs/access.dm +++ b/code/modules/jobs/access.dm @@ -64,6 +64,7 @@ /var/const/access_mineral_storeroom = 64 /var/const/access_minisat = 65 /var/const/access_weapons = 66 //Weapon authorization for secbots +/var/const/access_network = 67 //BEGIN CENTCOM ACCESS /*Should leave plenty of room if we need to add more access levels. @@ -237,7 +238,7 @@ access_hydroponics, access_library, access_lawyer, access_virology, access_cmo, access_qm, access_surgery, access_theatre, access_research, access_mining, access_mailsorting, access_weapons, access_heads_vault, access_mining_station, access_xenobiology, access_ce, access_hop, access_hos, access_RC_announce, - access_keycard_auth, access_tcomsat, access_gateway, access_mineral_storeroom, access_minisat) + access_keycard_auth, access_tcomsat, access_gateway, access_mineral_storeroom, access_minisat, access_network) /proc/get_all_centcom_access() return list(access_cent_general, access_cent_thunder, access_cent_specops, access_cent_medical, access_cent_living, access_cent_storage, access_cent_teleporter, access_cent_captain) @@ -267,7 +268,7 @@ if(3) //medbay return list(access_medical, access_genetics, access_morgue, access_chemistry, access_virology, access_surgery, access_cmo) if(4) //research - return list(access_research, access_tox, access_tox_storage, access_genetics, access_robotics, access_xenobiology, access_minisat, access_rd) + return list(access_research, access_tox, access_tox_storage, access_genetics, access_robotics, access_xenobiology, access_minisat, access_rd, access_network) if(5) //engineering and maintenance return list(access_construction, access_maint_tunnels, access_engine, access_engine_equip, access_external_airlocks, access_tech_storage, access_atmospherics, access_tcomsat, access_minisat, access_ce) if(6) //supply @@ -424,6 +425,8 @@ return "AI Satellite" if(access_weapons) return "Weapon Permit" + if(access_network) + return "Network Access" /proc/get_centcom_access_desc(A) switch(A) diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index 9a1822fad7e..4ef3a5dc086 100644 --- a/code/modules/jobs/job_types/assistant.dm +++ b/code/modules/jobs/job_types/assistant.dm @@ -14,6 +14,7 @@ Assistant minimal_access = list() //See /datum/job/assistant/get_access() outfit = /datum/outfit/job/assistant + /datum/job/assistant/get_access() if((config.jobs_have_maint_access & ASSISTANTS_HAVE_MAINT_ACCESS) || !config.jobs_have_minimal_access) //Config has assistant maint access set . = ..() diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index b605a977be8..38705fb0391 100644 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -99,7 +99,7 @@ Head of Personnel shoes = /obj/item/clothing/shoes/sneakers/brown head = /obj/item/clothing/head/hopcap backpack_contents = list(/obj/item/weapon/storage/box/ids=1,\ - /obj/item/weapon/melee/classic_baton/telescopic=1) + /obj/item/weapon/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced = 1) /datum/outfit/job/hop/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) ..() diff --git a/code/modules/jobs/job_types/cargo_service.dm b/code/modules/jobs/job_types/cargo_service.dm index 6cc148adfbd..c8f4203dcb3 100644 --- a/code/modules/jobs/job_types/cargo_service.dm +++ b/code/modules/jobs/job_types/cargo_service.dm @@ -52,6 +52,7 @@ Cargo Technician belt = /obj/item/device/pda/cargo ears = /obj/item/device/radio/headset/headset_cargo uniform = /obj/item/clothing/under/rank/cargotech + backpack_contents = list(/obj/item/modular_computer/tablet/preset/cheap=1) /* @@ -86,7 +87,7 @@ Shaft Miner backpack_contents = list( /obj/item/weapon/storage/bag/ore=1,\ /obj/item/weapon/kitchen/knife/combat/survival=1,\ - /obj/item/weapon/mining_voucher=1) + /obj/item/weapon/mining_voucher=1,/obj/item/modular_computer/tablet/preset/cheap=1) backpack = /obj/item/weapon/storage/backpack/explorer satchel = /obj/item/weapon/storage/backpack/satchel/explorer @@ -121,7 +122,7 @@ Bartender ears = /obj/item/device/radio/headset/headset_srv uniform = /obj/item/clothing/under/rank/bartender suit = /obj/item/clothing/suit/armor/vest - backpack_contents = list(/obj/item/weapon/storage/box/beanbag=1) + backpack_contents = list(/obj/item/weapon/storage/box/beanbag=1,/obj/item/modular_computer/tablet/preset/cheap=1) shoes = /obj/item/clothing/shoes/laceup /* @@ -152,6 +153,7 @@ Cook uniform = /obj/item/clothing/under/rank/chef suit = /obj/item/clothing/suit/toggle/chef head = /obj/item/clothing/head/chefhat + backpack_contents = list(/obj/item/modular_computer/tablet/preset/cheap=1) /datum/outfit/job/cook/pre_equip(mob/living/carbon/human/H) ..() @@ -200,6 +202,7 @@ Botanist backpack = /obj/item/weapon/storage/backpack/botany satchel = /obj/item/weapon/storage/backpack/satchel/hyd + backpack_contents = list(/obj/item/modular_computer/tablet/preset/cheap=1) /* @@ -228,3 +231,4 @@ Janitor belt = /obj/item/device/pda/janitor ears = /obj/item/device/radio/headset/headset_srv uniform = /obj/item/clothing/under/rank/janitor + backpack_contents = list(/obj/item/modular_computer/tablet/preset/cheap=1) diff --git a/code/modules/jobs/job_types/science.dm b/code/modules/jobs/job_types/science.dm index fa163abbf8c..b52f8ac919a 100644 --- a/code/modules/jobs/job_types/science.dm +++ b/code/modules/jobs/job_types/science.dm @@ -20,12 +20,12 @@ Research Director access_tox_storage, access_teleporter, access_sec_doors, access_research, access_robotics, access_xenobiology, access_ai_upload, access_RC_announce, access_keycard_auth, access_gateway, access_mineral_storeroom, - access_tech_storage, access_minisat, access_maint_tunnels) + access_tech_storage, access_minisat, access_maint_tunnels, access_network) minimal_access = list(access_rd, access_heads, access_tox, access_genetics, access_morgue, access_tox_storage, access_teleporter, access_sec_doors, access_research, access_robotics, access_xenobiology, access_ai_upload, access_RC_announce, access_keycard_auth, access_gateway, access_mineral_storeroom, - access_tech_storage, access_minisat, access_maint_tunnels) + access_tech_storage, access_minisat, access_maint_tunnels, access_network) /datum/outfit/job/rd name = "Research Director" @@ -38,7 +38,7 @@ Research Director suit = /obj/item/clothing/suit/toggle/labcoat l_hand = /obj/item/weapon/clipboard l_pocket = /obj/item/device/laser_pointer - backpack_contents = list(/obj/item/weapon/melee/classic_baton/telescopic=1) + backpack_contents = list(/obj/item/weapon/melee/classic_baton/telescopic=1,/obj/item/modular_computer/tablet/preset/advanced=1) backpack = /obj/item/weapon/storage/backpack/science satchel = /obj/item/weapon/storage/backpack/satchel/tox diff --git a/code/modules/jobs/job_types/security.dm b/code/modules/jobs/job_types/security.dm index ec5b4a2db64..946e31e3a64 100644 --- a/code/modules/jobs/job_types/security.dm +++ b/code/modules/jobs/job_types/security.dm @@ -158,7 +158,7 @@ Detective backpack_contents = list(/obj/item/weapon/storage/box/evidence=1,\ /obj/item/device/detective_scanner=1,\ /obj/item/weapon/melee/classic_baton=1,\ - /obj/item/weapon/reagent_containers/food/drinks/flask/det) + /obj/item/weapon/reagent_containers/food/drinks/flask/det=1) mask = /obj/item/clothing/mask/cigarette /datum/outfit/job/detective/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) diff --git a/code/modules/mining/equipment.dm b/code/modules/mining/equipment.dm index a2e76222198..6dc3aa38efd 100644 --- a/code/modules/mining/equipment.dm +++ b/code/modules/mining/equipment.dm @@ -459,4 +459,111 @@ C.preserved() user << "You inject the [M] with the stabilizer. It will no longer go inert." - qdel(src) \ No newline at end of file + qdel(src) + +/*********************Mining Hammer****************/ +/obj/item/weapon/twohanded/required/mining_hammer + icon = 'icons/obj/mining.dmi' + icon_state = "mining_hammer1" + item_state = "mining_hammer1" + name = "proto-kinetic crusher" + desc = "An early design of the proto-kinetic accelerator, it is little more than an combination of various mining tools cobbled together, forming a high-tech club.\ + While it is an effective mining tool, it did little to aid any but the most skilled and/or suicidal miners against local fauna. \ + \nMark a mob with the destabilizing force, then hit them in melee to activate it for extra damage. Extra damage if backstabbed in this fashion. \ + This weapon is only particularly effective against large creatures." + force = 20 //As much as a bone spear, but this is significantly more annoying to carry around due to requiring the use of both hands at all times + w_class = 4 + slot_flags = SLOT_BACK + force_unwielded = 20 //It's never not wielded so these are the same + force_wielded = 20 + throwforce = 5 + throw_speed = 4 + luminosity = 4 + armour_penetration = 10 + materials = list(MAT_METAL=1150, MAT_GLASS=2075) + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("smashes", "crushes", "cleaves", "chops", "pulps") + sharpness = IS_SHARP + var/charged = 1 + var/charge_time = 16 + var/atom/mark = null + var/marked_image = null + +/obj/item/projectile/destabilizer + name = "destabilizing force" + icon_state = "pulse1" + damage = 0 //We're just here to mark people. This is still a melee weapon. + damage_type = BRUTE + flag = "bomb" + range = 6 + var/obj/item/weapon/twohanded/required/mining_hammer/hammer_synced = null + +/obj/item/projectile/destabilizer/on_hit(atom/target, blocked = 0, hit_zone) + if(hammer_synced) + if(hammer_synced.mark == target) + return ..() + if(isliving(target)) + if(hammer_synced.mark && hammer_synced.marked_image) + hammer_synced.mark.underlays -= hammer_synced.marked_image + hammer_synced.marked_image = null + var/mob/living/L = target + if(L.mob_size >= MOB_SIZE_LARGE) + hammer_synced.mark = L + var/image/I = image('icons/effects/effects.dmi', loc = L, icon_state = "shield2",pixel_y = (-L.pixel_y),pixel_x = (-L.pixel_x)) + L.underlays += I + hammer_synced.marked_image = I + var/target_turf = get_turf(target) + if(istype(target_turf, /turf/closed/mineral)) + var/turf/closed/mineral/M = target_turf + PoolOrNew(/obj/effect/overlay/temp/kinetic_blast, M) + M.gets_drilled(firer) + ..() + +/obj/item/weapon/twohanded/required/mining_hammer/afterattack(atom/target, mob/user, proximity_flag) + if(!proximity_flag && charged)//Mark a target, or mine a tile. + var/turf/proj_turf = get_turf(src) + if(!istype(proj_turf, /turf)) + return + var/datum/gas_mixture/environment = proj_turf.return_air() + var/pressure = environment.return_pressure() + if(pressure > 50) + playsound(user, 'sound/weapons/empty.ogg', 100, 1) + return + var/obj/item/projectile/destabilizer/D = new /obj/item/projectile/destabilizer(user.loc) + D.preparePixelProjectile(target,get_turf(target), user) + D.hammer_synced = src + playsound(user, 'sound/weapons/plasma_cutter.ogg', 100, 1) + D.fire() + charged = 0 + icon_state = "mining_hammer1_uncharged" + addtimer(src, "Recharge", charge_time) + return + if(proximity_flag && target == mark && isliving(target)) + var/mob/living/L = target + PoolOrNew(/obj/effect/overlay/temp/kinetic_blast, get_turf(L)) + mark = 0 + if(L.mob_size >= MOB_SIZE_LARGE) + L.underlays -= marked_image + qdel(marked_image) + marked_image = null + var/backstab = check_target_facings(user, L) + var/def_check = L.getarmor(type = "bomb") + if(backstab == FACING_INIT_FACING_TARGET_TARGET_FACING_PERPENDICULAR || backstab == FACING_SAME_DIR) + L.apply_damage(80, BRUTE, blocked = def_check) + playsound(user, 'sound/weapons/Kenetic_accel.ogg', 100, 1) //Seriously who spelled it wrong + else + L.apply_damage(50, BRUTE, blocked = def_check) + +/obj/item/weapon/twohanded/required/mining_hammer/proc/Recharge() + if(!charged) + charged = 1 + icon_state = "mining_hammer1" + playsound(src.loc, 'sound/weapons/kenetic_reload.ogg', 60, 1) + +/obj/item/weapon/twohanded/required/mining_hammer/pickup(mob/user) + ..() + user.AddLuminosity(luminosity) + +/obj/item/weapon/twohanded/required/mining_hammer/dropped(mob/user) + ..() + user.AddLuminosity(-luminosity) \ No newline at end of file diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm index 7bfeefc707a..02fddfe6b58 100644 --- a/code/modules/mining/machine_vending.dm +++ b/code/modules/mining/machine_vending.dm @@ -27,6 +27,7 @@ new /datum/data/mining_equipment("Brute First-Aid Kit", /obj/item/weapon/storage/firstaid/brute, 600), new /datum/data/mining_equipment("Tracking Implant Kit",/obj/item/weapon/storage/box/minertracker, 600), new /datum/data/mining_equipment("Jaunter", /obj/item/device/wormhole_jaunter, 750), + new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/weapon/twohanded/required/mining_hammer, 750), new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/weapon/gun/energy/kinetic_accelerator, 750), new /datum/data/mining_equipment("Resonator", /obj/item/weapon/resonator, 800), new /datum/data/mining_equipment("Medivac Balloon", /obj/item/weapon/extraction_pack/medivac, 800), @@ -38,7 +39,6 @@ new /datum/data/mining_equipment("Mining Hardsuit", /obj/item/clothing/suit/space/hardsuit/mining, 2000), new /datum/data/mining_equipment("Diamond Pickaxe", /obj/item/weapon/pickaxe/diamond, 2000), new /datum/data/mining_equipment("Super Resonator", /obj/item/weapon/resonator/upgraded, 2500), - new /datum/data/mining_equipment("Super Accelerator", /obj/item/weapon/gun/energy/kinetic_accelerator/super, 3000), new /datum/data/mining_equipment("Point Transfer Card", /obj/item/weapon/card/mining_point_card, 500), new /datum/data/mining_equipment("Mining Drone", /mob/living/simple_animal/hostile/mining_drone, 800), new /datum/data/mining_equipment("Drone Melee Upgrade", /obj/item/device/mine_bot_ugprade, 400), @@ -159,7 +159,7 @@ return ..() /obj/machinery/mineral/equipment_vendor/proc/RedeemVoucher(obj/item/weapon/mining_voucher/voucher, mob/redeemer) - var/items = list("Survival Capsule and Explorer's Webbing", "Resonator and Advanced Scanner", "Mining Drone", "Medivac Kit", "Extraction Kit") + var/items = list("Survival Capsule and Explorer's Webbing", "Resonator and Advanced Scanner", "Mining Drone", "Medivac Kit", "Extraction Kit", "Crusher Kit") var/selection = input(redeemer, "Pick your equipment", "Mining Voucher Redemption") as null|anything in items if(!selection || !Adjacent(redeemer) || qdeleted(voucher) || voucher.loc != redeemer) @@ -182,6 +182,10 @@ new /obj/item/stack/sheet/metal/five(loc) new /obj/item/weapon/extraction_pack(loc) new /obj/item/fulton_core(loc) + if("Crusher Kit") + new /obj/item/weapon/twohanded/required/mining_hammer(loc) + new /obj/item/weapon/storage/belt/mining/alt(loc) + new /obj/item/weapon/extinguisher/mini(loc) feedback_add_details("mining_voucher_redeemed", selection) qdel(voucher) diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm index 32e46bfc34a..12b44e23d48 100644 --- a/code/modules/mining/mine_turfs.dm +++ b/code/modules/mining/mine_turfs.dm @@ -270,7 +270,7 @@ /turf/open/floor/plating/asteroid/airless/cave/volcanic mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goldgrub = 10, /mob/living/simple_animal/hostile/asteroid/goliath/beast = 50, /mob/living/simple_animal/hostile/asteroid/basilisk/watcher = 40, /mob/living/simple_animal/hostile/asteroid/hivelord/legion = 30, /mob/living/simple_animal/hostile/spawner/lavaland = 2, /mob/living/simple_animal/hostile/spawner/lavaland/goliath = 3, /mob/living/simple_animal/hostile/spawner/lavaland/legion = 3, \ - /mob/living/simple_animal/hostile/megafauna/dragon = 2, /mob/living/simple_animal/hostile/megafauna/bubblegum = 2, /mob/living/simple_animal/hostile/megafauna/colossus = 2) + /mob/living/simple_animal/hostile/megafauna/dragon = 2, /mob/living/simple_animal/hostile/megafauna/bubblegum = 2) turf_type = /turf/open/floor/plating/asteroid/basalt/lava_land_surface initial_gas_mix = "o2=14;n2=23;TEMP=300" diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index c2d06ce0ad9..1fde9b07fcf 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -297,11 +297,14 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp ..() if(statpanel("Status")) stat(null, "Station Time: [worldtime2text()]") - if(ticker) - if(ticker.mode) - for(var/datum/gang/G in ticker.mode.gangs) - if(G.is_dominating) - stat(null, "[G.name] Gang Takeover: [max(G.domination_time_remaining(), 0)]") + if(ticker && ticker.mode) + for(var/datum/gang/G in ticker.mode.gangs) + if(G.is_dominating) + stat(null, "[G.name] Gang Takeover: [max(G.domination_time_remaining(), 0)]") + if(istype(ticker.mode, /datum/game_mode/blob)) + var/datum/game_mode/blob/B = ticker.mode + if(B.message_sent) + stat(null, "Blobs to Blob Win: [blobs_legit.len]/[B.blobwincount]") /mob/dead/observer/verb/reenter_corpse() set category = "Ghost" diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 239c6eae027..d5750778a71 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -203,12 +203,19 @@ for(var/obj/item/I in BP.embedded_objects) msg += "[t_He] [t_has] \a \icon[I] [I] embedded in [t_his] [BP.name]!\n" + //stores how many left limbs are missing + var/l_limbs_missing = 0 for(var/t in missing) if(t=="head") msg += "[capitalize(t_his)] [parse_zone(t)] is missing!\n" continue + if(t == "l_arm" || t == "l_leg") + l_limbs_missing++ msg += "[capitalize(t_his)] [parse_zone(t)] is missing!\n" + if(l_limbs_missing >= 2) + msg += "[t_He] looks all right now.\n" + if(temp) if(temp < 30) msg += "[t_He] [t_has] minor bruising.\n" @@ -345,7 +352,6 @@ msg += "\[Add crime\] " msg += "\[View comment log\] " msg += "\[Add comment\]\n" - msg += "*---------*" user << msg diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 936e2907d99..8208c2d3bea 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -953,12 +953,16 @@ Sorry Giacom. Please don't be mad :( /mob/living/Stat() ..() + if(statpanel("Status")) - if(ticker) - if(ticker.mode) - for(var/datum/gang/G in ticker.mode.gangs) - if(G.is_dominating) - stat(null, "[G.name] Gang Takeover: [max(G.domination_time_remaining(), 0)]") + if(ticker && ticker.mode) + for(var/datum/gang/G in ticker.mode.gangs) + if(G.is_dominating) + stat(null, "[G.name] Gang Takeover: [max(G.domination_time_remaining(), 0)]") + if(istype(ticker.mode, /datum/game_mode/blob)) + var/datum/game_mode/blob/B = ticker.mode + if(B.message_sent) + stat(null, "Blobs to Blob Win: [blobs_legit.len]/[B.blobwincount]") /mob/living/cancel_camera() ..() diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 1fb34fd9e90..3ac0348d765 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -10,6 +10,7 @@ healable = 0 damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + maxbodytemp = INFINITY minbodytemp = 0 has_unlimited_silicon_privilege = 1 sentience_type = SENTIENCE_ARTIFICIAL 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 733239b922c..6f368f69560 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -313,6 +313,7 @@ Difficulty: Very Hard return current = src ReadMemory() + . = ..() /obj/machinery/smartfridge/black_box/process() ..() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm index 2a670be3528..4cd49e0c043 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm @@ -51,6 +51,7 @@ Difficulty: Medium aggro_vision_range = 18 idle_vision_range = 13 appearance_flags = 0 + mouse_opacity = 1 /mob/living/simple_animal/hostile/megafauna/legion/New() ..() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index c68f310fda4..8b51d9c25c8 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -22,6 +22,7 @@ var/elimination = 0 anchored = TRUE layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise + mouse_opacity = 2 // Easier to click on in melee, they're giant targets anyway /mob/living/simple_animal/hostile/megafauna/death(gibbed) if(health > 0) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm index f2f83306c10..79752bbdaf2 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm @@ -15,6 +15,7 @@ var/icon_aggro = null // for swapping to when we get aggressive see_in_dark = 8 see_invisible = SEE_INVISIBLE_MINIMUM + mob_size = MOB_SIZE_LARGE /mob/living/simple_animal/hostile/asteroid/Aggro() ..() @@ -452,7 +453,6 @@ aggro_vision_range = 9 idle_vision_range = 5 anchored = 1 //Stays anchored until death as to be unpullable - mob_size = MOB_SIZE_LARGE var/pre_attack = 0 var/pre_attack_icon = "Goliath_preattack" loot = list(/obj/item/stack/sheet/animalhide/goliath_hide) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index da9d63281a8..ea5c3d6535c 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -2,6 +2,7 @@ mob_list -= src dead_mob_list -= src living_mob_list -= src + all_clockwork_mobs -= src if(observers && observers.len) for(var/M in observers) var/mob/dead/observe = M @@ -593,7 +594,7 @@ var/next_mob_id = 0 if(statpanel("Status")) stat(null, "Map: [MAP_NAME]") - if (nextmap && istype(nextmap)) + if(nextmap && istype(nextmap)) stat(null, "Next Map: [nextmap.friendlyname]") stat(null, "Server Time: [time2text(world.realtime, "YYYY-MM-DD hh:mm")]") if(SSshuttle.emergency) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index d396b59715d..e2a6518a629 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -12,7 +12,26 @@ return 1 return (!mover.density || !density || lying) +//there are bugs with byond's default versions of these verbs +/client/verb/moveup() + set name = ".moveup" + set instant = 1 + Move(get_step(mob, NORTH), NORTH) +/client/verb/movedown() + set name = ".movedown" + set instant = 1 + Move(get_step(mob, SOUTH), SOUTH) + +/client/verb/moveright() + set name = ".moveright" + set instant = 1 + Move(get_step(mob, EAST), EAST) + +/client/verb/moveleft() + set name = ".moveleft" + set instant = 1 + Move(get_step(mob, WEST), WEST) /client/Northeast() swap_hand() @@ -90,14 +109,15 @@ /client/Move(n, direct) + if(world.time < move_delay) + return 0 + move_delay = world.time+world.tick_lag //this is here because Move() can now be called mutiple times per tick if(!mob || !mob.loc) return 0 if(mob.notransform) return 0 //This is sota the goto stop mobs from moving var if(mob.control_object) return Move_object(direct) - if(world.time < move_delay) - return 0 if(!isliving(mob)) return mob.Move(n,direct) if(mob.stat == DEAD) diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 08495e7a388..e1b2f760b4c 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -422,8 +422,8 @@ . = new_slime qdel(src) -/mob/proc/become_overmind(mode_made = 0) - var/mob/camera/blob/B = new /mob/camera/blob(loc, 0, mode_made) +/mob/proc/become_overmind(mode_made, starting_points = 60) + var/mob/camera/blob/B = new /mob/camera/blob(loc, 0, mode_made, starting_points) if(mind) mind.transfer_to(B) else diff --git a/code/modules/modular_computers/NTNet/NTNRC/conversation.dm b/code/modules/modular_computers/NTNet/NTNRC/conversation.dm new file mode 100644 index 00000000000..075bef313fe --- /dev/null +++ b/code/modules/modular_computers/NTNet/NTNRC/conversation.dm @@ -0,0 +1,65 @@ +var/global/static/ntnrc_uid = 0 + +/datum/ntnet_conversation/ + var/id = null + var/title = "Untitled Conversation" + var/datum/computer_file/program/chatclient/operator // "Administrator" of this channel. Creator starts as channel's operator, + var/list/messages = list() + var/list/clients = list() + var/password + +/datum/ntnet_conversation/New() + id = ntnrc_uid + ntnrc_uid++ + if(ntnet_global) + ntnet_global.chat_channels.Add(src) + ..() + +/datum/ntnet_conversation/proc/add_message(message, username) + message = "[worldtime2text()] [username]: [message]" + messages.Add(message) + trim_message_list() + +/datum/ntnet_conversation/proc/add_status_message(message) + messages.Add("[worldtime2text()] -!- [message]") + trim_message_list() + +/datum/ntnet_conversation/proc/trim_message_list() + if(messages.len <= 50) + return + messages = messages.Copy(messages.len-50 ,0) + +/datum/ntnet_conversation/proc/add_client(datum/computer_file/program/chatclient/C) + if(!istype(C)) + return + clients.Add(C) + add_status_message("[C.username] has joined the channel.") + // No operator, so we assume the channel was empty. Assign this user as operator. + if(!operator) + changeop(C) + +/datum/ntnet_conversation/proc/remove_client(datum/computer_file/program/chatclient/C) + if(!istype(C) || !(C in clients)) + return + clients.Remove(C) + add_status_message("[C.username] has left the channel.") + + // Channel operator left, pick new operator + if(C == operator) + operator = null + if(clients.len) + var/datum/computer_file/program/chatclient/newop = pick(clients) + changeop(newop) + + +/datum/ntnet_conversation/proc/changeop(datum/computer_file/program/chatclient/newop) + if(istype(newop)) + operator = newop + add_status_message("Channel operator status transferred to [newop.username].") + +/datum/ntnet_conversation/proc/change_title(newtitle, datum/computer_file/program/chatclient/client) + if(operator != client) + return 0 // Not Authorised + + add_status_message("[client.username] has changed channel title from [title] to [newtitle]") + title = newtitle \ No newline at end of file diff --git a/code/modules/modular_computers/NTNet/NTNet.dm b/code/modules/modular_computers/NTNet/NTNet.dm new file mode 100644 index 00000000000..b80668361b8 --- /dev/null +++ b/code/modules/modular_computers/NTNet/NTNet.dm @@ -0,0 +1,145 @@ +var/global/datum/ntnet/ntnet_global = new() + + +// This is the NTNet datum. There can be only one NTNet datum in game at once. Modular computers read data from this. +/datum/ntnet/ + var/list/relays = list() + var/list/logs = list() + var/list/available_station_software = list() + var/list/available_antag_software = list() + var/list/chat_channels = list() + var/list/fileservers = list() + // Amount of logs the system tries to keep in memory. Keep below 999 to prevent byond from acting weirdly. + // High values make displaying logs much laggier. + var/setting_maxlogcount = 100 + + // These only affect wireless. LAN (consoles) are unaffected since it would be possible to create scenario where someone turns off NTNet, and is unable to turn it back on since it refuses connections + var/setting_softwaredownload = 1 + var/setting_peertopeer = 1 + var/setting_communication = 1 + var/setting_systemcontrol = 1 + var/setting_disabled = 0 // Setting to 1 will disable all wireless, independently on relays status. + + var/intrusion_detection_enabled = 1 // Whether the IDS warning system is enabled + var/intrusion_detection_alarm = 0 // Set when there is an IDS warning due to malicious (antag) software. + + +// If new NTNet datum is spawned, it replaces the old one. +/datum/ntnet/New() + if(ntnet_global && (ntnet_global != src)) + ntnet_global = src // There can be only one. + for(var/obj/machinery/ntnet_relay/R in machines) + relays.Add(R) + R.NTNet = src + build_software_lists() + add_log("NTNet logging system activated.") + +// Simplified logging: Adds a log. log_string is mandatory parameter, source is optional. +/datum/ntnet/proc/add_log(log_string, obj/item/weapon/computer_hardware/network_card/source = null) + var/log_text = "[worldtime2text()] - " + if(source) + log_text += "[source.get_network_tag()] - " + else + log_text += "*SYSTEM* - " + log_text += log_string + logs.Add(log_text) + + + // We have too many logs, remove the oldest entries until we get into the limit + if(logs.len > setting_maxlogcount) + logs = logs.Copy(logs.len-setting_maxlogcount,0) + + +// Checks whether NTNet operates. If parameter is passed checks whether specific function is enabled. +/datum/ntnet/proc/check_function(specific_action = 0) + if(!relays || !relays.len) // No relays found. NTNet is down + return 0 + + var/operating = 0 + + // Check all relays. If we have at least one working relay, network is up. + for(var/M in relays) + var/obj/machinery/ntnet_relay/R = M + if(R.is_operational()) + operating = 1 + break + + if(setting_disabled) + return 0 + + switch(specific_action) + if(NTNET_SOFTWAREDOWNLOAD) + return (operating && setting_softwaredownload) + if(NTNET_PEERTOPEER) + return (operating && setting_peertopeer) + if(NTNET_COMMUNICATION) + return (operating && setting_communication) + if(NTNET_SYSTEMCONTROL) + return (operating && setting_systemcontrol) + return operating + +// Builds lists that contain downloadable software. +/datum/ntnet/proc/build_software_lists() + available_station_software = list() + available_antag_software = list() + for(var/F in typesof(/datum/computer_file/program)) + var/datum/computer_file/program/prog = new F + // Invalid type (shouldn't be possible but just in case), invalid filetype (not executable program) or invalid filename (unset program) + if(!prog || prog.filename == "UnknownProgram" || prog.filetype != "PRG") + continue + // Check whether the program should be available for station/antag download, if yes, add it to lists. + if(prog.available_on_ntnet) + available_station_software.Add(prog) + if(prog.available_on_syndinet) + available_antag_software.Add(prog) + +// Attempts to find a downloadable file according to filename var +/datum/ntnet/proc/find_ntnet_file_by_name(filename) + for(var/N in available_station_software) + var/datum/computer_file/program/P = N + if(filename == P.filename) + return P + for(var/N in available_antag_software) + var/datum/computer_file/program/P = N + if(filename == P.filename) + return P + +// Resets the IDS alarm +/datum/ntnet/proc/resetIDS() + intrusion_detection_alarm = 0 + +/datum/ntnet/proc/toggleIDS() + resetIDS() + intrusion_detection_enabled = !intrusion_detection_enabled + +// Removes all logs +/datum/ntnet/proc/purge_logs() + logs = list() + add_log("-!- LOGS DELETED BY SYSTEM OPERATOR -!-") + +// Updates maximal amount of stored logs. Use this instead of setting the number, it performs required checks. +/datum/ntnet/proc/update_max_log_count(lognumber) + if(!lognumber) + return 0 + // Trim the value if necessary + lognumber = max(MIN_NTNET_LOGS, min(lognumber, MAX_NTNET_LOGS)) + setting_maxlogcount = lognumber + add_log("Configuration Updated. Now keeping [setting_maxlogcount] logs in system memory.") + +/datum/ntnet/proc/toggle_function(function) + if(!function) + return + function = text2num(function) + switch(function) + if(NTNET_SOFTWAREDOWNLOAD) + setting_softwaredownload = !setting_softwaredownload + add_log("Configuration Updated. Wireless network firewall now [setting_softwaredownload ? "allows" : "disallows"] connection to software repositories.") + if(NTNET_PEERTOPEER) + setting_peertopeer = !setting_peertopeer + add_log("Configuration Updated. Wireless network firewall now [setting_peertopeer ? "allows" : "disallows"] peer to peer network traffic.") + if(NTNET_COMMUNICATION) + setting_communication = !setting_communication + add_log("Configuration Updated. Wireless network firewall now [setting_communication ? "allows" : "disallows"] instant messaging and similar communication services.") + if(NTNET_SYSTEMCONTROL) + setting_systemcontrol = !setting_systemcontrol + add_log("Configuration Updated. Wireless network firewall now [setting_systemcontrol ? "allows" : "disallows"] remote control of station's systems.") diff --git a/code/modules/modular_computers/NTNet/NTNet_relay.dm b/code/modules/modular_computers/NTNet/NTNet_relay.dm new file mode 100644 index 00000000000..1eb87cec084 --- /dev/null +++ b/code/modules/modular_computers/NTNet/NTNet_relay.dm @@ -0,0 +1,127 @@ +// Relays don't handle any actual communication. Global NTNet datum does that, relays only tell the datum if it should or shouldn't work. +/obj/machinery/ntnet_relay + name = "NTNet Quantum Relay" + desc = "A very complex router and transmitter capable of connecting electronic devices together. Looks fragile." + use_power = 2 + active_power_usage = 20000 //20kW, apropriate for machine that keeps massive cross-Zlevel wireless network operational. + idle_power_usage = 100 + icon = 'icons/obj/machines/telecomms.dmi' + icon_state = "bus" + anchored = 1 + density = 1 + var/datum/ntnet/NTNet = null // This is mostly for backwards reference and to allow varedit modifications from ingame. + var/enabled = 1 // Set to 0 if the relay was turned off + var/dos_failure = 0 // Set to 1 if the relay failed due to (D)DoS attack + var/list/dos_sources = list() // Backwards reference for qdel() stuff + + // Denial of Service attack variables + var/dos_overload = 0 // Amount of DoS "packets" in this relay's buffer + var/dos_capacity = 500 // Amount of DoS "packets" in buffer required to crash the relay + var/dos_dissipate = 1 // Amount of DoS "packets" dissipated over time. + + +// TODO: Implement more logic here. For now it's only a placeholder. +/obj/machinery/ntnet_relay/is_operational() + if(stat & (BROKEN | NOPOWER | EMPED)) + return 0 + if(dos_failure) + return 0 + if(!enabled) + return 0 + return 1 + +/obj/machinery/ntnet_relay/update_icon() + if(is_operational()) + icon_state = "bus" + else + icon_state = "bus_off" + +/obj/machinery/ntnet_relay/process() + if(is_operational()) + use_power = 2 + else + use_power = 1 + + if(dos_overload) + dos_overload = max(0, dos_overload - dos_dissipate) + + // If DoS traffic exceeded capacity, crash. + if((dos_overload > dos_capacity) && !dos_failure) + dos_failure = 1 + update_icon() + ntnet_global.add_log("Quantum relay switched from normal operation mode to overload recovery mode.") + // If the DoS buffer reaches 0 again, restart. + if((dos_overload == 0) && dos_failure) + dos_failure = 0 + update_icon() + ntnet_global.add_log("Quantum relay switched from overload recovery mode to normal operation mode.") + ..() + +/obj/machinery/ntnet_relay/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + + if(!ui) + ui = new(user, src, ui_key, "ntnet_relay", "NTNet Quantum Relay", 500, 300, master_ui, state) + ui.open() + + +/obj/machinery/ntnet_relay/ui_data(mob/user) + var/list/data = list() + data["enabled"] = enabled + data["dos_capacity"] = dos_capacity + data["dos_overload"] = dos_overload + data["dos_crashed"] = dos_failure + return data + + +/obj/machinery/ntnet_relay/ui_act(action, params) + if(..()) + return + switch(action) + if("restart") + dos_overload = 0 + dos_failure = 0 + update_icon() + ntnet_global.add_log("Quantum relay manually restarted from overload recovery mode to normal operation mode.") + if("toggle") + enabled = !enabled + ntnet_global.add_log("Quantum relay manually [enabled ? "enabled" : "disabled"].") + update_icon() + + +/obj/machinery/ntnet_relay/attack_hand(mob/living/user) + ui_interact(user) + +/obj/machinery/ntnet_relay/New() + uid = gl_uid + gl_uid++ + component_parts = list() + var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/ntnet_relay(null) + B.apply_default_parts(src) + + if(ntnet_global) + ntnet_global.relays.Add(src) + NTNet = ntnet_global + ntnet_global.add_log("New quantum relay activated. Current amount of linked relays: [NTNet.relays.len]") + ..() + +/obj/machinery/ntnet_relay/Destroy() + if(ntnet_global) + ntnet_global.relays.Remove(src) + ntnet_global.add_log("Quantum relay connection severed. Current amount of linked relays: [NTNet.relays.len]") + NTNet = null + + for(var/datum/computer_file/program/ntnet_dos/D in dos_sources) + D.target = null + D.error = "Connection to quantum relay severed" + + ..() + +/obj/item/weapon/circuitboard/machine/ntnet_relay + name = "circuit board (NTNet Relay)" + build_path = /obj/machinery/ntnet_relay + origin_tech = "programming=3;bluespace=3;magnets=2" + req_components = list( + /obj/item/stack/cable_coil = 2, + /obj/item/weapon/stock_parts/subspace/filter = 1) \ No newline at end of file diff --git a/code/modules/modular_computers/computers/item/modular_computer.dm b/code/modules/modular_computers/computers/item/modular_computer.dm new file mode 100644 index 00000000000..feb63b2c8f7 --- /dev/null +++ b/code/modules/modular_computers/computers/item/modular_computer.dm @@ -0,0 +1,704 @@ +// This is the base type that does all the hardware stuff. +// Other types expand it - tablets use a direct subtypes, and +// consoles and laptops use "procssor" item that is held inside machinery piece +/obj/item/modular_computer + name = "Modular Microcomputer" + desc = "A small portable microcomputer." + + var/enabled = 0 // Whether the computer is turned on. + var/screen_on = 1 // Whether the computer is active/opened/it's screen is on. + var/datum/computer_file/program/active_program = null // A currently active program running on the computer. + var/hardware_flag = 0 // A flag that describes this device type + var/last_power_usage = 0 + var/last_battery_percent = 0 // Used for deciding if battery percentage has chandged + var/last_world_time = "00:00" + var/list/last_header_icons + var/computer_emagged = 0 // Whether the computer is emagged. + + var/base_active_power_usage = 50 // Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too. + var/base_idle_power_usage = 5 // Power usage when the computer is idle and screen is off (currently only applies to laptops) + + // Modular computers can run on various devices. Each DEVICE (Laptop, Console, Tablet,..) + // must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently + // If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example. + + icon = 'icons/obj/computer.dmi' + icon_state = "laptop-open" + var/icon_state_unpowered = null // Icon state when the computer is turned off + var/icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen. + var/max_hardware_size = 0 // Maximal hardware size. Currently, tablets have 1, laptops 2 and consoles 3. Limits what hardware types can be installed. + var/steel_sheet_cost = 5 // Amount of steel sheets refunded when disassembling an empty frame of this computer. + + // Damage of the chassis. If the chassis takes too much damage it will break apart. + var/damage = 0 // Current damage level + var/broken_damage = 50 // Damage level at which the computer ceases to operate + var/max_damage = 100 // Damage level at which the computer breaks apart. + + // Important hardware (must be installed for computer to work) + var/obj/item/weapon/computer_hardware/processor_unit/processor_unit // CPU. Without it the computer won't run. Better CPUs can run more programs at once. + var/obj/item/weapon/computer_hardware/network_card/network_card // Network Card component of this computer. Allows connection to NTNet + var/obj/item/weapon/computer_hardware/hard_drive/hard_drive // Hard Drive component of this computer. Stores programs and files. + var/obj/item/weapon/computer_hardware/battery_module/battery_module // An internal power source for this computer. Can be recharged. + // Optional hardware (improves functionality, but is not critical for computer to work) + var/obj/item/weapon/computer_hardware/card_slot/card_slot // ID Card slot component of this computer. Mostly for HoP modification console that needs ID slot for modification. + var/obj/item/weapon/computer_hardware/card_slot/card_slot2 // second slot. popular demand requested the hop need to insert 2 ids. + var/obj/item/weapon/computer_hardware/nano_printer/nano_printer // Nano Printer component of this computer, for your everyday paperwork needs. + var/obj/item/weapon/computer_hardware/hard_drive/portable/portable_drive // Portable data storage + + var/list/idle_threads = list() // Idle programs on background. They still receive process calls but can't be interacted with. + var/obj/physical = null + + + +// Eject ID card from computer, if it has ID slot with card inside. +/obj/item/modular_computer/verb/eject_id() + set name = "Eject ID" + set category = "Object" + set src in view(1) + + if(usr.incapacitated() || !istype(usr, /mob/living)) + usr << "You can't do that." + return + + if(!Adjacent(usr)) + usr << "You can't reach it." + return + + proc_eject_id(usr) + +// Eject ID card from computer, if it has ID slot with card inside. +/obj/item/modular_computer/verb/eject_usb() + set name = "Eject Portable Device" + set category = "Object" + set src in view(1) + + if(usr.incapacitated() || !istype(usr, /mob/living)) + usr << "You can't do that." + return + + if(!Adjacent(usr)) + usr << "You can't reach it." + return + + proc_eject_usb(usr) + +/obj/item/modular_computer/proc/proc_eject_id(mob/user, slot) + if(!user) + user = usr + + if(!card_slot) + user << "\The [src] does not have an ID card slot" + return + + if(!card_slot.stored_card && !card_slot.stored_card2) + user << "There is no card in \the [src]" + return + + if(active_program) + active_program.event_idremoved(0, slot) + + for(var/I in idle_threads) + var/datum/computer_file/program/P = I + P.event_idremoved(1, slot) + if(card_slot.stored_card && (!slot || slot == 1)) + card_slot.stored_card.forceMove(get_turf(src)) + card_slot.stored_card = null + if(card_slot.stored_card2 && (!slot || slot == 2)) + card_slot.stored_card2.forceMove(get_turf(src)) + card_slot.stored_card2 = null + user << "You remove the card[card_slot.stored_card && card_slot.stored_card2 && !slot ? "s" : ""] from \the [src]" + +/obj/item/modular_computer/proc/proc_eject_usb(mob/user) + if(!user) + user = usr + + if(!portable_drive) + user << "There is no portable device connected to \the [src]." + return + + uninstall_component(user, portable_drive) + +/obj/item/modular_computer/attack_ghost(mob/dead/observer/user) + if(enabled) + ui_interact(user) + else if(check_rights_for(user.client, R_ADMIN)) + var/response = alert(user, "This computer is turned off. Would you like to turn it on?", "Admin Override", "Yes", "No") + if(response == "Yes") + turn_on(user) + +/obj/item/modular_computer/emag_act(mob/user) + if(computer_emagged) + user << "\The [src] was already emagged." + return 0 + else + computer_emagged = 1 + user << "You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message." + return 1 + +/obj/item/modular_computer/examine(mob/user) + ..() + if(damage > broken_damage) + user << "It is heavily damaged!" + else if(damage) + user << "It is damaged." + +/obj/item/modular_computer/New() + START_PROCESSING(SSmachine, src) + update_icon() + if(!physical) + physical = src + ..() + +/obj/item/modular_computer/Destroy() + kill_program(1) + STOP_PROCESSING(SSmachine, src) + for(var/H in get_all_components()) + var/obj/item/weapon/computer_hardware/CH = H + uninstall_component(null, CH) + qdel(CH) + return ..() + +/obj/item/modular_computer/update_icon() + icon_state = icon_state_unpowered + cut_overlays() + if(!enabled) + return + if(active_program) + add_overlay(active_program.program_icon_state ? active_program.program_icon_state : icon_state_menu) + else + add_overlay(icon_state_menu) + +// Used by child types if they have other power source than battery +/obj/item/modular_computer/proc/check_power_override() + return 0 + +// Operates NanoUI +/obj/item/modular_computer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + + if(!screen_on || !enabled) + if(ui) + ui.close() + return 0 + if((!battery_module || !battery_module.battery.charge) && !check_power_override()) + if(ui) + ui.close() + return 0 + + // If we have an active program switch to it now. + if(active_program) + if(ui) // This is the main laptop screen. Since we are switching to program's UI close it for now. + ui.close() + active_program.ui_interact(user) + return + + // We are still here, that means there is no program loaded. Load the BIOS/ROM/OS/whatever you want to call it. + // This screen simply lists available programs and user may select them. + if(!hard_drive || !hard_drive.stored_files || !hard_drive.stored_files.len) + physical.visible_message("\The [src] beeps three times, it's screen displaying \"DISK ERROR\" warning.") + return // No HDD, No HDD files list or no stored files. Something is very broken. + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + ui = new(user, src, ui_key, "computer_main", "NTOS Main menu", 400, 500, master_ui, state) + ui.open() + ui.set_autoupdate(state = 1) + + +/obj/item/modular_computer/ui_data(mob/user) + + var/list/data = get_header_data() + data["programs"] = list() + for(var/datum/computer_file/program/P in hard_drive.stored_files) + var/running = 0 + if(P in idle_threads) + running = 1 + + data["programs"] += list(list("name" = P.filename, "desc" = P.filedesc, "running" = running)) + + return data + + +// On-click handling. Turns on the computer if it's off and opens the GUI. +/obj/item/modular_computer/attack_self(mob/user) + if(enabled) + ui_interact(user) + else + turn_on(user) + +/obj/item/modular_computer/proc/break_apart() + physical.visible_message("\The [src] breaks apart!") + var/turf/newloc = get_turf(src) + new /obj/item/stack/sheet/metal(newloc, round(steel_sheet_cost/2)) + for(var/C in get_all_components()) + var/obj/item/weapon/computer_hardware/H = C + uninstall_component(null, H) + H.forceMove(newloc) + if(prob(25)) + H.take_damage(rand(10,30)) + relay_qdel() + qdel() + +/obj/item/modular_computer/proc/turn_on(mob/user) + var/issynth = issilicon(user) // Robots and AIs get different activation messages. + if(damage > broken_damage) + if(issynth) + user << "You send an activation signal to \the [src], but it responds with an error code. It must be damaged." + else + user << "You press the power button, but the computer fails to boot up, displaying variety of errors before shutting down again." + return + if(processor_unit && ((battery_module && battery_module.battery.charge && battery_module.check_functionality()) || check_power_override())) // Battery-run and charged or non-battery but powered by APC. + if(issynth) + user << "You send an activation signal to \the [src], turning it on" + else + user << "You press the power button and start up \the [src]" + enabled = 1 + update_icon() + ui_interact(user) + else // Unpowered + if(issynth) + user << "You send an activation signal to \the [src] but it does not respond" + else + user << "You press the power button but \the [src] does not respond" + +// Process currently calls handle_power(), may be expanded in future if more things are added. +/obj/item/modular_computer/process() + if(!enabled) // The computer is turned off + last_power_usage = 0 + return 0 + + if(damage > broken_damage) + shutdown_computer() + return 0 + + if(active_program && active_program.requires_ntnet && !get_ntnet_status(active_program.requires_ntnet_feature)) // Active program requires NTNet to run but we've just lost connection. Crash. + active_program.event_networkfailure(0) + + for(var/I in idle_threads) + var/datum/computer_file/program/P = I + if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature)) + P.event_networkfailure(1) + + if(active_program) + if(active_program.program_state != PROGRAM_STATE_KILLED) + active_program.process_tick() + active_program.ntnet_status = get_ntnet_status() + active_program.computer_emagged = computer_emagged + else + active_program = null + + for(var/I in idle_threads) + var/datum/computer_file/program/P = I + if(P.program_state != PROGRAM_STATE_KILLED) + P.process_tick() + P.ntnet_status = get_ntnet_status() + P.computer_emagged = computer_emagged + else + idle_threads.Remove(P) + + handle_power() // Handles all computer power interaction + //check_update_ui_need() + +// Function used by NanoUI's to obtain data for header. All relevant entries begin with "PC_" +/obj/item/modular_computer/proc/get_header_data() + var/list/data = list() + + if(battery_module) + switch(battery_module.battery.percent()) + if(80 to 200) // 100 should be maximal but just in case.. + data["PC_batteryicon"] = "batt_100.gif" + if(60 to 80) + data["PC_batteryicon"] = "batt_80.gif" + if(40 to 60) + data["PC_batteryicon"] = "batt_60.gif" + if(20 to 40) + data["PC_batteryicon"] = "batt_40.gif" + if(5 to 20) + data["PC_batteryicon"] = "batt_20.gif" + else + data["PC_batteryicon"] = "batt_5.gif" + data["PC_batterypercent"] = "[round(battery_module.battery.percent())] %" + data["PC_showbatteryicon"] = 1 + else + data["PC_batteryicon"] = "batt_5.gif" + data["PC_batterypercent"] = "N/C" + data["PC_showbatteryicon"] = battery_module ? 1 : 0 + + switch(get_ntnet_status()) + if(0) + data["PC_ntneticon"] = "sig_none.gif" + if(1) + data["PC_ntneticon"] = "sig_low.gif" + if(2) + data["PC_ntneticon"] = "sig_high.gif" + if(3) + data["PC_ntneticon"] = "sig_lan.gif" + + if(idle_threads.len) + var/list/program_headers = list() + for(var/I in idle_threads) + var/datum/computer_file/program/P = I + if(!P.ui_header) + continue + program_headers.Add(list(list( + "icon" = P.ui_header + ))) + + data["PC_programheaders"] = program_headers + + data["PC_stationtime"] = worldtime2text() + data["PC_hasheader"] = 1 + data["PC_showexitprogram"] = active_program ? 1 : 0 // Hides "Exit Program" button on mainscreen + return data + +// Relays kill program request to currently active program. Use this to quit current program. +/obj/item/modular_computer/proc/kill_program(forced = 0) + if(active_program) + active_program.kill_program(forced) + active_program = null + var/mob/user = usr + if(user && istype(user)) + ui_interact(user) // Re-open the UI on this computer. It should show the main screen now. + update_icon() + +// Returns 0 for No Signal, 1 for Low Signal and 2 for Good Signal. 3 is for wired connection (always-on) +/obj/item/modular_computer/proc/get_ntnet_status(specific_action = 0) + if(network_card) + return network_card.get_signal(specific_action) + else + return 0 + +/obj/item/modular_computer/proc/add_log(text) + if(!get_ntnet_status()) + return 0 + return ntnet_global.add_log(text, network_card) + +/obj/item/modular_computer/proc/shutdown_computer(loud = 1) + kill_program(1) + for(var/datum/computer_file/program/P in idle_threads) + P.kill_program(1) + idle_threads.Remove(P) + if(loud) + physical.visible_message("\The [src] shuts down.") + enabled = 0 + update_icon() + return + +// Handles user's GUI input +/obj/item/modular_computer/ui_act(action, params) + if(..()) + return + switch(action) + if("PC_exit") + kill_program() + return 1 + if("PC_shutdown") + shutdown_computer() + return 1 + if("PC_minimize") + var/mob/user = usr + if(!active_program || !processor_unit) + return + + idle_threads.Add(active_program) + active_program.program_state = PROGRAM_STATE_BACKGROUND // Should close any existing UIs + + active_program = null + update_icon() + if(user && istype(user)) + ui_interact(user) // Re-open the UI on this computer. It should show the main screen now. + + if("PC_killprogram") + var/prog = params["name"] + var/datum/computer_file/program/P = null + var/mob/user = usr + if(hard_drive) + P = hard_drive.find_file_by_name(prog) + + if(!istype(P) || P.program_state == PROGRAM_STATE_KILLED) + return + + P.kill_program(1) + user << "Program [P.filename].[P.filetype] with PID [rand(100,999)] has been killed." + + if("PC_runprogram") + var/prog = params["name"] + var/datum/computer_file/program/P = null + var/mob/user = usr + if(hard_drive) + P = hard_drive.find_file_by_name(prog) + + if(!P || !istype(P)) // Program not found or it's not executable program. + user << "\The [src]'s screen shows \"I/O ERROR - Unable to run program\" warning." + return + + P.computer = src + + if(!P.is_supported_by_hardware(hardware_flag, 1, user)) + return + + // The program is already running. Resume it. + if(P in idle_threads) + P.program_state = PROGRAM_STATE_ACTIVE + active_program = P + idle_threads.Remove(P) + update_icon() + return + + if(idle_threads.len >= processor_unit.max_idle_programs+1) + user << "\The [src] displays a \"Maximal CPU load reached. Unable to run another program.\" error" + return + + if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature)) // The program requires NTNet connection, but we are not connected to NTNet. + user << "\The [src]'s screen shows \"NETWORK ERROR - Unable to connect to NTNet. Please retry. If problem persists contact your system administrator.\" warning." + return + P.computer_emagged = computer_emagged + if(P.run_program(user)) + active_program = P + update_icon() + return 1 + else + return + +// Used in following function to reduce copypaste +/obj/item/modular_computer/proc/power_failure(malfunction = 0) + if(enabled) // Shut down the computer + physical.visible_message("\The [src]'s screen flickers \"BATTERY [malfunction ? "MALFUNCTION" : "CRITICAL"]\" warning as it shuts down unexpectedly.") + if(active_program) + active_program.event_powerfailure(0) + for(var/I in idle_threads) + var/datum/computer_file/program/PRG = I + PRG.event_powerfailure(1) + shutdown_computer(0) + +// Handles power-related things, such as battery interaction, recharging, shutdown when it's discharged +/obj/item/modular_computer/proc/handle_power() + if(!battery_module || battery_module.battery.charge <= 0) // Battery-run but battery is depleted. + power_failure() + return 0 + + var/power_usage = screen_on ? base_active_power_usage : base_idle_power_usage + + for(var/obj/item/weapon/computer_hardware/H in get_all_components()) + if(H.enabled) + power_usage += H.power_usage + + if(battery_module) + if(!battery_module.check_functionality()) + power_failure(1) + return + if(!battery_module.battery.use(power_usage * CELLRATE)) + battery_module.battery.charge = 0 + power_failure() + return 0 + + last_power_usage = power_usage + +/obj/item/modular_computer/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W, /obj/item/weapon/card/id)) // ID Card, try to insert it. + var/obj/item/weapon/card/id/I = W + if(!card_slot) + user << "You try to insert \the [I] into \the [src], but it does not have an ID card slot installed." + return + + if(card_slot.stored_card && card_slot.stored_card2) + user << "You try to insert \the [I] into \the [src], but it's ID card slots are occupied." + return + if(!user.drop_item(I)) + return + if(!card_slot.stored_card) + card_slot.stored_card = I + else + card_slot.stored_card2 = I + I.forceMove(src) +// update_uis() + user << "You insert \the [I] into \the [src]." + return + if(istype(W, /obj/item/weapon/paper)) + if(!nano_printer) + return + nano_printer.attackby(W, user) + if(istype(W, /obj/item/weapon/computer_hardware)) + var/obj/item/weapon/computer_hardware/C = W + if(C.hardware_size <= max_hardware_size) + C.try_install_component(user, src) + else + user << "This component is too large for \the [src]." + if(istype(W, /obj/item/weapon/wrench)) + var/list/components = get_all_components() + if(components.len) + user << "Remove all components from \the [src] before disassembling it." + return + new /obj/item/stack/sheet/metal( get_turf(src.loc), steel_sheet_cost ) + physical.visible_message("\The [src] has been disassembled by [user].") + relay_qdel() + qdel(src) + return + if(istype(W, /obj/item/weapon/weldingtool)) + var/obj/item/weapon/weldingtool/WT = W + if(!WT.isOn()) + user << "\The [W] is off." + return + + if(!damage) + user << "\The [src] does not require repairs." + return + + user << "You begin repairing damage to \the [src]..." + if(WT.remove_fuel(round(damage/75)) && do_after(usr, damage/10)) + damage = 0 + user << "You repair \the [src]." + return + + if(istype(W, /obj/item/weapon/screwdriver)) + var/list/all_components = get_all_components() + if(!all_components.len) + user << "This device doesn't have any components installed." + return + var/list/component_names = list() + for(var/obj/item/weapon/computer_hardware/H in all_components) + component_names.Add(H.name) + + var/choice = input(usr, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in component_names + + if(!choice) + return + + if(!Adjacent(usr)) + return + + var/obj/item/weapon/computer_hardware/H = find_hardware_by_name(choice) + + if(!H) + return + + uninstall_component(user, H) + + return + + ..() + +// Used by processor to relay qdel() to machinery type. +/obj/item/modular_computer/proc/relay_qdel() + return + +// Uninstalls component. Found and Critical vars may be passed by parent types, if they have additional hardware. +/obj/item/modular_computer/proc/uninstall_component(mob/living/user, obj/item/weapon/computer_hardware/H, found = 0, critical = 0) + if(portable_drive == H) + portable_drive = null + found = 1 + if(hard_drive == H) + hard_drive = null + found = 1 + critical = 1 + if(network_card == H) + network_card = null + found = 1 + if(nano_printer == H) + nano_printer = null + found = 1 + if(card_slot == H) + card_slot = null + found = 1 + if(battery_module == H) + battery_module = null + found = 1 + if(processor_unit == H) + processor_unit = null + found = 1 + critical = 1 + if(found) + if(user) + user << "You remove \the [H] from \the [src]." + H.forceMove(get_turf(src)) + H.holder2 = null + if(critical && enabled) + if(user) + user << "\The [src]'s screen freezes for few seconds and then displays an \"HARDWARE ERROR: Critical component disconnected. Please verify component connection and reboot the device. If the problem persists contact technical support for assistance.\" warning." + shutdown_computer() + update_icon() + + +// Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null +/obj/item/modular_computer/proc/find_hardware_by_name(name) + if(portable_drive && (portable_drive.name == name)) + return portable_drive + if(hard_drive && (hard_drive.name == name)) + return hard_drive + if(network_card && (network_card.name == name)) + return network_card + if(nano_printer && (nano_printer.name == name)) + return nano_printer + if(card_slot && (card_slot.name == name)) + return card_slot + if(battery_module && (battery_module.name == name)) + return battery_module + if(processor_unit && (processor_unit.name == name)) + return processor_unit + return null + +// Returns list of all components +/obj/item/modular_computer/proc/get_all_components() + var/list/all_components = list() + if(hard_drive) + all_components.Add(hard_drive) + if(network_card) + all_components.Add(network_card) + if(portable_drive) + all_components.Add(portable_drive) + if(nano_printer) + all_components.Add(nano_printer) + if(card_slot) + all_components.Add(card_slot) + if(battery_module) + all_components.Add(battery_module) + if(processor_unit) + all_components.Add(processor_unit) + return all_components + +/obj/item/modular_computer/proc/take_damage(amount, component_probability, damage_casing = 1, randomize = 1) + if(randomize) + // 75%-125%, rand() works with integers, apparently. + amount *= (rand(75, 125) / 100.0) + amount = round(amount) + if(damage_casing) + damage += amount + damage = max(0,min(max_damage,damage)) + + if(component_probability) + for(var/I in get_all_components()) + var/obj/item/weapon/computer_hardware/H = I + if(prob(component_probability)) + H.take_damage(round(amount / 2)) + + if(damage >= max_damage) + break_apart() + +// Stronger explosions cause serious damage to internal components +// Minor explosions are mostly mitigitated by casing. +/obj/item/modular_computer/ex_act(severity) + take_damage(rand(100,200) / severity, 30 / severity) + +// EMPs are similar to explosions, but don't cause physical damage to the casing. Instead they screw up the components +/obj/item/modular_computer/emp_act(severity) + take_damage(rand(100,200) / severity, 50 / severity, 0) + +// "Stun" weapons can cause minor damage to components (short-circuits?) +// "Burn" damage is equally strong against internal components and exterior casing +// "Brute" damage mostly damages the casing. +/obj/item/modular_computer/bullet_act(obj/item/projectile/Proj) + switch(Proj.damage_type) + if(BRUTE) + take_damage(Proj.damage, Proj.damage / 2) + if(BURN) + take_damage(Proj.damage, Proj.damage / 1.5) + +/obj/item/modular_computer/ui_host() + if(physical) + return physical + else + return src \ No newline at end of file diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm new file mode 100644 index 00000000000..f9148b66596 --- /dev/null +++ b/code/modules/modular_computers/computers/item/processor.dm @@ -0,0 +1,119 @@ +// Held by /obj/machinery/modular_computer to reduce amount of copy-pasted code. +/obj/item/modular_computer/processor + name = "processing unit" + desc = "You shouldn't see this. If you do, report it." + icon = null + icon_state = null + icon_state_unpowered = null + icon_state_menu = null + hardware_flag = 0 + + var/obj/machinery/modular_computer/machinery_computer = null + +/obj/item/modular_computer/processor/Destroy() + . = ..() + if(machinery_computer && (machinery_computer.cpu == src)) + machinery_computer.cpu = null + machinery_computer = null + +// Due to how processes work, we'd receive two process calls - one from machinery type and one from our own type. +// Since we want this to be in-sync with machinery (as it's hidden type for machinery-based computers) we'll ignore +// non-relayed process calls. +/obj/item/modular_computer/processor/process(relayed = 0) + if(relayed) + ..() + else + return + +/obj/item/modular_computer/processor/examine(var/mob/user) + if(damage > broken_damage) + user << "It is heavily damaged!" + else if(damage) + user << "It is damaged." + +// Power interaction is handled by our machinery part, due to machinery having APC connection. +/obj/item/modular_computer/processor/handle_power() + if(machinery_computer) + machinery_computer.handle_power() + +/obj/item/modular_computer/processor/New(comp) + if(!comp || !istype(comp, /obj/machinery/modular_computer)) + CRASH("Inapropriate type passed to obj/item/modular_computer/processor/New()! Aborting.") + return + // Obtain reference to machinery computer + machinery_computer = comp + machinery_computer.cpu = src + hardware_flag = machinery_computer.hardware_flag + max_hardware_size = machinery_computer.max_hardware_size + steel_sheet_cost = machinery_computer.steel_sheet_cost + max_damage = machinery_computer._max_damage + broken_damage = machinery_computer._break_damage + +/obj/item/modular_computer/processor/relay_qdel() + qdel(machinery_computer) + +/obj/item/modular_computer/processor/find_hardware_by_name(N) + var/obj/item/weapon/computer_hardware/H = machinery_computer.find_hardware_by_name(N) + if(H) + return H + else + return ..() + +/obj/item/modular_computer/processor/update_icon() + if(machinery_computer) + return machinery_computer.update_icon() + +/obj/item/modular_computer/processor/get_header_data() + var/list/L = ..() + if(machinery_computer.tesla_link && machinery_computer.tesla_link.enabled && machinery_computer.powered()) + L["PC_apclinkicon"] = "charging.gif" + return L + +// Checks whether the machinery computer doesn't take power from APC network +/obj/item/modular_computer/processor/check_power_override() + if(!machinery_computer) + return 0 + if(!machinery_computer.tesla_link || !machinery_computer.tesla_link.enabled) + return 0 + return machinery_computer.powered() + +// This thing is not meant to be used on it's own, get topic data from our machinery owner. +//obj/item/modular_computer/processor/canUseTopic(user, state) +// if(!machinery_computer) +// return 0 + +// return machinery_computer.canUseTopic(user, state) + +/obj/item/modular_computer/processor/shutdown_computer() + if(!machinery_computer) + return + ..() + machinery_computer.update_icon() + machinery_computer.use_power = 0 + return + +/obj/item/modular_computer/processor/uninstall_component(mob/living/user, obj/item/weapon/computer_hardware/H, found = 0, critical = 0) + if(machinery_computer.tesla_link == H) + machinery_computer.tesla_link = null + var/obj/item/weapon/computer_hardware/tesla_link/L = H + L.holder = null + found = 1 + ..(user, H, found, critical) + +/obj/item/modular_computer/processor/get_all_components() + var/list/all_components = ..() + if(machinery_computer && machinery_computer.tesla_link) + all_components.Add(machinery_computer.tesla_link) + return all_components + +// Perform adjacency checks on our machinery counterpart, rather than on ourselves. +/obj/item/modular_computer/processor/Adjacent(atom/neighbor) + if(!machinery_computer) + return 0 + return machinery_computer.Adjacent(neighbor) + +/obj/item/modular_computer/processor/turn_on(mob/user) + // If we have a tesla link on our machinery counterpart, enable it automatically. Lets computer without a battery work. + if(machinery_computer && machinery_computer.tesla_link) + machinery_computer.tesla_link.enabled = 1 + ..() \ No newline at end of file diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm new file mode 100644 index 00000000000..d03c25cca12 --- /dev/null +++ b/code/modules/modular_computers/computers/item/tablet.dm @@ -0,0 +1,10 @@ +/obj/item/modular_computer/tablet + name = "tablet computer" + icon = 'icons/obj/modular_tablet.dmi' + icon_state = "tablet" + icon_state_unpowered = "tablet" + icon_state_menu = "menu" + hardware_flag = PROGRAM_TABLET + max_hardware_size = 1 + w_class = 2 + var/use_power = 0 \ No newline at end of file diff --git a/code/modules/modular_computers/computers/item/tablet_presets.dm b/code/modules/modular_computers/computers/item/tablet_presets.dm new file mode 100644 index 00000000000..ffcb95ce57a --- /dev/null +++ b/code/modules/modular_computers/computers/item/tablet_presets.dm @@ -0,0 +1,21 @@ + +// Available as custom loadout item, this is literally the worst possible cheap tablet +/obj/item/modular_computer/tablet/preset/cheap/New() + . = ..() + desc = "A low-end tablet often seen among low ranked station personnel." + processor_unit = new/obj/item/weapon/computer_hardware/processor_unit/small(src) + battery_module = new/obj/item/weapon/computer_hardware/battery_module/micro(src) + battery_module.charge_to_full() + hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/micro(src) + network_card = new/obj/item/weapon/computer_hardware/network_card(src) + +// Alternative version, an average one, for higher ranked positions mostly +/obj/item/modular_computer/tablet/preset/advanced/New() + . = ..() + processor_unit = new/obj/item/weapon/computer_hardware/processor_unit/small(src) + battery_module = new/obj/item/weapon/computer_hardware/battery_module(src) + battery_module.charge_to_full() + hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/small(src) + network_card = new/obj/item/weapon/computer_hardware/network_card(src) + nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(src) + card_slot = new/obj/item/weapon/computer_hardware/card_slot(src) \ No newline at end of file diff --git a/code/modules/modular_computers/computers/machinery/console_presets.dm b/code/modules/modular_computers/computers/machinery/console_presets.dm new file mode 100644 index 00000000000..0f6aec6558b --- /dev/null +++ b/code/modules/modular_computers/computers/machinery/console_presets.dm @@ -0,0 +1,65 @@ +/obj/machinery/modular_computer/console/preset/ + // Can be changed to give devices specific hardware + var/_has_id_slot = 0 + var/_has_printer = 0 + var/_has_battery = 0 + +/obj/machinery/modular_computer/console/preset/New() + . = ..() + if(!cpu) + return + cpu.processor_unit = new/obj/item/weapon/computer_hardware/processor_unit(cpu) + if(_has_id_slot) + cpu.card_slot = new/obj/item/weapon/computer_hardware/card_slot(cpu) + if(_has_printer) + cpu.nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(cpu) + if(_has_battery) + cpu.battery_module = new/obj/item/weapon/computer_hardware/battery_module/super(cpu) + install_programs() + +// Override in child types to install preset-specific programs. +/obj/machinery/modular_computer/console/preset/proc/install_programs() + return + + + +// ===== ENGINEERING CONSOLE ===== +/obj/machinery/modular_computer/console/preset/engineering + console_department = "Engineering" + desc = "A stationary computer. This one comes preloaded with engineering programs." + +/obj/machinery/modular_computer/console/preset/engineering/install_programs() + cpu.hard_drive.store_file(new/datum/computer_file/program/power_monitor()) + cpu.hard_drive.store_file(new/datum/computer_file/program/alarm_monitor()) + +// ===== RESEARCH CONSOLE ===== +/obj/machinery/modular_computer/console/preset/research + console_department = "Research" + desc = "A stationary computer. This one comes preloaded with research programs." + +/obj/machinery/modular_computer/console/preset/research/install_programs() + cpu.hard_drive.store_file(new/datum/computer_file/program/ntnetmonitor()) + cpu.hard_drive.store_file(new/datum/computer_file/program/nttransfer()) + cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) + + +// ===== COMMAND CONSOLE ===== +/obj/machinery/modular_computer/console/preset/command + console_department = "Command" + desc = "A stationary computer. This one comes preloaded with command programs." + _has_id_slot = 1 + _has_printer = 1 + +/obj/machinery/modular_computer/console/preset/command/install_programs() + cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) + cpu.hard_drive.store_file(new/datum/computer_file/program/card_mod()) + +// ===== CIVILIAN CONSOLE ===== +/obj/machinery/modular_computer/console/preset/civilian + console_department = "Civilian" + desc = "A stationary computer. This one comes preloaded with generic programs." + +/obj/machinery/modular_computer/console/preset/civilian/install_programs() + cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient()) + cpu.hard_drive.store_file(new/datum/computer_file/program/nttransfer()) + diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm new file mode 100644 index 00000000000..98b29951607 --- /dev/null +++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm @@ -0,0 +1,207 @@ +// Global var to track modular computers +var/list/global_modular_computers = list() + +// Modular Computer - device that runs various programs and operates with hardware +// DO NOT SPAWN THIS TYPE. Use /laptop/ or /console/ instead. +/obj/machinery/modular_computer/ + name = "modular computer" + desc = "An advanced computer." + + var/battery_powered = 0 // Whether computer should be battery powered. It is set automatically + use_power = 0 + var/hardware_flag = 0 // A flag that describes this device type + var/last_power_usage = 0 // Power usage during last tick + + // Modular computers can run on various devices. Each DEVICE (Laptop, Console, Tablet,..) + // must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently + // If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example. + + icon = null + icon_state = null + var/icon_state_unpowered = null // Icon state when the computer is turned off + var/screen_icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen. + var/screen_icon_screensaver = "standby" // Icon state overlay when the computer is powered, but not 'switched on'. + var/max_hardware_size = 0 // Maximal hardware size. Currently, tablets have 1, laptops 2 and consoles 3. Limits what hardware types can be installed. + var/steel_sheet_cost = 10 // Amount of steel sheets refunded when disassembling an empty frame of this computer. + var/light_strength = 0 // Light luminosity when turned on + var/base_active_power_usage = 100 // Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too. + var/base_idle_power_usage = 10 // Power usage when the computer is idle and screen is off (currently only applies to laptops) + + var/_max_damage = 100 + var/_break_damage = 50 + + var/obj/item/weapon/computer_hardware/tesla_link/tesla_link // Tesla Link component of this computer. Allows remote charging from nearest APC. + + var/obj/item/modular_computer/processor/cpu = null // CPU that handles most logic while this type only handles power and other specific things. + +/obj/machinery/modular_computer/attack_ghost(mob/dead/observer/user) + if(cpu) + cpu.attack_ghost(user) + +/obj/machinery/modular_computer/emag_act(mob/user) + return cpu ? cpu.emag_act(user) : 1 + +/obj/machinery/modular_computer/update_icon() + icon_state = icon_state_unpowered + cut_overlays() + + if(!cpu || !cpu.enabled) + if (!(stat & NOPOWER) || battery_powered) + add_overlay(screen_icon_screensaver) + SetLuminosity(0) + return + SetLuminosity(light_strength) + if(cpu.active_program) + add_overlay(cpu.active_program.program_icon_state ? cpu.active_program.program_icon_state : screen_icon_state_menu) + else + overlays.Add(screen_icon_state_menu) + +// Eject ID card from computer, if it has ID slot with card inside. +/obj/machinery/modular_computer/verb/eject_id() + set name = "Eject ID" + set category = "Object" + set src in view(1) + + if(cpu) + cpu.eject_id() + +// Eject ID card from computer, if it has ID slot with card inside. +/obj/machinery/modular_computer/verb/eject_usb() + set name = "Eject Portable Device" + set category = "Object" + set src in view(1) + + if(cpu) + cpu.eject_usb() + +/obj/machinery/modular_computer/New() + ..() + cpu = new(src) + cpu.physical = src + global_modular_computers.Add(src) + +/obj/machinery/modular_computer/Destroy() + if(cpu) + qdel(cpu) + cpu = null + return ..() + +// On-click handling. Turns on the computer if it's off and opens the GUI. +/obj/machinery/modular_computer/attack_hand(mob/user) + if(cpu) + cpu.attack_self(user) // CPU is an item, that's why we route attack_hand to attack_self + +/obj/machinery/modular_computer/examine(mob/user) + . = ..() + if(cpu) + cpu.examine(user) + +// Process currently calls handle_power(), may be expanded in future if more things are added. +/obj/machinery/modular_computer/process() + if(cpu) + // Keep names in sync. + cpu.name = src.name + cpu.process(1) + +// Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null +/obj/machinery/modular_computer/proc/find_hardware_by_name(N) + if(tesla_link && (tesla_link.name == N)) + return tesla_link + return null + +// Used in following function to reduce copypaste +/obj/machinery/modular_computer/proc/power_failure(malfunction = 0) + if(cpu && cpu.enabled) // Shut down the computer + visible_message("\The [src]'s screen flickers [cpu.battery_module ? "\"BATTERY [malfunction ? "MALFUNCTION" : "CRITICAL"]\"" : "\"EXTERNAL POWER LOSS\""] warning as it shuts down unexpectedly.") + if(cpu) + cpu.shutdown_computer(0) + battery_powered = 0 + stat |= NOPOWER + update_icon() + +// Called by cpu item's process() automatically, handles our power interaction. +/obj/machinery/modular_computer/proc/handle_power() + if(!check_external_power()) + if(cpu.battery_module && cpu.battery_module.battery.charge <= 0)// Battery-run but battery is depleted. + power_failure() + return 0 + else if(!cpu.battery_module) // Not battery run, but lacking APC connection. + power_failure() + return 0 + else if(stat & NOPOWER) + stat &= ~NOPOWER + + if(cpu.battery_module && cpu.battery_module.battery.charge) + battery_powered = 1 + else + battery_powered = 0 + + var/power_usage = cpu.screen_on ? base_active_power_usage : base_idle_power_usage + for(var/C in src.cpu.get_all_components()) + var/obj/item/weapon/computer_hardware/CH = C + if(CH.enabled) + power_usage += CH.power_usage + + // Wireless APC connection exists. + if(tesla_link && tesla_link.enabled && tesla_link.check_functionality()) + idle_power_usage = power_usage + active_power_usage = idle_power_usage + 100 // APCLink only charges at 100W rate, but covers any power usage. + use_power = 1 + // Battery is not fully charged. Begin slowly recharging. + if(cpu.battery_module && (cpu.battery_module.battery.charge < cpu.battery_module.battery.maxcharge)) + use_power = 2 + + if(cpu.battery_module && powered() && (use_power == 2)) // Battery charging itself + cpu.battery_module.battery.give(100 * CELLRATE) + else if(cpu.battery_module && !powered()) // Unpowered, but battery covers the usage. + if(!cpu.battery_module.battery.use(idle_power_usage * CELLRATE)) + cpu.battery_module.battery.charge = 0 + power_failure() + + else // No wireless connection run only on battery. + use_power = 0 + if (cpu.battery_module) + if(!cpu.battery_module.check_functionality()) + power_failure(1) + return + if(!cpu.battery_module.battery.use(power_usage * CELLRATE)) + cpu.battery_module.battery.charge = 0 + power_failure() + cpu.last_power_usage = power_usage + +// Modular computers can have battery in them, we handle power in previous proc, so prevent this from messing it up for us. +/obj/machinery/modular_computer/power_change() + if(battery_powered) + return + ..() + +/obj/machinery/modular_computer/attackby(var/obj/item/weapon/W as obj, mob/user) + if(cpu) + return cpu.attackby(W, user) + return ..() + + +// Stronger explosions cause serious damage to internal components +// Minor explosions are mostly mitigitated by casing. +/obj/machinery/modular_computer/ex_act(severity) + if(cpu) + cpu.ex_act(severity) + +// EMPs are similar to explosions, but don't cause physical damage to the casing. Instead they screw up the components +/obj/machinery/modular_computer/emp_act(severity) + if(cpu) + cpu.emp_act(severity) + +// "Stun" weapons can cause minor damage to components (short-circuits?) +// "Burn" damage is equally strong against internal components and exterior casing +// "Brute" damage mostly damages the casing. +/obj/machinery/modular_computer/bullet_act(obj/item/projectile/Proj) + if(cpu) + cpu.bullet_act(Proj) + + +/obj/machinery/modular_computer/proc/check_external_power() + if((!powered() || !tesla_link || !tesla_link.enabled || !tesla_link.check_functionality())) + return 0 + else + return 1 \ No newline at end of file diff --git a/code/modules/modular_computers/computers/machinery/modular_console.dm b/code/modules/modular_computers/computers/machinery/modular_console.dm new file mode 100644 index 00000000000..79a7c913c2e --- /dev/null +++ b/code/modules/modular_computers/computers/machinery/modular_console.dm @@ -0,0 +1,46 @@ +/obj/machinery/modular_computer/console/ + name = "console" + desc = "A stationary computer." + + icon = 'icons/obj/modular_console.dmi' + icon_state = "console" + icon_state_unpowered = "console" + screen_icon_state_menu = "menu" + hardware_flag = PROGRAM_CONSOLE + var/console_department = "" // Used in New() to set network tag according to our area. + anchored = 1 + density = 1 + base_idle_power_usage = 100 + base_active_power_usage = 500 + max_hardware_size = 3 + steel_sheet_cost = 20 + light_strength = 2 + _max_damage = 300 + _break_damage = 150 + +/obj/machinery/modular_computer/console/buildable/New() + ..() + // User-built consoles start as empty frames. + qdel(tesla_link) + qdel(cpu.network_card) + qdel(cpu.hard_drive) + +/obj/machinery/modular_computer/console/New() + ..() + cpu.battery_module = null + cpu.network_card = new/obj/item/weapon/computer_hardware/network_card/wired(src) + tesla_link = new/obj/item/weapon/computer_hardware/tesla_link(src) + cpu.hard_drive = new/obj/item/weapon/computer_hardware/hard_drive/super(src) // Consoles generally have better HDDs due to lower space limitations + var/area/A = get_area(src) + // Attempts to set this console's tag according to our area. Since some areas have stuff like "XX - YY" in their names we try to remove that too. + if(A && console_department) + cpu.network_card.identification_string = replacetext(replacetext(replacetext("[A.name] [console_department] Console", " ", "_"), "-", ""), "__", "_") // Replace spaces with "_" + else if(A) + cpu.network_card.identification_string = replacetext(replacetext(replacetext("[A.name] Console", " ", "_"), "-", ""), "__", "_") + else if(console_department) + cpu.network_card.identification_string = replacetext(replacetext(replacetext("[console_department] Console", " ", "_"), "-", ""), "__", "_") + else + cpu.network_card.identification_string = "Unknown Console" + if(cpu) + cpu.screen_on = 1 + update_icon() \ No newline at end of file diff --git a/code/modules/modular_computers/computers/machinery/modular_laptop.dm b/code/modules/modular_computers/computers/machinery/modular_laptop.dm new file mode 100644 index 00000000000..55cb385a4e1 --- /dev/null +++ b/code/modules/modular_computers/computers/machinery/modular_laptop.dm @@ -0,0 +1,110 @@ +// Laptop in it's item state, can be carried around. + +/obj/item/laptop + name = "laptop computer" + desc = "A portable computer. It is closed." + icon = 'icons/obj/modular_laptop.dmi' + icon_state = "laptop-closed" + item_state = "laptop-inhand" + w_class = 3 + var/obj/machinery/modular_computer/laptop/stored_computer = null + +/obj/item/laptop/verb/open_computer() + set name = "Open Laptop" + set category = "Object" + set src in view(1) + + if(usr.stat || usr.restrained() || usr.lying || !istype(usr, /mob/living)) + usr << "You can't do that." + return + + if(!Adjacent(usr)) + usr << "You can't reach it." + return + + if(!istype(loc,/turf)) + usr << "[src] is too bulky! You'll have to set it down." + return + + if(!stored_computer) + if(contents.len) + for(var/obj/O in contents) + O.forceMove(src.loc) + usr << "\The [src] crumbles to pieces." + spawn(5) + qdel(src) + return + + stored_computer.forceMove(src.loc) + stored_computer.stat &= ~MAINT + stored_computer.update_icon() + if(stored_computer.cpu) + stored_computer.cpu.screen_on = 1 + loc = stored_computer + usr << "You open \the [src]." + + +/obj/item/laptop/AltClick() + if(Adjacent(usr)) + open_computer() + +// The actual laptop +/obj/machinery/modular_computer/laptop + name = "laptop computer" + desc = "A portable computer." + var/obj/item/laptop/portable = null // Portable version of this computer, dropped on alt-click to allow transport. Used by laptops. + hardware_flag = PROGRAM_LAPTOP + icon_state_unpowered = "laptop-open" // Icon state when the computer is turned off + icon = 'icons/obj/modular_laptop.dmi' + icon_state = "laptop-open" + base_idle_power_usage = 25 + base_active_power_usage = 200 + max_hardware_size = 2 + light_strength = 3 + _max_damage = 200 + _break_damage = 100 + +/obj/machinery/modular_computer/laptop/buildable/New() + ..() + // User-built consoles start as empty frames. + qdel(tesla_link) + qdel(cpu.network_card) + qdel(cpu.hard_drive) + +// Close the computer. collapsing it into movable item that can't be used. +/obj/machinery/modular_computer/laptop/verb/close_computer() + set name = "Close Laptop" + set category = "Object" + set src in view(1) + + if(usr.stat || usr.restrained() || usr.lying || !istype(usr, /mob/living)) + usr << "You can't do that." + return + + if(!Adjacent(usr)) + usr << "You can't reach it." + return + + close_laptop(usr) + +/obj/machinery/modular_computer/laptop/proc/close_laptop(mob/user = null) + if(istype(loc,/obj/item/laptop)) + return + if(!istype(loc,/turf)) + return + + if(!portable) + portable=new + portable.stored_computer = src + + portable.forceMove(src.loc) + src.forceMove(portable) + stat |= MAINT + if(user) + user << "You close \the [src]." + if(cpu) + cpu.screen_on = 0 + +/obj/machinery/modular_computer/laptop/AltClick() + if(Adjacent(usr)) + close_laptop() \ No newline at end of file diff --git a/code/modules/modular_computers/computers/status_icons.dmi b/code/modules/modular_computers/computers/status_icons.dmi new file mode 100644 index 00000000000..52127ec4842 Binary files /dev/null and b/code/modules/modular_computers/computers/status_icons.dmi differ diff --git a/code/modules/modular_computers/file_system/computer_file.dm b/code/modules/modular_computers/file_system/computer_file.dm new file mode 100644 index 00000000000..c853e7ce0fd --- /dev/null +++ b/code/modules/modular_computers/file_system/computer_file.dm @@ -0,0 +1,39 @@ +var/global/file_uid = 0 + +/datum/computer_file/ + var/filename = "NewFile" // Placeholder. No spacebars + var/filetype = "XXX" // File full names are [filename].[filetype] so like NewFile.XXX in this case + var/size = 1 // File size in GQ. Integers only! + var/obj/item/weapon/computer_hardware/hard_drive/holder // Holder that contains this file. + var/unsendable = 0 // Whether the file may be sent to someone via NTNet transfer or other means. + var/undeletable = 0 // Whether the file may be deleted. Setting to 1 prevents deletion/renaming/etc. + var/uid // UID of this file + +/datum/computer_file/New() + ..() + uid = file_uid + file_uid++ + +/datum/computer_file/Destroy() + if(!holder) + return ..() + + holder.remove_file(src) + // holder.holder is the computer that has drive installed. If we are Destroy()ing program that's currently running kill it. + if(holder.holder2 && holder.holder2.active_program == src) + holder.holder2.kill_program(1) + holder = null + ..() + +// Returns independent copy of this file. +/datum/computer_file/proc/clone(rename = 0) + var/datum/computer_file/temp = new type + temp.unsendable = unsendable + temp.undeletable = undeletable + temp.size = size + if(rename) + temp.filename = filename + "(Copy)" + else + temp.filename = filename + temp.filetype = filetype + return temp \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/data.dm b/code/modules/modular_computers/file_system/data.dm new file mode 100644 index 00000000000..32ef6f53dd1 --- /dev/null +++ b/code/modules/modular_computers/file_system/data.dm @@ -0,0 +1,20 @@ +// /data/ files store data in string format. +// They don't contain other logic for now. +/datum/computer_file/data + var/stored_data = "" // Stored data in string format. + filetype = "DAT" + var/block_size = 250 + var/do_not_edit = 0 // Whether the user will be reminded that the file probably shouldn't be edited. + +/datum/computer_file/data/clone() + var/datum/computer_file/data/temp = ..() + temp.stored_data = stored_data + return temp + +// Calculates file size from amount of characters in saved string +/datum/computer_file/data/proc/calculate_size() + size = max(1, round(length(stored_data) / block_size)) + +/datum/computer_file/data/logfile + filetype = "LOG" + diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm new file mode 100644 index 00000000000..87aa3dab1ab --- /dev/null +++ b/code/modules/modular_computers/file_system/program.dm @@ -0,0 +1,190 @@ +// /program/ files are executable programs that do things. +/datum/computer_file/program + filetype = "PRG" + filename = "UnknownProgram" // File name. FILE NAME MUST BE UNIQUE IF YOU WANT THE PROGRAM TO BE DOWNLOADABLE FROM NTNET! + var/required_access = null // List of required accesses to *run* the program. + var/transfer_access = null // List of required access to download or file host the program + var/program_state = PROGRAM_STATE_KILLED// PROGRAM_STATE_KILLED or PROGRAM_STATE_BACKGROUND or PROGRAM_STATE_ACTIVE - specifies whether this program is running. + var/obj/item/modular_computer/computer // Device that runs this program. + var/filedesc = "Unknown Program" // User-friendly name of this program. + var/extended_desc = "N/A" // Short description of this program's function. + var/program_icon_state = null // Program-specific screen icon state + var/requires_ntnet = 0 // Set to 1 for program to require nonstop NTNet connection to run. If NTNet connection is lost program crashes. + var/requires_ntnet_feature = 0 // Optional, if above is set to 1 checks for specific function of NTNet (currently NTNET_SOFTWAREDOWNLOAD, NTNET_PEERTOPEER, NTNET_SYSTEMCONTROL and NTNET_COMMUNICATION) + var/ntnet_status = 1 // NTNet status, updated every tick by computer running this program. Don't use this for checks if NTNet works, computers do that. Use this for calculations, etc. + var/usage_flags = PROGRAM_ALL // Bitflags (PROGRAM_CONSOLE, PROGRAM_LAPTOP, PROGRAM_TABLET combination) or PROGRAM_ALL + var/network_destination = null // Optional string that describes what NTNet server/system this program connects to. Used in default logging. + var/available_on_ntnet = 1 // Whether the program can be downloaded from NTNet. Set to 0 to disable. + var/available_on_syndinet = 0 // Whether the program can be downloaded from SyndiNet (accessible via emagging the computer). Set to 1 to enable. + var/computer_emagged = 0 // Set to 1 if computer that's running us was emagged. Computer updates this every Process() tick + var/ui_header = null // Example: "something.gif" - a header image that will be rendered in computer's UI when this program is running at background. Images are taken from /icons/program_icons. Be careful not to use too large images! + +/datum/computer_file/program/New(obj/item/modular_computer/comp = null) + ..() + if(comp && istype(comp)) + computer = comp + +/datum/computer_file/program/Destroy() + computer = null + . = ..() + +/datum/computer_file/program/clone() + var/datum/computer_file/program/temp = ..() + temp.required_access = required_access + temp.filedesc = filedesc + temp.program_icon_state = program_icon_state + temp.requires_ntnet = requires_ntnet + temp.requires_ntnet_feature = requires_ntnet_feature + temp.usage_flags = usage_flags + return temp + +// Relays icon update to the computer. +/datum/computer_file/program/proc/update_computer_icon() + if(computer) + computer.update_icon() + +// Attempts to create a log in global ntnet datum. Returns 1 on success, 0 on fail. +/datum/computer_file/program/proc/generate_network_log(text) + if(computer) + return computer.add_log(text) + return 0 + +/datum/computer_file/program/proc/is_supported_by_hardware(hardware_flag = 0, loud = 0, mob/user = null) + if(!(hardware_flag & usage_flags)) + if(loud && computer && user) + user << "\The [computer] flashes an \"Hardware Error - Incompatible software\" warning." + return 0 + return 1 + +/datum/computer_file/program/proc/get_signal(specific_action = 0) + if(computer) + return computer.get_ntnet_status(specific_action) + return 0 + +// Called by Process() on device that runs us, once every tick. +/datum/computer_file/program/proc/process_tick() + return 1 + +// Check if the user can run program. Only humans can operate computer. Automatically called in run_program() +// User has to wear their ID for ID Scan to work. +// Can also be called manually, with optional parameter being access_to_check to scan the user's ID +/datum/computer_file/program/proc/can_run(mob/user, loud = 0, access_to_check, transfer = 0) + // Defaults to required_access + if(!access_to_check) + if(transfer && transfer_access) + access_to_check = transfer_access + else + access_to_check = required_access + if(!access_to_check) // No required_access, allow it. + return 1 + + if(computer_emagged && !transfer) //emags can bypass the execution locks but not the download ones. + return 1 + + if(IsAdminGhost(user)) + return 1 + + if(issilicon(user)) + return 1 + + if(ishuman(user)) + var/mob/living/carbon/human/h = user + var/obj/item/weapon/card/id/I = h.get_idcard() + var/obj/item/weapon/card/id/C = h.get_active_hand() + if (istype(C, /obj/item/device/pda)) + var/obj/item/device/pda/pda = C + C = pda.id + if(!(C && istype(C))) + C = null + + if(!I && !C) + if(loud) + user << "\The [computer] flashes an \"RFID Error - Unable to scan ID\" warning." + return 0 + + if(I) + if(access_to_check in I.GetAccess()) + return 1 + else if(C) + if(access_to_check in C.GetAccess()) + return 1 + if(loud) + user << "\The [computer] flashes an \"Access Denied\" warning." + return 0 + +// This attempts to retrieve header data for UIs. If implementing completely new device of different type than existing ones +// always include the device here in this proc. This proc basically relays the request to whatever is running the program. +/datum/computer_file/program/proc/get_header_data() + if(computer) + return computer.get_header_data() + return list() + +// This is performed on program startup. May be overriden to add extra logic. Remember to include ..() call. Return 1 on success, 0 on failure. +// When implementing new program based device, use this to run the program. +/datum/computer_file/program/proc/run_program(mob/living/user) + if(can_run(user, 1)) + if(requires_ntnet && network_destination) + generate_network_log("Connection opened to [network_destination].") + program_state = PROGRAM_STATE_ACTIVE + return 1 + return 0 + +// Use this proc to kill the program. Designed to be implemented by each program if it requires on-quit logic, such as the NTNRC client. +/datum/computer_file/program/proc/kill_program(forced = 0) + program_state = PROGRAM_STATE_KILLED + if(network_destination) + generate_network_log("Connection to [network_destination] closed.") + return 1 + +// This is called every tick when the program is enabled. Ensure you do parent call if you override it. If parent returns 1 continue with UI initialisation. + +/datum/computer_file/program/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + if(program_state != PROGRAM_STATE_ACTIVE) // Our program was closed. Close the ui if it exists. + return computer.ui_interact(user) + return 1 + + +// CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC: +// Topic calls are automagically forwarded from NanoModule this program contains. +// Calls beginning with "PRG_" are reserved for programs handling. +// Calls beginning with "PC_" are reserved for computer handling (by whatever runs the program) +// ALWAYS INCLUDE PARENT CALL ..() OR DIE IN FIRE. +/datum/computer_file/program/ui_act(action,params,datum/tgui/ui) + if(..()) + return 1 + if(computer) + switch(action) + if("PC_exit") + computer.kill_program() + ui.close() + return 1 + if("PC_shutdown") + computer.shutdown_computer() + ui.close() + return 1 + if("PC_minimize") + var/mob/user = usr + if(!computer.active_program || !computer.processor_unit) + return + + computer.idle_threads.Add(computer.active_program) + program_state = PROGRAM_STATE_BACKGROUND // Should close any existing UIs + + computer.active_program = null + computer.update_icon() + ui.close() + + if(user && istype(user)) + computer.ui_interact(user) // Re-open the UI on this computer. It should show the main screen now. + + +/datum/computer_file/program/ui_host() + if(computer.physical) + return computer.physical + else + return computer + +/datum/computer_file/program/ui_status(mob/user) + if(program_state != PROGRAM_STATE_ACTIVE) // Our program was closed. Close the ui if it exists. + return UI_CLOSE + return ..() diff --git a/code/modules/modular_computers/file_system/program_events.dm b/code/modules/modular_computers/file_system/program_events.dm new file mode 100644 index 00000000000..996d6393722 --- /dev/null +++ b/code/modules/modular_computers/file_system/program_events.dm @@ -0,0 +1,18 @@ +// Events are sent to the program by the computer. +// Always include a parent call when overriding an event. + +// Called when the ID card is removed from computer. ID is removed AFTER this proc. +/datum/computer_file/program/proc/event_idremoved(background, slot) + return + +// Called when the computer fails due to power loss. Override when program wants to specifically react to power loss. +/datum/computer_file/program/proc/event_powerfailure(background) + kill_program(1) + +// Called when the network connectivity fails. Computer does necessary checks and only calls this when requires_ntnet_feature and similar variables are not met. +/datum/computer_file/program/proc/event_networkfailure(background) + kill_program(1) + if(background) + computer.visible_message("\The [computer]'s screen displays an \"Process [filename].[filetype] (PID [rand(100,999)]) terminated - Network Error\" error") + else + computer.visible_message("\The [computer]'s screen briefly freezes and then shows \"NETWORK ERROR - NTNet connection lost. Please retry. If problem persists contact your system administrator.\" error.") diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm new file mode 100644 index 00000000000..3a3c9d72e78 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/alarm.dm @@ -0,0 +1,106 @@ + + + +/datum/computer_file/program/alarm_monitor + filename = "alarmmonitor" + filedesc = "Alarm Monitoring" + ui_header = "alarm_green.gif" + program_icon_state = "alert-green" + extended_desc = "This program provides visual interface for station's alarm system." + requires_ntnet = 1 + network_destination = "alarm monitoring network" + size = 5 + var/has_alert = 0 + var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) + +/datum/computer_file/program/alarm_monitor/process_tick() + ..() + + if(has_alert) + program_icon_state = "alert-red" + ui_header = "alarm_red.gif" + update_computer_icon() + else + if(!has_alert) + program_icon_state = "alert-green" + ui_header = "alarm_green.gif" + update_computer_icon() + return 1 + + + +/datum/computer_file/program/alarm_monitor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \ + datum/tgui/master_ui = null, datum/ui_state/state = default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "station_alert_prog", "Alarm Monitoring", 300, 500, master_ui, state) + ui.open() + +/datum/computer_file/program/alarm_monitor/ui_data(mob/user) + var/list/data = get_header_data() + + data["alarms"] = list() + for(var/class in alarms) + data["alarms"][class] = list() + for(var/area in alarms[class]) + data["alarms"][class] += area + + return data + +/datum/computer_file/program/alarm_monitor/proc/triggerAlarm(class, area/A, O, obj/source) + + var/list/L = alarms[class] + for(var/I in L) + if (I == A.name) + var/list/alarm = L[I] + var/list/sources = alarm[3] + if (!(source in sources)) + sources += source + return 1 + var/obj/machinery/camera/C = null + var/list/CL = null + if(O && istype(O, /list)) + CL = O + if (CL.len == 1) + C = CL[1] + else if(O && istype(O, /obj/machinery/camera)) + C = O + L[A.name] = list(A, (C ? C : O), list(source)) + + update_alarm_display() + + return 1 + + +/datum/computer_file/program/alarm_monitor/proc/cancelAlarm(class, area/A, obj/origin) + + + var/list/L = alarms[class] + var/cleared = 0 + for (var/I in L) + if (I == A.name) + var/list/alarm = L[I] + var/list/srcs = alarm[3] + if (origin in srcs) + srcs -= origin + if (srcs.len == 0) + cleared = 1 + L -= I + + update_alarm_display() + return !cleared + +/datum/computer_file/program/alarm_monitor/proc/update_alarm_display() + has_alert = FALSE + for(var/cat in alarms) + var/list/L = alarms[cat] + if(L.len) + has_alert = TRUE + +/datum/computer_file/program/alarm_monitor/run_program(mob/user) + . = ..(user) + alarmdisplay += src + +/datum/computer_file/program/alarm_monitor/kill_program(forced = 0) + alarmdisplay -= src + ..(forced) \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm new file mode 100644 index 00000000000..a3eb9a27354 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm @@ -0,0 +1,106 @@ +/datum/computer_file/program/ntnet_dos + filename = "ntn_dos" + filedesc = "DoS Traffic Generator" + program_icon_state = "hostile" + extended_desc = "This advanced script can perform denial of service attacks against NTNet quantum relays. The system administrator will probably notice this. Multiple devices can run this program together against same relay for increased effect" + size = 20 + requires_ntnet = 1 + available_on_ntnet = 0 + available_on_syndinet = 1 + var/obj/machinery/ntnet_relay/target = null + var/dos_speed = 0 + var/error = "" + var/executed = 0 + +/datum/computer_file/program/ntnet_dos/process_tick() + dos_speed = 0 + switch(ntnet_status) + if(1) + dos_speed = NTNETSPEED_LOWSIGNAL * 10 + if(2) + dos_speed = NTNETSPEED_HIGHSIGNAL * 10 + if(3) + dos_speed = NTNETSPEED_ETHERNET * 10 + if(target && executed) + target.dos_overload += dos_speed + if(!target.is_operational()) + target.dos_sources.Remove(src) + target = null + error = "Connection to destination relay lost." + +/datum/computer_file/program/ntnet_dos/kill_program(var/forced) + if(target) + target.dos_sources.Remove(src) + target = null + executed = 0 + + ..(forced) + + +/datum/computer_file/program/ntnet_dos/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + ui = new(user, src, ui_key, "ntnet_dos", "DoS Traffic Generator", 400, 250, state = state) + ui.set_style("syndicate") + ui.set_autoupdate(state = 1) + ui.open() + + + +/datum/computer_file/program/ntnet_dos/ui_act(action, params) + if(..()) + return 1 + switch(action) + if("PRG_target_relay") + for(var/obj/machinery/ntnet_relay/R in ntnet_global.relays) + if("[R.uid]" == params["targid"]) + target = R + return 1 + if("PRG_reset") + if(target) + target.dos_sources.Remove(src) + target = null + executed = 0 + error = "" + return 1 + if("PRG_execute") + if(target) + executed = 1 + target.dos_sources.Add(src) + if(ntnet_global.intrusion_detection_enabled) + ntnet_global.add_log("IDS WARNING - Excess traffic flood targeting relay [target.uid] detected from device: [computer.network_card.get_network_tag()]") + ntnet_global.intrusion_detection_alarm = 1 + return 1 + +/datum/computer_file/program/ntnet_dos/ui_data(mob/user) + if(!ntnet_global) + return + + var/list/data = list() + + data = get_header_data() + + if(error) + data["error"] = error + else if(target && executed) + data["target"] = 1 + data["speed"] = dos_speed + + // This is mostly visual, generate some strings of 1s and 0s + // Probability of 1 is equal of completion percentage of DoS attack on this relay. + // Combined with UI updates this adds quite nice effect to the UI + var/percentage = target.dos_overload * 100 / target.dos_capacity + data["dos_strings"] = list() + for(var/j, j<10, j++) + var/string = "" + for(var/i, i<20, i++) + string = "[string][prob(percentage)]" + data["dos_strings"] += list(list("nums" = string)) + else + data["relays"] = list() + for(var/obj/machinery/ntnet_relay/R in ntnet_global.relays) + data["relays"] += list(list("id" = R.uid)) + data["focus"] = target ? target.uid : null + + return data \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm new file mode 100644 index 00000000000..91b61d37836 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm @@ -0,0 +1,73 @@ +/datum/computer_file/program/revelation + filename = "revelation" + filedesc = "Revelation" + program_icon_state = "hostile" + extended_desc = "This virus can destroy hard drive of system it is executed on. It may be obfuscated to look like another non-malicious program. Once armed, it will destroy the system upon next execution." + size = 13 + requires_ntnet = 0 + available_on_ntnet = 0 + available_on_syndinet = 1 + var/armed = 0 + +/datum/computer_file/program/revelation/run_program(var/mob/living/user) + . = ..(user) + if(armed) + activate() + +/datum/computer_file/program/revelation/proc/activate() + if(computer) + computer.visible_message("\The [computer]'s screen brightly flashes and loud electrical buzzing is heard.") + computer.enabled = 0 + computer.update_icon() + qdel(computer.hard_drive) + computer.take_damage(25, 10, 1, 1) + if(computer.battery_module && prob(25)) + qdel(computer.battery_module) + computer.visible_message("\The [computer]'s battery explodes in rain of sparks.") + var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread + spark_system.start() + if(istype(computer, /obj/item/modular_computer/processor)) + var/obj/item/modular_computer/processor/P = computer + if(P.machinery_computer.tesla_link && prob(50)) + qdel(P.machinery_computer.tesla_link) + computer.visible_message("\The [computer]'s tesla link explodes in rain of sparks.") + var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread + spark_system.start() + +/datum/computer_file/program/revelation/ui_act(action, params) + if(..()) + return 1 + switch(action) + if("PRG_arm") + armed = !armed + if("PRG_activate") + activate() + if("PRG_obfuscate") + var/mob/living/user = usr + var/newname = sanitize(input(user, "Enter new program name: ")) + if(!newname) + return + filedesc = newname + + +/datum/computer_file/program/revelation/clone() + var/datum/computer_file/program/revelation/temp = ..() + temp.armed = armed + return temp + +/datum/computer_file/program/revelation/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + ui = new(user, src, ui_key, "revelation", "Revelation Virus", 400, 250, state = state) + ui.set_style("syndicate") + ui.set_autoupdate(state = 1) + ui.open() + + +/datum/computer_file/program/revelation/ui_data(mob/user) + var/list/data = get_header_data() + + data["armed"] = armed + + return data \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm new file mode 100644 index 00000000000..feb688b1d2a --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/card.dm @@ -0,0 +1,471 @@ +/datum/computer_file/program/card_mod + filename = "cardmod" + filedesc = "ID card modification program" + program_icon_state = "id" + extended_desc = "Program for programming employee ID cards to access parts of the station." + transfer_access = access_change_ids + requires_ntnet = 0 + size = 8 + var/mod_mode = 1 + var/is_centcom = 0 + var/show_assignments = 0 + var/minor = 0 + var/authenticated = 0 + var/list/reg_ids = list() + var/list/region_access = null + var/list/head_subordinates = null + var/target_dept = 0 //Which department this computer has access to. 0=all departments + var/change_position_cooldown = 60 + //Jobs you cannot open new positions for + var/list/blacklisted = list( + "AI", + "Assistant", + "Cyborg", + "Captain", + "Head of Personnel", + "Head of Security", + "Chief Engineer", + "Research Director", + "Chief Medical Officer", + "Chaplain") + + //The scaling factor of max total positions in relation to the total amount of people on board the station in % + var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players + + //This is used to keep track of opened positions for jobs to allow instant closing + //Assoc array: "JobName" = (int) + var/list/opened_positions = list(); + + +/datum/computer_file/program/card_mod/event_idremoved(background, slot) + if(slot == 2) + minor = 0 + authenticated = 0 + head_subordinates = null + region_access = null + + +/datum/computer_file/program/card_mod/proc/job_blacklisted(jobtitle) + return (jobtitle in blacklisted) + + +//Logic check for if you can open the job +/datum/computer_file/program/card_mod/proc/can_open_job(datum/job/job) + if(job) + if(!job_blacklisted(job.title)) + if((job.total_positions <= player_list.len * (max_relative_positions / 100))) + var/delta = (world.time / 10) - time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) + return 1 + return -2 + return 0 + return 0 + +//Logic check for if you can close the job +/datum/computer_file/program/card_mod/proc/can_close_job(datum/job/job) + if(job) + if(!job_blacklisted(job.title)) + if(job.total_positions > job.current_positions) + var/delta = (world.time / 10) - time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) + return 1 + return -2 + return 0 + return 0 + + +/datum/computer_file/program/card_mod/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + ui = new(user, src, ui_key, "identification_computer", "ID card modification program", 600, 700, state = state) + ui.open() + ui.set_autoupdate(state = 1) + + +/datum/computer_file/program/card_mod/proc/format_jobs(list/jobs) + var/obj/item/weapon/card/id/id_card = computer.card_slot.stored_card + var/list/formatted = list() + for(var/job in jobs) + formatted.Add(list(list( + "display_name" = replacetext(job, " ", " "), + "target_rank" = id_card && id_card.assignment ? id_card.assignment : "Unassigned", + "job" = job))) + + return formatted + +/datum/computer_file/program/card_mod/ui_act(action, params) + if(..()) + return 1 + + var/obj/item/weapon/card/id/user_id_card = null + var/mob/user = usr + + var/obj/item/weapon/card/id/id_card = computer.card_slot.stored_card + var/obj/item/weapon/card/id/auth_card = computer.card_slot.stored_card2 + + if(auth_card) + user_id_card = auth_card + else + if(ishuman(user)) + var/mob/living/carbon/human/h = user + user_id_card = h.get_idcard() + + switch(action) + if("PRG_switchm") + if(params["target"] == "mod") + mod_mode = 1 + else if (params["target"] == "manifest") + mod_mode = 0 + else if (params["target"] == "manage") + mod_mode = 2 + if("PRG_togglea") + if(show_assignments) + show_assignments = 0 + else + show_assignments = 1 + if("PRG_print") + if(computer && computer.nano_printer) //This option should never be called if there is no printer + if(mod_mode) + if(authorized()) + var/contents = {"

Access Report

+ Prepared By: [user_id_card && user_id_card.registered_name ? user_id_card.registered_name : "Unknown"]
+ For: [id_card.registered_name ? id_card.registered_name : "Unregistered"]
+
+ Assignment: [id_card.assignment]
+ Access:
+ "} + + var/known_access_rights = get_all_accesses() + for(var/A in id_card.access) + if(A in known_access_rights) + contents += " [get_access_desc(A)]" + + if(!computer.nano_printer.print_text(contents,"access report")) + usr << "Hardware error: Printer was unable to print the file. It may be out of paper." + return + else + computer.visible_message("\The [computer] prints out paper.") + else + var/contents = {"

Crew Manifest

+
+ [data_core ? data_core.get_manifest(0) : ""] + "} + if(!computer.nano_printer.print_text(contents,text("crew manifest ([])", worldtime2text()))) + usr << "Hardware error: Printer was unable to print the file. It may be out of paper." + return + else + computer.visible_message("\The [computer] prints out paper.") + if("PRG_eject") + if(computer && computer.card_slot) + var/select = params["target"] + switch(select) + if("id") + if(id_card) + data_core.manifest_modify(id_card.registered_name, id_card.assignment) + computer.proc_eject_id(user, 1) + else + var/obj/item/I = usr.get_active_hand() + if (istype(I, /obj/item/weapon/card/id)) + if(!usr.drop_item()) + return + I.forceMove(computer) + computer.card_slot.stored_card = I + if("auth") + if(auth_card) + if(id_card) + data_core.manifest_modify(id_card.registered_name, id_card.assignment) + head_subordinates = null + region_access = null + authenticated = 0 + minor = 0 + computer.proc_eject_id(user, 2) + else + var/obj/item/I = usr.get_active_hand() + if (istype(I, /obj/item/weapon/card/id)) + if(!usr.drop_item()) + return + I.forceMove(computer) + computer.card_slot.stored_card2 = I + if("PRG_terminate") + if(computer && ((id_card.assignment in head_subordinates) || id_card.assignment == "Assistant")) + id_card.assignment = "Unassigned" + remove_nt_access(id_card) + + if("PRG_edit") + if(computer && authorized()) + if(params["name"]) + var/temp_name = reject_bad_name(input("Enter name.", "Name", id_card.registered_name)) + if(temp_name) + id_card.registered_name = temp_name + else + computer.visible_message("[computer] buzzes rudely.") + //else if(params["account"]) + // var/account_num = text2num(input("Enter account number.", "Account", id_card.associated_account_number)) + // id_card.associated_account_number = account_num + if("PRG_assign") + if(computer && authorized() && id_card) + var/t1 = params["assign_target"] + if(t1 == "Custom") + var/temp_t = reject_bad_text(input("Enter a custom job assignment.","Assignment", id_card.assignment), 45) + //let custom jobs function as an impromptu alt title, mainly for sechuds + if(temp_t) + id_card.assignment = temp_t + else + var/list/access = list() + if(is_centcom) + access = get_centcom_access(t1) + else + var/datum/job/jobdatum + for(var/jobtype in typesof(/datum/job)) + var/datum/job/J = new jobtype + if(ckey(J.title) == ckey(t1)) + jobdatum = J + break + if(!jobdatum) + usr << "No log exists for this job: [t1]" + return + + access = jobdatum.get_access() + + remove_nt_access(id_card) + apply_access(id_card, access) + id_card.assignment = t1 + + if("PRG_access") + if(params["allowed"] && computer && authorized()) + var/access_type = text2num(params["access_target"]) + var/access_allowed = text2num(params["allowed"]) + if(access_type in (is_centcom ? get_all_centcom_access() : get_all_accesses())) + id_card.access -= access_type + if(!access_allowed) + id_card.access += access_type + if("PRG_open_job") + var/edit_job_target = params["target"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j) + return 0 + if(can_open_job(j) != 1) + return 0 + if(opened_positions[edit_job_target] >= 0) + time_last_changed_position = world.time / 10 + j.total_positions++ + opened_positions[edit_job_target]++ + if("PRG_close_job") + var/edit_job_target = params["target"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j) + return 0 + if(can_close_job(j) != 1) + return 0 + //Allow instant closing without cooldown if a position has been opened before + if(opened_positions[edit_job_target] <= 0) + time_last_changed_position = world.time / 10 + j.total_positions-- + opened_positions[edit_job_target]-- + if("PRG_regsel") + if(!reg_ids) + reg_ids = list() + var/regsel = text2num(params["region"]) + if(regsel in reg_ids) + reg_ids -= regsel + else + reg_ids += regsel + + if(id_card) + id_card.name = text("[id_card.registered_name]'s ID Card ([id_card.assignment])") + + return 1 + +/datum/computer_file/program/card_mod/proc/remove_nt_access(obj/item/weapon/card/id/id_card) + id_card.access -= get_all_accesses() + id_card.access -= get_all_centcom_access() + +/datum/computer_file/program/card_mod/proc/apply_access(obj/item/weapon/card/id/id_card, list/accesses) + id_card.access |= accesses + +/datum/computer_file/program/card_mod/ui_data(mob/user) + + var/list/data = get_header_data() + + data["mmode"] = mod_mode + + var/authed = 0 + if(computer && computer.card_slot) + var/obj/item/weapon/card/id/auth_card = computer.card_slot.stored_card2 + data["auth_name"] = auth_card ? strip_html_simple(auth_card.name) : "-----" + authed = authorized() + + + if(mod_mode == 2) + data["slots"] = list() + var/list/pos = list() + for(var/datum/job/job in SSjob.occupations) + if(job.title in blacklisted) + continue + + var/list/status_open = build_manage(job,1) + var/list/status_close = build_manage(job,0) + + pos.Add(list(list( + "title" = job.title, + "current" = job.current_positions, + "total" = job.total_positions, + "status_open" = authed ? status_open["enable"]: 0, + "status_close" = authed ? status_close["enable"] : 0, + "desc_open" = status_open["desc"], + "desc_close" = status_close["desc"]))) + data["slots"] = pos + + data["src"] = "\ref[src]" + data["station_name"] = station_name() + + + if(!mod_mode) + data["manifest"] = list() + var/list/crew = list() + for(var/datum/data/record/t in sortRecord(data_core.general)) + crew.Add(list(list( + "name" = t.fields["name"], + "rank" = t.fields["rank"]))) + + data["manifest"] = crew + data["assignments"] = show_assignments + if(computer) + data["have_id_slot"] = !!computer.card_slot + data["have_printer"] = !!computer.nano_printer + if(!computer.card_slot && mod_mode == 1) + mod_mode = 0 //We can't modify IDs when there is no card reader + else + data["have_id_slot"] = 0 + data["have_printer"] = 0 + + data["centcom_access"] = is_centcom + + + data["authenticated"] = authed + + + if(mod_mode == 1) + + if(computer && computer.card_slot) + var/obj/item/weapon/card/id/id_card = computer.card_slot.stored_card + + data["has_id"] = !!id_card + data["id_rank"] = id_card && id_card.assignment ? html_encode(id_card.assignment) : "Unassigned" + data["id_owner"] = id_card && id_card.registered_name ? html_encode(id_card.registered_name) : "-----" + data["id_name"] = id_card ? strip_html_simple(id_card.name) : "-----" + + if(show_assignments) + data["engineering_jobs"] = format_jobs(engineering_positions) + data["medical_jobs"] = format_jobs(medical_positions) + data["science_jobs"] = format_jobs(science_positions) + data["security_jobs"] = format_jobs(security_positions) + data["cargo_jobs"] = format_jobs(supply_positions) + data["civilian_jobs"] = format_jobs(civilian_positions) + data["centcom_jobs"] = format_jobs(get_all_centcom_jobs()) + + + if(computer.card_slot.stored_card) + var/obj/item/weapon/card/id/id_card = computer.card_slot.stored_card + if(is_centcom) + var/list/all_centcom_access = list() + for(var/access in get_all_centcom_access()) + all_centcom_access.Add(list(list( + "desc" = replacetext(get_centcom_access_desc(access), " ", " "), + "ref" = access, + "allowed" = (access in id_card.access) ? 1 : 0))) + data["all_centcom_access"] = all_centcom_access + else + var/list/regions = list() + for(var/i = 1; i <= 7; i++) + if((minor || target_dept) && !(i in region_access)) + continue + + var/list/accesses = list() + if(i in reg_ids) + for(var/access in get_region_accesses(i)) + if (get_access_desc(access)) + accesses.Add(list(list( + "desc" = replacetext(get_access_desc(access), " ", " "), + "ref" = access, + "allowed" = (access in id_card.access) ? 1 : 0))) + + regions.Add(list(list( + "name" = get_region_accesses_name(i), + "regid" = i, + "selected" = (i in reg_ids) ? 1 : null, + "accesses" = accesses))) + data["regions"] = regions + + data["minor"] = target_dept || minor ? 1 : 0 + + + return data + + +/datum/computer_file/program/card_mod/proc/build_manage(datum/job,open = 0) + var/out = "Denied" + var/can_change= 0 + if(open) + can_change = can_open_job(job) + else + can_change = can_close_job(job) + var/enable = 0 + if(can_change == 1) + out = "[open ? "Open Position" : "Close Position"]" + enable = 1 + else if(can_change == -2) + var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - time_last_changed_position), 1) + var/mins = round(time_to_wait / 60) + var/seconds = time_to_wait - (60*mins) + out = "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" + else + out = "Denied" + + return list("enable" = enable, "desc" = out) + + +/datum/computer_file/program/card_mod/proc/authorized() + if(!authenticated) + if(computer && computer.card_slot) + var/obj/item/weapon/card/id/auth_card = computer.card_slot.stored_card2 + if(auth_card) + region_access = list() + if(transfer_access in auth_card.GetAccess()) + minor = 0 + authenticated = 1 + return 1 + else + if((access_hop in auth_card.access) && ((target_dept==1) || !target_dept)) + region_access |= 1 + region_access |= 6 + get_subordinates("Head of Personnel") + if((access_hos in auth_card.access) && ((target_dept==2) || !target_dept)) + region_access |= 2 + get_subordinates("Head of Security") + if((access_cmo in auth_card.access) && ((target_dept==3) || !target_dept)) + region_access |= 3 + get_subordinates("Chief Medical Officer") + if((access_rd in auth_card.access) && ((target_dept==4) || !target_dept)) + region_access |= 4 + get_subordinates("Research Director") + if((access_ce in auth_card.access) && ((target_dept==5) || !target_dept)) + region_access |= 5 + get_subordinates("Chief Engineer") + if(region_access) + minor = 1 + authenticated = 1 + return 1 + else + return authenticated + +/datum/computer_file/program/card_mod/proc/get_subordinates(rank) + head_subordinates = list() + for(var/datum/job/job in SSjob.occupations) + if(rank in job.department_head) + head_subordinates += job.title diff --git a/code/modules/modular_computers/file_system/programs/configurator.dm b/code/modules/modular_computers/file_system/programs/configurator.dm new file mode 100644 index 00000000000..e0fd5e2f3cf --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/configurator.dm @@ -0,0 +1,81 @@ +// This is special hardware configuration program. +// It is to be used only with modular computers. +// It allows you to toggle components of your device. + +/datum/computer_file/program/computerconfig + filename = "compconfig" + filedesc = "Computer Configuration Tool" + extended_desc = "This program allows configuration of computer's hardware" + program_icon_state = "generic" + unsendable = 1 + undeletable = 1 + size = 4 + available_on_ntnet = 0 + requires_ntnet = 0 + var/obj/item/modular_computer/movable = null + + +/datum/computer_file/program/computerconfig/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + ui = new(user, src, ui_key, "laptop_configuration", "NTOS Configuration Utility", 575, 700, state = state) + ui.open() + ui.set_autoupdate(state = 1) + +/datum/computer_file/program/computerconfig/ui_data(mob/user) + + movable = computer + if(!istype(movable)) + movable = null + + // No computer connection, we can't get data from that. + if(!movable) + return 0 + + var/list/data = list() + + + data = get_header_data() + + var/list/hardware = movable.get_all_components() + + data["disk_size"] = movable.hard_drive.max_capacity + data["disk_used"] = movable.hard_drive.used_capacity + data["power_usage"] = movable.last_power_usage + data["battery_exists"] = movable.battery_module ? 1 : 0 + if(movable.battery_module) + data["battery_rating"] = movable.battery_module.battery.maxcharge + data["battery_percent"] = round(movable.battery_module.battery.percent()) + + if(movable.battery_module) + data["battery"] = list("max" = movable.battery_module.battery.maxcharge, "charge" = round(movable.battery_module.battery.charge)) + + var/list/all_entries[0] + for(var/I in hardware) + var/obj/item/weapon/computer_hardware/H = I + all_entries.Add(list(list( + "name" = H.name, + "desc" = H.desc, + "enabled" = H.enabled, + "critical" = H.critical, + "powerusage" = H.power_usage + ))) + + data["hardware"] = all_entries + return data + + +/datum/computer_file/program/computerconfig/ui_act(action,params) + if(..()) + return + switch(action) + if("PC_toggle_component") + var/obj/item/weapon/computer_hardware/H = movable.find_hardware_by_name(params["name"]) + if(H && istype(H)) + H.enabled = !H.enabled + . = TRUE diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/file_browser.dm new file mode 100644 index 00000000000..1ae58135aac --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/file_browser.dm @@ -0,0 +1,240 @@ +/datum/computer_file/program/filemanager + filename = "filemanager" + filedesc = "NTOS File Manager" + extended_desc = "This program allows management of files." + program_icon_state = "generic" + size = 8 + requires_ntnet = 0 + available_on_ntnet = 0 + undeletable = 1 + var/open_file + var/error + +/datum/computer_file/program/filemanager/ui_act(action, params) + if(..()) + return 1 + + switch(action) + if("PRG_openfile") + . = 1 + open_file = params["name"] + if("PRG_newtextfile") + . = 1 + var/newname = sanitize(input(usr, "Enter file name or leave blank to cancel:", "File rename")) + if(!newname) + return 1 + var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive + if(!HDD) + return 1 + var/datum/computer_file/data/F = new/datum/computer_file/data() + F.filename = newname + F.filetype = "TXT" + HDD.store_file(F) + if("PRG_deletefile") + . = 1 + var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive + if(!HDD) + return 1 + var/datum/computer_file/file = HDD.find_file_by_name(params["name"]) + if(!file || file.undeletable) + return 1 + HDD.remove_file(file) + if("PRG_usbdeletefile") + . = 1 + var/obj/item/weapon/computer_hardware/hard_drive/RHDD = computer.portable_drive + if(!RHDD) + return 1 + var/datum/computer_file/file = RHDD.find_file_by_name(params["name"]) + if(!file || file.undeletable) + return 1 + RHDD.remove_file(file) + if("PRG_closefile") + . = 1 + open_file = null + error = null + if("PRG_clone") + . = 1 + var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive + if(!HDD) + return 1 + var/datum/computer_file/F = HDD.find_file_by_name(params["name"]) + if(!F || !istype(F)) + return 1 + var/datum/computer_file/C = F.clone(1) + HDD.store_file(C) + if("PRG_rename") + . = 1 + var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive + if(!HDD) + return 1 + var/datum/computer_file/file = HDD.find_file_by_name(params["name"]) + if(!file || !istype(file)) + return 1 + var/newname = sanitize(input(usr, "Enter new file name:", "File rename", file.filename)) + if(file && newname) + file.filename = newname + if("PRG_edit") + . = 1 + if(!open_file) + return 1 + var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive + if(!HDD) + return 1 + var/datum/computer_file/data/F = HDD.find_file_by_name(open_file) + if(!F || !istype(F)) + return 1 + if(F.do_not_edit && (alert("WARNING: This file is not compatible with editor. Editing it may result in permanently corrupted formatting or damaged data consistency. Edit anyway?", "Incompatible File", "No", "Yes") == "No")) + return 1 + // 16384 is the limit for file length in characters. Currently, papers have value of 2048 so this is 8 times as long, since we can't edit parts of the file independently. + var/newtext = sanitize(html_decode(input(usr, "Editing file [open_file]. You may use most tags used in paper formatting:", "Text Editor", F.stored_data) as message|null), 16384) + if(!newtext) + return + if(F) + var/datum/computer_file/data/backup = F.clone() + HDD.remove_file(F) + F.stored_data = newtext + F.calculate_size() + // We can't store the updated file, it's probably too large. Print an error and restore backed up version. + // This is mostly intended to prevent people from losing texts they spent lot of time working on due to running out of space. + // They will be able to copy-paste the text from error screen and store it in notepad or something. + if(!HDD.store_file(F)) + error = "I/O error: Unable to overwrite file. Hard drive is probably full. You may want to backup your changes before closing this window:

[F.stored_data]

" + HDD.store_file(backup) + if("PRG_printfile") + . = 1 + if(!open_file) + return 1 + var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive + if(!HDD) + return 1 + var/datum/computer_file/data/F = HDD.find_file_by_name(open_file) + if(!F || !istype(F)) + return 1 + if(!computer.nano_printer) + error = "Missing Hardware: Your computer does not have required hardware to complete this operation." + return 1 + if(!computer.nano_printer.print_text(parse_tags(F.stored_data))) + error = "Hardware error: Printer was unable to print the file. It may be out of paper." + return 1 + if("PRG_copytousb") + . = 1 + var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive + var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive + if(!HDD || !RHDD) + return 1 + var/datum/computer_file/F = HDD.find_file_by_name(params) + if(!F || !istype(F)) + return 1 + var/datum/computer_file/C = F.clone(0) + RHDD.store_file(C) + if("PRG_copyfromusb") + . = 1 + var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive + var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive + if(!HDD || !RHDD) + return 1 + var/datum/computer_file/F = RHDD.find_file_by_name(params) + if(!F || !istype(F)) + return 1 + var/datum/computer_file/C = F.clone(0) + HDD.store_file(C) + + +/datum/computer_file/program/filemanager/proc/parse_tags(t) + t = replacetext(t, "\[center\]", "
") + t = replacetext(t, "\[/center\]", "
") + t = replacetext(t, "\[br\]", "
") + t = replacetext(t, "\[b\]", "") + t = replacetext(t, "\[/b\]", "") + t = replacetext(t, "\[i\]", "") + t = replacetext(t, "\[/i\]", "") + t = replacetext(t, "\[u\]", "") + t = replacetext(t, "\[/u\]", "") + t = replacetext(t, "\[time\]", "[worldtime2text()]") + t = replacetext(t, "\[date\]", "[time2text(world.realtime, "MMM DD")] [year_integer+540]") + t = replacetext(t, "\[large\]", "") + t = replacetext(t, "\[/large\]", "") + t = replacetext(t, "\[h1\]", "

") + t = replacetext(t, "\[/h1\]", "

") + t = replacetext(t, "\[h2\]", "

") + t = replacetext(t, "\[/h2\]", "

") + t = replacetext(t, "\[h3\]", "

") + t = replacetext(t, "\[/h3\]", "

") + t = replacetext(t, "\[*\]", "
  • ") + t = replacetext(t, "\[hr\]", "
    ") + t = replacetext(t, "\[small\]", "") + t = replacetext(t, "\[/small\]", "") + t = replacetext(t, "\[list\]", "
      ") + t = replacetext(t, "\[/list\]", "
    ") + t = replacetext(t, "\[table\]", "") + t = replacetext(t, "\[/table\]", "
    ") + t = replacetext(t, "\[grid\]", "") + t = replacetext(t, "\[/grid\]", "
    ") + t = replacetext(t, "\[row\]", "") + t = replacetext(t, "\[tr\]", "") + t = replacetext(t, "\[td\]", "") + t = replacetext(t, "\[cell\]", "") + t = replacetext(t, "\[logo\]", "") + return t + + +/datum/computer_file/program/filemanager/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + ui = new(user, src, ui_key, "file_manager", "NTOS File Manage", 575, 700, state = state) + ui.open() + ui.set_autoupdate(state = 1) + +/datum/computer_file/program/filemanager/ui_data(mob/user) + var/list/data = get_header_data() + + var/obj/item/weapon/computer_hardware/hard_drive/HDD + var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD + if(error) + data["error"] = error + if(open_file) + var/datum/computer_file/data/file + + if(!computer || !computer.hard_drive) + data["error"] = "I/O ERROR: Unable to access hard drive." + else + HDD = computer.hard_drive + file = HDD.find_file_by_name(open_file) + if(!istype(file)) + data["error"] = "I/O ERROR: Unable to open file." + else + data["filedata"] = parse_tags(file.stored_data) + data["filename"] = "[file.filename].[file.filetype]" + else + if(!computer || !computer.hard_drive) + data["error"] = "I/O ERROR: Unable to access hard drive." + else + HDD = computer.hard_drive + RHDD = computer.portable_drive + var/list/files[0] + for(var/datum/computer_file/F in HDD.stored_files) + files.Add(list(list( + "name" = F.filename, + "type" = F.filetype, + "size" = F.size, + "undeletable" = F.undeletable + ))) + data["files"] = files + if(RHDD) + data["usbconnected"] = 1 + var/list/usbfiles[0] + for(var/datum/computer_file/F in RHDD.stored_files) + usbfiles.Add(list(list( + "name" = F.filename, + "type" = F.filetype, + "size" = F.size, + "undeletable" = F.undeletable + ))) + data["usbfiles"] = usbfiles + + return data \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm new file mode 100644 index 00000000000..a34163f8eef --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm @@ -0,0 +1,164 @@ +/datum/computer_file/program/ntnetdownload + filename = "ntndownloader" + filedesc = "NTNet Software Download Tool" + program_icon_state = "generic" + extended_desc = "This program allows downloads of software from official NT repositories" + unsendable = 1 + undeletable = 1 + size = 4 + requires_ntnet = 1 + requires_ntnet_feature = NTNET_SOFTWAREDOWNLOAD + available_on_ntnet = 0 + ui_header = "downloader_finished.gif" + var/datum/computer_file/program/downloaded_file = null + var/hacked_download = 0 + var/download_completion = 0 //GQ of downloaded data. + var/download_netspeed = 0 + var/downloaderror = "" + var/obj/item/modular_computer/my_computer = null + +/datum/computer_file/program/ntnetdownload/proc/begin_file_download(filename) + if(downloaded_file) + return 0 + + var/datum/computer_file/program/PRG = ntnet_global.find_ntnet_file_by_name(filename) + + if(!PRG || !istype(PRG)) + return 0 + + // Attempting to download antag only program, but without having emagged computer. No. + if(PRG.available_on_syndinet && !computer_emagged) + return 0 + + if(!computer || !computer.hard_drive || !computer.hard_drive.try_store_file(PRG)) + return 0 + + ui_header = "downloader_running.gif" + + if(PRG in ntnet_global.available_station_software) + generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from NTNet Software Repository.") + hacked_download = 0 + else if(PRG in ntnet_global.available_antag_software) + generate_network_log("Began downloading file **ENCRYPTED**.[PRG.filetype] from unspecified server.") + hacked_download = 1 + else + generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from unspecified server.") + hacked_download = 0 + + downloaded_file = PRG.clone() + +/datum/computer_file/program/ntnetdownload/proc/abort_file_download() + if(!downloaded_file) + return + generate_network_log("Aborted download of file [hacked_download ? "**ENCRYPTED**" : downloaded_file.filename].[downloaded_file.filetype].") + downloaded_file = null + download_completion = 0 + ui_header = "downloader_finished.gif" + +/datum/computer_file/program/ntnetdownload/proc/complete_file_download() + if(!downloaded_file) + return + generate_network_log("Completed download of file [hacked_download ? "**ENCRYPTED**" : downloaded_file.filename].[downloaded_file.filetype].") + if(!computer || !computer.hard_drive || !computer.hard_drive.store_file(downloaded_file)) + // The download failed + downloaderror = "I/O ERROR - Unable to save file. Check whether you have enough free space on your hard drive and whether your hard drive is properly connected. If the issue persists contact your system administrator for assistance." + downloaded_file = null + download_completion = 0 + ui_header = "downloader_finished.gif" + +/datum/computer_file/program/ntnetdownload/process_tick() + if(!downloaded_file) + return + if(download_completion >= downloaded_file.size) + complete_file_download() + // Download speed according to connectivity state. NTNet server is assumed to be on unlimited speed so we're limited by our local connectivity + download_netspeed = 0 + // Speed defines are found in misc.dm + switch(ntnet_status) + if(1) + download_netspeed = NTNETSPEED_LOWSIGNAL + if(2) + download_netspeed = NTNETSPEED_HIGHSIGNAL + if(3) + download_netspeed = NTNETSPEED_ETHERNET + download_completion += download_netspeed + +/datum/computer_file/program/ntnetdownload/ui_act(action, params) + if(..()) + return 1 + switch(action) + if("PRG_downloadfile") + if(!downloaded_file) + begin_file_download(params["filename"]) + return 1 + if("PRG_reseterror") + if(downloaderror) + download_completion = 0 + download_netspeed = 0 + downloaded_file = null + downloaderror = "" + return 1 + return 0 + +/datum/computer_file/program/ntnetdownload/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + ui = new(user, src, ui_key, "ntnet_downloader", "NTNet Download Program", 575, 700, state = state) + ui.open() + ui.set_autoupdate(state = 1) + +/datum/computer_file/program/ntnetdownload/ui_data(mob/user) + + my_computer = computer + + if(!istype(my_computer)) + return + + var/list/data = get_header_data() + + // This IF cuts on data transferred to client, so i guess it's worth it. + if(downloaderror) // Download errored. Wait until user resets the program. + data["error"] = downloaderror + else if(downloaded_file) // Download running. Wait please.. + data["downloadname"] = downloaded_file.filename + data["downloaddesc"] = downloaded_file.filedesc + data["downloadsize"] = downloaded_file.size + data["downloadspeed"] = download_netspeed + data["downloadcompletion"] = round(download_completion, 0.1) + else // No download running, pick file. + data["disk_size"] = my_computer.hard_drive.max_capacity + data["disk_used"] = my_computer.hard_drive.used_capacity + var/list/all_entries[0] + for(var/A in ntnet_global.available_station_software) + var/datum/computer_file/program/P = A + // Only those programs our user can run will show in the list + if(!P.can_run(user,transfer = 1)) + continue + all_entries.Add(list(list( + "filename" = P.filename, + "filedesc" = P.filedesc, + "fileinfo" = P.extended_desc, + "size" = P.size + ))) + data["hackedavailable"] = 0 + if(computer_emagged) // If we are running on emagged computer we have access to some "bonus" software + var/list/hacked_programs[0] + for(var/S in ntnet_global.available_antag_software) + var/datum/computer_file/program/P = S + data["hackedavailable"] = 1 + hacked_programs.Add(list(list( + "filename" = P.filename, + "filedesc" = P.filedesc, + "fileinfo" = P.extended_desc, + "size" = P.size + ))) + data["hacked_programs"] = hacked_programs + + data["downloadable_programs"] = all_entries + + return data \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/ntmonitor.dm new file mode 100644 index 00000000000..e12299b250b --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/ntmonitor.dm @@ -0,0 +1,94 @@ +/datum/computer_file/program/ntnetmonitor + filename = "ntmonitor" + filedesc = "NTNet Diagnostics and Monitoring" + program_icon_state = "comm_monitor" + extended_desc = "This program monitors stationwide NTNet network, provides access to logging systems, and allows for configuration changes" + size = 12 + requires_ntnet = 1 + required_access = access_network //Network control is a more secure program. + available_on_ntnet = 1 + +/datum/computer_file/program/ntnetmonitor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + + ui = new(user, src, ui_key, "ntnet_monitor", "NTNet Diagnostics and Monitoring Tool", 575, 700, state = state) + ui.open() + ui.set_autoupdate(state = 1) + + +/datum/computer_file/program/ntnetmonitor/ui_act(action, params) + if(..()) + return 1 + switch(action) + if("resetIDS") + . = 1 + if(ntnet_global) + ntnet_global.resetIDS() + return 1 + if("toggleIDS") + . = 1 + if(ntnet_global) + ntnet_global.toggleIDS() + return 1 + if("toggleWireless") + . = 1 + if(!ntnet_global) + return 1 + + // NTNet is disabled. Enabling can be done without user prompt + if(ntnet_global.setting_disabled) + ntnet_global.setting_disabled = 0 + return 1 + + // NTNet is enabled and user is about to shut it down. Let's ask them if they really want to do it, as wirelessly connected computers won't connect without NTNet being enabled (which may prevent people from turning it back on) + var/mob/user = usr + if(!user) + return 1 + var/response = alert(user, "Really disable NTNet wireless? If your computer is connected wirelessly you won't be able to turn it back on! This will affect all connected wireless devices.", "NTNet shutdown", "Yes", "No") + if(response == "Yes") + ntnet_global.setting_disabled = 1 + return 1 + if("purgelogs") + . = 1 + if(ntnet_global) + ntnet_global.purge_logs() + if("updatemaxlogs") + . = 1 + var/mob/user = usr + var/logcount = text2num(input(user,"Enter amount of logs to keep in memory ([MIN_NTNET_LOGS]-[MAX_NTNET_LOGS]):")) + if(ntnet_global) + ntnet_global.update_max_log_count(logcount) + if("toggle_function") + . = 1 + if(!ntnet_global) + return 1 + ntnet_global.toggle_function(text2num(params["id"])) + +/datum/computer_file/program/ntnetmonitor/ui_data(mob/user) + if(!ntnet_global) + return + var/list/data = get_header_data() + + data["ntnetstatus"] = ntnet_global.check_function() + data["ntnetrelays"] = ntnet_global.relays.len + data["idsstatus"] = ntnet_global.intrusion_detection_enabled + data["idsalarm"] = ntnet_global.intrusion_detection_alarm + + data["config_softwaredownload"] = ntnet_global.setting_softwaredownload + data["config_peertopeer"] = ntnet_global.setting_peertopeer + data["config_communication"] = ntnet_global.setting_communication + data["config_systemcontrol"] = ntnet_global.setting_systemcontrol + + data["ntnetlogs"] = list() + + for(var/i in ntnet_global.logs) + data["ntnetlogs"] += list(list("entry" = i)) + data["ntnetmaxlogs"] = ntnet_global.setting_maxlogcount + + return data \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm new file mode 100644 index 00000000000..e8cbe357113 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm @@ -0,0 +1,236 @@ +/datum/computer_file/program/chatclient + filename = "ntnrc_client" + filedesc = "NTNet Relay Chat Client" + program_icon_state = "command" + extended_desc = "This program allows communication over NTNRC network" + size = 8 + requires_ntnet = 1 + requires_ntnet_feature = NTNET_COMMUNICATION + network_destination = "NTNRC server" + ui_header = "ntnrc_idle.gif" + available_on_ntnet = 1 + var/last_message = null // Used to generate the toolbar icon + var/username + var/datum/ntnet_conversation/channel = null + var/operator_mode = 0 // Channel operator mode + var/netadmin_mode = 0 // Administrator mode (invisible to other users + bypasses passwords) + +/datum/computer_file/program/chatclient/New() + username = "DefaultUser[rand(100, 999)]" + +/datum/computer_file/program/chatclient/ui_act(action, params) + if(..()) + return 1 + + switch(action) + if("PRG_speak") + . = 1 + if(!channel) + return 1 + var/mob/living/user = usr + var/message = sanitize(input(user, "Enter message or leave blank to cancel: ")) + if(!message || !channel) + return + channel.add_message(message, username) + log_chat("[user]/([user.ckey]) as [username] sent to [channel.title]: [message]") + + if("PRG_joinchannel") + . = 1 + var/datum/ntnet_conversation/C + for(var/datum/ntnet_conversation/chan in ntnet_global.chat_channels) + if(chan.id == text2num(params["id"])) + C = chan + break + + if(!C) + return 1 + + if(netadmin_mode) + channel = C // Bypasses normal leave/join and passwords. Technically makes the user invisible to others. + return 1 + + if(C.password) + var/mob/living/user = usr + var/password = sanitize(input(user,"Access Denied. Enter password:")) + if(C && (password == C.password)) + C.add_client(src) + channel = C + return 1 + C.add_client(src) + channel = C + if("PRG_leavechannel") + . = 1 + if(channel) + channel.remove_client(src) + channel = null + if("PRG_newchannel") + . = 1 + var/mob/living/user = usr + var/channel_title = sanitize(input(user,"Enter channel name or leave blank to cancel:")) + if(!channel_title) + return + var/datum/ntnet_conversation/C = new/datum/ntnet_conversation() + C.add_client(src) + C.operator = src + channel = C + C.title = channel_title + if("PRG_toggleadmin") + . = 1 + if(netadmin_mode) + netadmin_mode = 0 + if(channel) + channel.remove_client(src) // We shouldn't be in channel's user list, but just in case... + channel = null + return 1 + var/mob/living/user = usr + if(can_run(usr, 1, access_network)) + if(channel) + var/response = alert(user, "Really engage admin-mode? You will be disconnected from your current channel!", "NTNRC Admin mode", "Yes", "No") + if(response == "Yes") + if(channel) + channel.remove_client(src) + channel = null + else + return + netadmin_mode = 1 + if("PRG_changename") + . = 1 + var/mob/living/user = usr + var/newname = sanitize(input(user,"Enter new nickname or leave blank to cancel:")) + if(!newname) + return 1 + if(channel) + channel.add_status_message("[username] is now known as [newname].") + username = newname + + if("PRG_savelog") + . = 1 + if(!channel) + return + var/mob/living/user = usr + var/logname = input(user,"Enter desired logfile name (.log) or leave blank to cancel:") + if(!logname || !channel) + return 1 + var/datum/computer_file/data/logfile = new/datum/computer_file/data/logfile() + // Now we will generate HTML-compliant file that can actually be viewed/printed. + logfile.filename = logname + logfile.stored_data = "\[b\]Logfile dump from NTNRC channel [channel.title]\[/b\]\[BR\]" + for(var/logstring in channel.messages) + logfile.stored_data += "[logstring]\[BR\]" + logfile.stored_data += "\[b\]Logfile dump completed.\[/b\]" + logfile.calculate_size() + if(!computer || !computer.hard_drive || !computer.hard_drive.store_file(logfile)) + if(!computer) + // This program shouldn't even be runnable without computer. + CRASH("Var computer is null!") + return 1 + if(!computer.hard_drive) + computer.visible_message("\The [computer] shows an \"I/O Error - Hard drive connection error\" warning.") + else // In 99.9% cases this will mean our HDD is full + computer.visible_message("\The [computer] shows an \"I/O Error - Hard drive may be full. Please free some space and try again. Required space: [logfile.size]GQ\" warning.") + if("PRG_renamechannel") + . = 1 + if(!operator_mode || !channel) + return 1 + var/mob/living/user = usr + var/newname = sanitize(input(user, "Enter new channel name or leave blank to cancel:")) + if(!newname || !channel) + return + channel.add_status_message("Channel renamed from [channel.title] to [newname] by operator.") + channel.title = newname + if("PRG_deletechannel") + . = 1 + if(channel && ((channel.operator == src) || netadmin_mode)) + qdel(channel) + channel = null + if("PRG_setpassword") + . = 1 + if(!channel || ((channel.operator != src) && !netadmin_mode)) + return 1 + + var/mob/living/user = usr + var/newpassword = sanitize(input(user, "Enter new password for this channel. Leave blank to cancel, enter 'nopassword' to remove password completely:")) + if(!channel || !newpassword || ((channel.operator != src) && !netadmin_mode)) + return 1 + + if(newpassword == "nopassword") + channel.password = "" + else + channel.password = newpassword + +/datum/computer_file/program/chatclient/process_tick() + ..() + if(program_state != PROGRAM_STATE_KILLED) + ui_header = "ntnrc_idle.gif" + if(channel) + // Remember the last message. If there is no message in the channel remember null. + last_message = channel.messages.len ? channel.messages[channel.messages.len - 1] : null + else + last_message = null + return 1 + if(channel && channel.messages && channel.messages.len) + ui_header = last_message == channel.messages[channel.messages.len - 1] ? "ntnrc_idle.gif" : "ntnrc_new.gif" + else + ui_header = "ntnrc_idle.gif" + +/datum/computer_file/program/chatclient/kill_program(forced = 0) + if(channel) + channel.remove_client(src) + channel = null + ..(forced) + +/datum/computer_file/program/chatclient/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + + ui = new(user, src, ui_key, "ntnet_chat", "NTNet Relay Chat Client", 575, 700, state = state) + ui.open() + ui.set_autoupdate(state = 1) + + +/datum/computer_file/program/chatclient/ui_data(mob/user) + if(!ntnet_global || !ntnet_global.chat_channels) + return + + var/list/data = list() + + data = get_header_data() + + + data["adminmode"] = netadmin_mode + if(channel) + data["title"] = channel.title + var/list/messages[0] + for(var/M in channel.messages) + messages.Add(list(list( + "msg" = M + ))) + data["messages"] = messages + var/list/clients[0] + for(var/C in channel.clients) + var/datum/computer_file/program/chatclient/cl = C + clients.Add(list(list( + "name" = cl.username + ))) + data["clients"] = clients + operator_mode = (channel.operator == src) ? 1 : 0 + data["is_operator"] = operator_mode || netadmin_mode + + else // Channel selection screen + var/list/all_channels[0] + for(var/C in ntnet_global.chat_channels) + var/datum/ntnet_conversation/conv = C + if(conv && conv.title) + all_channels.Add(list(list( + "chan" = conv.title, + "id" = conv.id + ))) + data["all_channels"] = all_channels + + return data \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/nttransfer.dm b/code/modules/modular_computers/file_system/programs/nttransfer.dm new file mode 100644 index 00000000000..d9d0c6b8a30 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/nttransfer.dm @@ -0,0 +1,195 @@ +var/global/nttransfer_uid = 0 + +/datum/computer_file/program/nttransfer + filename = "nttransfer" + filedesc = "NTNet P2P Transfer Client" + extended_desc = "This program allows for simple file transfer via direct peer to peer connection." + program_icon_state = "comm_logs" + size = 7 + requires_ntnet = 1 + requires_ntnet_feature = NTNET_PEERTOPEER + network_destination = "other device via P2P tunnel" + available_on_ntnet = 1 + + var/error = "" // Error screen + var/server_password = "" // Optional password to download the file. + var/datum/computer_file/provided_file = null // File which is provided to clients. + var/datum/computer_file/downloaded_file = null // File which is being downloaded + var/list/connected_clients = list() // List of connected clients. + var/datum/computer_file/program/nttransfer/remote // Client var, specifies who are we downloading from. + var/download_completion = 0 // Download progress in GQ + var/download_netspeed = 0 // Our connectivity speed in GQ/s + var/actual_netspeed = 0 // Displayed in the UI, this is the actual transfer speed. + var/unique_token // UID of this program + var/upload_menu = 0 // Whether we show the program list and upload menu + +/datum/computer_file/program/nttransfer/New() + unique_token = nttransfer_uid + nttransfer_uid++ + ..() + +/datum/computer_file/program/nttransfer/process_tick() + // Server mode + update_netspeed() + if(provided_file) + for(var/datum/computer_file/program/nttransfer/C in connected_clients) + // Transfer speed is limited by device which uses slower connectivity. + // We can have multiple clients downloading at same time, but let's assume we use some sort of multicast transfer + // so they can all run on same speed. + C.actual_netspeed = min(C.download_netspeed, download_netspeed) + C.download_completion += C.actual_netspeed + if(C.download_completion >= provided_file.size) + C.finish_download() + else if(downloaded_file) // Client mode + if(!remote) + crash_download("Connection to remote server lost") + +/datum/computer_file/program/nttransfer/kill_program(forced = 0) + if(downloaded_file) // Client mode, clean up variables for next use + finalize_download() + + if(provided_file) // Server mode, disconnect all clients + for(var/datum/computer_file/program/nttransfer/P in connected_clients) + P.crash_download("Connection terminated by remote server") + downloaded_file = null + ..(forced) + +/datum/computer_file/program/nttransfer/proc/update_netspeed() + download_netspeed = 0 + switch(ntnet_status) + if(1) + download_netspeed = NTNETSPEED_LOWSIGNAL + if(2) + download_netspeed = NTNETSPEED_HIGHSIGNAL + if(3) + download_netspeed = NTNETSPEED_ETHERNET + +// Finishes download and attempts to store the file on HDD +/datum/computer_file/program/nttransfer/proc/finish_download() + if(!computer || !computer.hard_drive || !computer.hard_drive.store_file(downloaded_file)) + error = "I/O Error: Unable to save file. Check your hard drive and try again." + finalize_download() + +// Crashes the download and displays specific error message +/datum/computer_file/program/nttransfer/proc/crash_download(var/message) + error = message ? message : "An unknown error has occured during download" + finalize_download() + +// Cleans up variables for next use +/datum/computer_file/program/nttransfer/proc/finalize_download() + if(remote) + remote.connected_clients.Remove(src) + downloaded_file = null + remote = null + download_completion = 0 + + +/datum/computer_file/program/nttransfer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if (!ui) + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + + ui = new(user, src, ui_key, "ntnet_transfer", "NTNet P2P Transfer Client", 575, 700, state = state) + ui.open() + ui.set_autoupdate(state = 1) + +/datum/computer_file/program/nttransfer/ui_act(action, params) + if(..()) + return 1 + switch(action) + if("PRG_downloadfile") + for(var/datum/computer_file/program/nttransfer/P in ntnet_global.fileservers) + if("[P.unique_token]" == params["id"]) + remote = P + break + if(!remote || !remote.provided_file) + return + if(remote.server_password) + var/pass = sanitize(input(usr, "Code 401 Unauthorized. Please enter password:", "Password required")) + if(pass != remote.server_password) + error = "Incorrect Password" + return + downloaded_file = remote.provided_file.clone() + remote.connected_clients.Add(src) + return 1 + if("PRG_reset") + error = "" + upload_menu = 0 + finalize_download() + if(src in ntnet_global.fileservers) + ntnet_global.fileservers.Remove(src) + for(var/datum/computer_file/program/nttransfer/T in connected_clients) + T.crash_download("Remote server has forcibly closed the connection") + provided_file = null + return 1 + if("PRG_setpassword") + var/pass = sanitize(input(usr, "Enter new server password. Leave blank to cancel, input 'none' to disable password.", "Server security", "none")) + if(!pass) + return + if(pass == "none") + server_password = "" + return + server_password = pass + return 1 + if("PRG_uploadfile") + for(var/datum/computer_file/F in computer.hard_drive.stored_files) + if("[F.uid]" == params["id"]) + if(F.unsendable) + error = "I/O Error: File locked." + return + if(istype(F, /datum/computer_file/program)) + var/datum/computer_file/program/P = F + if(!P.can_run(usr,transfer = 1)) + error = "Access Error: Insufficient rights to upload file." + provided_file = F + ntnet_global.fileservers.Add(src) + return + error = "I/O Error: Unable to locate file on hard drive." + return 1 + if("PRG_uploadmenu") + upload_menu = 1 + + +/datum/computer_file/program/nttransfer/ui_data(mob/user) + + var/list/data = get_header_data() + + if(error) + data["error"] = error + else if(downloaded_file) + data["downloading"] = 1 + data["download_size"] = downloaded_file.size + data["download_progress"] = download_completion + data["download_netspeed"] = actual_netspeed + data["download_name"] = "[downloaded_file.filename].[downloaded_file.filetype]" + else if (provided_file) + data["uploading"] = 1 + data["upload_uid"] = unique_token + data["upload_clients"] = connected_clients.len + data["upload_haspassword"] = server_password ? 1 : 0 + data["upload_filename"] = "[provided_file.filename].[provided_file.filetype]" + else if (upload_menu) + var/list/all_files[0] + for(var/datum/computer_file/F in computer.hard_drive.stored_files) + all_files.Add(list(list( + "uid" = F.uid, + "filename" = "[F.filename].[F.filetype]", + "size" = F.size + ))) + data["upload_filelist"] = all_files + else + var/list/all_servers[0] + for(var/datum/computer_file/program/nttransfer/P in ntnet_global.fileservers) + all_servers.Add(list(list( + "uid" = P.unique_token, + "filename" = "[P.provided_file.filename].[P.provided_file.filetype]", + "size" = P.provided_file.size, + "haspassword" = P.server_password ? 1 : 0 + ))) + data["servers"] = all_servers + + return data \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/powermonitor.dm b/code/modules/modular_computers/file_system/programs/powermonitor.dm new file mode 100644 index 00000000000..316b90ec00c --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm @@ -0,0 +1,93 @@ + + +/datum/computer_file/program/power_monitor + filename = "powermonitor" + filedesc = "Power Monitoring" + program_icon_state = "power_monitor" + extended_desc = "This program connects to sensors around the station to provide information about electrical systems" + ui_header = "power_norm.gif" + transfer_access = access_engine + usage_flags = PROGRAM_CONSOLE + requires_ntnet = 0 + network_destination = "power monitoring system" + size = 9 + var/has_alert = 0 + var/obj/structure/cable/attached + var/list/history = list() + var/record_size = 60 + var/record_interval = 50 + var/next_record = 0 + + + + +/datum/computer_file/program/power_monitor/run_program(mob/living/user) + . = ..(user) + search() + history["supply"] = list() + history["demand"] = list() + + +/datum/computer_file/program/power_monitor/process_tick() + if(!attached) + search() + else + record() + +/datum/computer_file/program/power_monitor/proc/search() + var/turf/T = get_turf(computer) + attached = locate() in T + +/datum/computer_file/program/power_monitor/proc/record() + if(world.time >= next_record) + next_record = world.time + record_interval + + var/list/supply = history["supply"] + supply += attached.powernet.viewavail + if(supply.len > record_size) + supply.Cut(1, 2) + + var/list/demand = history["demand"] + demand += attached.powernet.viewload + if(demand.len > record_size) + demand.Cut(1, 2) + +/datum/computer_file/program/power_monitor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \ + datum/tgui/master_ui = null, datum/ui_state/state = default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) + assets.send(user) + + + ui = new(user, src, ui_key, "power_monitor_prog", "Power Monitoring", 1200, 1000, master_ui, state) + ui.open() + +/datum/computer_file/program/power_monitor/ui_data() + var/list/data = get_header_data() + data["stored"] = record_size + data["interval"] = record_interval / 10 + data["attached"] = attached ? TRUE : FALSE + if(attached) + data["supply"] = attached.powernet.viewavail + data["demand"] = attached.powernet.viewload + data["history"] = history + + data["areas"] = list() + if(attached) + for(var/obj/machinery/power/terminal/term in attached.powernet.nodes) + var/obj/machinery/power/apc/A = term.master + if(istype(A)) + data["areas"] += list(list( + "name" = A.area.name, + "charge" = A.cell.percent(), + "load" = A.lastused_total, + "charging" = A.charging, + "eqp" = A.equipment, + "lgt" = A.lighting, + "env" = A.environ + )) + + return data + diff --git a/code/modules/modular_computers/hardware/battery_module.dm b/code/modules/modular_computers/hardware/battery_module.dm new file mode 100644 index 00000000000..c79df6c6dea --- /dev/null +++ b/code/modules/modular_computers/hardware/battery_module.dm @@ -0,0 +1,79 @@ +// This device is wrapper for actual power cell. I have decided to not use power cells directly as even low-end cells available on station +// have tremendeous capacity in comparsion. Higher tier cells would provide your device with nearly infinite battery life, which is something i want to avoid. +/obj/item/weapon/computer_hardware/battery_module + name = "standard battery" + desc = "A standard power cell, commonly seen in high-end portable microcomputers or low-end laptops. It's rating is 750." + icon_state = "battery_normal" + critical = 1 + malfunction_probability = 1 + origin_tech = list("powerstorage" = 1, "engineering" = 1) + var/battery_rating = 750 + var/obj/item/weapon/stock_parts/cell/battery = null + +/obj/item/weapon/computer_hardware/battery_module/advanced + name = "advanced battery" + desc = "An advanced power cell, often used in most laptops. It is too large to be fitted into smaller devices. It's rating is 1100." + icon_state = "battery_advanced" + origin_tech = list("powerstorage" = 2, "engineering" = 2) + hardware_size = 2 + battery_rating = 1100 + +/obj/item/weapon/computer_hardware/battery_module/super + name = "super battery" + desc = "A very advanced power cell, often used in high-end devices, or as uninterruptable power supply for important consoles or servers. It's rating is 1500." + icon_state = "battery_super" + origin_tech = list("powerstorage" = 3, "engineering" = 3) + hardware_size = 2 + battery_rating = 1500 + +/obj/item/weapon/computer_hardware/battery_module/ultra + name = "ultra battery" + desc = "A very advanced large power cell. It's often used as uninterruptable power supply for critical consoles or servers. It's rating is 2000." + icon_state = "battery_ultra" + origin_tech = list("powerstorage" = 5, "engineering" = 4) + hardware_size = 3 + battery_rating = 2000 + +/obj/item/weapon/computer_hardware/battery_module/micro + name = "micro battery" + desc = "A small power cell, commonly seen in most portable microcomputers. It's rating is 500." + icon_state = "battery_micro" + origin_tech = list("powerstorage" = 2, "engineering" = 2) + battery_rating = 500 + +/obj/item/weapon/computer_hardware/battery_module/nano + name = "nano battery" + desc = "A tiny power cell, commonly seen in low-end portable microcomputers. It's rating is 300." + icon_state = "battery_nano" + origin_tech = list("powerstorage" = 1, "engineering" = 1) + battery_rating = 300 + +// This is not intended to be obtainable in-game. Intended for adminbus and debugging purposes. +/obj/item/weapon/computer_hardware/battery_module/lambda + name = "lambda coil" + desc = "A very complex device that creates it's own bluespace dimension. This dimension may be used to store massive amounts of energy." + icon_state = "battery_lambda" + hardware_size = 1 + battery_rating = 1000000 + +/obj/item/weapon/computer_hardware/battery_module/diagnostics(var/mob/user) + ..() + user << "Internal battery charge: [battery.charge]/[battery.maxcharge] CU" + +/obj/item/weapon/computer_hardware/battery_module/New() + battery = new/obj/item/weapon/stock_parts/cell(src) + battery.maxcharge = battery_rating + battery.charge = battery.maxcharge/2 + ..() + +/obj/item/weapon/computer_hardware/battery_module/proc/charge_to_full() + if(battery) + battery.charge = battery.maxcharge + +/obj/item/weapon/computer_hardware/battery_module/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(M.battery_module) + user << "This computer's battery slot is already occupied by \the [M.battery_module]." + return + found = 1 + M.battery_module = src + ..(user, M, found) \ No newline at end of file diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm new file mode 100644 index 00000000000..aba6bcae84c --- /dev/null +++ b/code/modules/modular_computers/hardware/card_slot.dm @@ -0,0 +1,29 @@ +/obj/item/weapon/computer_hardware/card_slot + name = "RFID card slot" + desc = "Slot that allows this computer to write data on RFID cards. Necessary for some programs to run properly." + power_usage = 10 //W + critical = 0 + icon_state = "cardreader" + hardware_size = 1 + origin_tech = list("programming" = 2) + + var/obj/item/weapon/card/id/stored_card = null + var/obj/item/weapon/card/id/stored_card2 = null + +/obj/item/weapon/computer_hardware/card_slot/Destroy() + if(holder2 && (holder2.card_slot == src)) + holder2.card_slot = null + if(stored_card) + stored_card.forceMove(get_turf(holder2)) + if(stored_card2) + stored_card2.forceMove(get_turf(holder2)) + holder2 = null + ..() + +/obj/item/weapon/computer_hardware/card_slot/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(M.card_slot) + user << "This computer's card slot is already occupied by \the [M.card_slot]." + return + found = 1 + M.card_slot = src + ..(user, M, found) \ No newline at end of file diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm new file mode 100644 index 00000000000..fcca93b2b69 --- /dev/null +++ b/code/modules/modular_computers/hardware/hard_drive.dm @@ -0,0 +1,175 @@ +/obj/item/weapon/computer_hardware/hard_drive/ + name = "basic hard drive" + desc = "A small power efficient solid state drive, with 128GQ of storage capacity for use in basic computers where power efficiency is desired." + power_usage = 25 // SSD or something with low power usage + icon_state = "hdd_normal" + hardware_size = 1 + origin_tech = list("programming" = 1, "engineering" = 1) + var/max_capacity = 128 + var/used_capacity = 0 + var/list/stored_files = list() // List of stored files on this drive. DO NOT MODIFY DIRECTLY! + +/obj/item/weapon/computer_hardware/hard_drive/advanced + name = "advanced hard drive" + desc = "A small hybrid hard drive with 256GQ of storage capacity for use in higher grade computers where balance between power efficiency and capacity is desired." + max_capacity = 256 + origin_tech = list("programming" = 2, "engineering" = 2) + power_usage = 50 // Hybrid, medium capacity and medium power storage + icon_state = "hdd_advanced" + hardware_size = 2 + +/obj/item/weapon/computer_hardware/hard_drive/super + name = "super hard drive" + desc = "A small hard drive with 512GQ of storage capacity for use in cluster storage solutions where capacity is more important than power efficiency." + max_capacity = 512 + origin_tech = list("programming" = 3, "engineering" = 3) + power_usage = 100 // High-capacity but uses lots of power, shortening battery life. Best used with APC link. + icon_state = "hdd_super" + hardware_size = 2 + +/obj/item/weapon/computer_hardware/hard_drive/cluster + name = "cluster hard drive" + desc = "A large storage cluster consisting of multiple hard drives for usage in high capacity storage systems. Has capacity of 2048 GQ." + power_usage = 500 + origin_tech = list("programming" = 4, "engineering" = 4) + max_capacity = 2048 + icon_state = "hdd_cluster" + hardware_size = 3 + +// For tablets, etc. - highly power efficient. +/obj/item/weapon/computer_hardware/hard_drive/small + name = "small hard drive" + desc = "A small highly efficient solid state drive for portable devices." + power_usage = 10 + origin_tech = list("programming" = 2, "engineering" = 2) + max_capacity = 64 + icon_state = "hdd_small" + hardware_size = 1 + +/obj/item/weapon/computer_hardware/hard_drive/micro + name = "micro hard drive" + desc = "A small micro hard drive for portable devices." + power_usage = 2 + origin_tech = list("programming" = 1, "engineering" = 1) + max_capacity = 32 + icon_state = "hdd_micro" + hardware_size = 1 + +/obj/item/weapon/computer_hardware/hard_drive/diagnostics(var/mob/user) + ..() + // 999 is a byond limit that is in place. It's unlikely someone will reach that many files anyway, since you would sooner run out of space. + user << "NT-NFS File Table Status: [stored_files.len]/999" + user << "Storage capacity: [used_capacity]/[max_capacity]GQ" + +// Use this proc to add file to the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks. +/obj/item/weapon/computer_hardware/hard_drive/proc/store_file(var/datum/computer_file/F) + if(!F || !istype(F)) + return 0 + + if(!can_store_file(F.size)) + return 0 + + if(!check_functionality()) + return 0 + + if(!stored_files) + return 0 + + // This file is already stored. Don't store it again. + if(F in stored_files) + return 0 + + F.holder = src + stored_files.Add(F) + recalculate_size() + return 1 + +// Use this proc to add file to the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks. +/obj/item/weapon/computer_hardware/hard_drive/proc/install_default_programs() + store_file(new/datum/computer_file/program/computerconfig(src)) // Computer configuration utility, allows hardware control and displays more info than status bar + store_file(new/datum/computer_file/program/ntnetdownload(src)) // NTNet Downloader Utility, allows users to download more software from NTNet repository + store_file(new/datum/computer_file/program/filemanager(src)) // File manager, allows text editor functions and basic file manipulation. + + +// Use this proc to remove file from the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks. +/obj/item/weapon/computer_hardware/hard_drive/proc/remove_file(var/datum/computer_file/F) + if(!F || !istype(F)) + return 0 + + if(!stored_files) + return 0 + + if(!check_functionality()) + return 0 + + if(F in stored_files) + stored_files -= F + recalculate_size() + return 1 + else + return 0 + +// Loops through all stored files and recalculates used_capacity of this drive +/obj/item/weapon/computer_hardware/hard_drive/proc/recalculate_size() + var/total_size = 0 + for(var/datum/computer_file/F in stored_files) + total_size += F.size + + used_capacity = total_size + +// Checks whether file can be stored on the hard drive. +/obj/item/weapon/computer_hardware/hard_drive/proc/can_store_file(var/size = 1) + // In the unlikely event someone manages to create that many files. + // BYOND is acting weird with numbers above 999 in loops (infinite loop prevention) + if(stored_files.len >= 999) + return 0 + if(used_capacity + size > max_capacity) + return 0 + else + return 1 + +// Checks whether we can store the file. We can only store unique files, so this checks whether we wouldn't get a duplicity by adding a file. +/obj/item/weapon/computer_hardware/hard_drive/proc/try_store_file(var/datum/computer_file/F) + if(!F || !istype(F)) + return 0 + var/name = F.filename + "." + F.filetype + for(var/datum/computer_file/file in stored_files) + if((file.filename + "." + file.filetype) == name) + return 0 + return can_store_file(F.size) + + + +// Tries to find the file by filename. Returns null on failure +/obj/item/weapon/computer_hardware/hard_drive/proc/find_file_by_name(var/filename) + if(!check_functionality()) + return null + + if(!filename) + return null + + if(!stored_files) + return null + + for(var/datum/computer_file/F in stored_files) + if(F.filename == filename) + return F + return null + +/obj/item/weapon/computer_hardware/hard_drive/Destroy() + if(holder2 && (holder2.hard_drive == src)) + holder2.hard_drive = null + stored_files = null + ..() + +/obj/item/weapon/computer_hardware/hard_drive/New() + install_default_programs() + ..() + +/obj/item/weapon/computer_hardware/hard_drive/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(M.hard_drive) + user << "This computer's hard drive slot is already occupied by \the [M.hard_drive]." + return + found = 1 + M.hard_drive = src + ..(user, M, found) \ No newline at end of file diff --git a/code/modules/modular_computers/hardware/hardware.dm b/code/modules/modular_computers/hardware/hardware.dm new file mode 100644 index 00000000000..4b0d378286a --- /dev/null +++ b/code/modules/modular_computers/hardware/hardware.dm @@ -0,0 +1,99 @@ +/obj/item/weapon/computer_hardware/ + name = "Hardware" + desc = "Unknown Hardware." + icon = 'icons/obj/modular_components.dmi' + var/obj/item/modular_computer/holder2 = null + var/power_usage = 0 // If the hardware uses extra power, change this. + var/enabled = 1 // If the hardware is turned off set this to 0. + var/critical = 1 // Prevent disabling for important component, like the HDD. + var/hardware_size = 1 // Limits which devices can contain this component. 1: Tablets/Laptops/Consoles, 2: Laptops/Consoles, 3: Consoles only + var/damage = 0 // Current damage level + var/max_damage = 100 // Maximal damage level. + var/damage_malfunction = 20 // "Malfunction" threshold. When damage exceeds this value the hardware piece will semi-randomly fail and do !!FUN!! things + var/damage_failure = 50 // "Failure" threshold. When damage exceeds this value the hardware piece will not work at all. + var/malfunction_probability = 10// Chance of malfunction when the component is damaged + +/obj/item/weapon/computer_hardware/attackby(var/obj/item/W as obj, var/mob/living/user as mob) + // Multitool. Runs diagnostics + if(istype(W, /obj/item/device/multitool)) + user << "***** DIAGNOSTICS REPORT *****" + diagnostics(user) + user << "******************************" + return 1 + // Nanopaste. Repair all damage if present for a single unit. + var/obj/item/stack/S = W +/* + if(istype(S, /obj/item/stack/sheet/glass)) + if(!damage) + user << "\The [src] doesn't seem to require repairs." + return 1 + if(S.use(1)) + user << "You apply a bit of \the [W] to \the [src]. It immediately repairs all damage." + damage = 0 + return 1 +*/ + // Cable coil. Works as repair method, but will probably require multiple applications and more cable. + if(istype(S, /obj/item/stack/cable_coil)) + if(!damage) + user << "\The [src] doesn't seem to require repairs." + return 1 + if(S.use(1)) + user << "You patch up \the [src] with a bit of \the [W]." + take_damage(-10) + return 1 + return ..() + + +// Called on multitool click, prints diagnostic information to the user. +/obj/item/weapon/computer_hardware/proc/diagnostics(var/mob/user) + user << "Hardware Integrity Test... (Corruption: [damage]/[max_damage]) [damage > damage_failure ? "FAIL" : damage > damage_malfunction ? "WARN" : "PASS"]" + +/obj/item/weapon/computer_hardware/New(var/obj/L) + w_class = hardware_size + if(istype(L, /obj/machinery/modular_computer)) + var/obj/machinery/modular_computer/C = L + if(C.cpu) + holder2 = C.cpu + return + if(istype(L, /obj/item/modular_computer)) + holder2 = L + return + +/obj/item/weapon/computer_hardware/Destroy() + holder2 = null + return ..() + +// Handles damage checks +/obj/item/weapon/computer_hardware/proc/check_functionality() + // Too damaged to work at all. + if(damage > damage_failure) + return 0 + // Still working. Well, sometimes... + if(damage > damage_malfunction) + if(prob(malfunction_probability)) + return 0 + // Good to go. + return 1 + +/obj/item/weapon/computer_hardware/examine(var/mob/user) + . = ..() + if(damage > damage_failure) + user << "It seems to be severely damaged!" + else if(damage > damage_malfunction) + user << "It seems to be damaged!" + else if(damage) + user << "It seems to be slightly damaged." + +// Damages the component. Contains necessary checks. Negative damage "heals" the component. +/obj/item/weapon/computer_hardware/proc/take_damage(var/amount) + damage += round(amount) // We want nice rounded numbers here. + damage = max(0, min(damage, max_damage)) // Clamp the value. + +// Attempts to install the hardware into apropriate slot. +/obj/item/weapon/computer_hardware/proc/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(found) + user << "You install \the [src] into \the [M]" + holder2 = M + if(!user.drop_item(src)) + return + forceMove(M) \ No newline at end of file diff --git a/code/modules/modular_computers/hardware/nano_printer.dm b/code/modules/modular_computers/hardware/nano_printer.dm new file mode 100644 index 00000000000..83b33418647 --- /dev/null +++ b/code/modules/modular_computers/hardware/nano_printer.dm @@ -0,0 +1,60 @@ +/obj/item/weapon/computer_hardware/nano_printer + name = "nano printer" + desc = "Small integrated printer with paper recycling module." + power_usage = 50 + origin_tech = list("programming" = 2, "engineering" = 2) + critical = 0 + icon_state = "printer" + hardware_size = 1 + var/stored_paper = 5 + var/max_paper = 10 + +/obj/item/weapon/computer_hardware/nano_printer/diagnostics(var/mob/user) + ..() + user << "Paper buffer level: [stored_paper]/[max_paper]" + +/obj/item/weapon/computer_hardware/nano_printer/proc/print_text(var/text_to_print, var/paper_title = null) + if(!stored_paper) + return 0 + if(!enabled) + return 0 + if(!check_functionality()) + return 0 + + var/obj/item/weapon/paper/P = new/obj/item/weapon/paper(get_turf(holder2)) + + // Damaged printer causes the resulting paper to be somewhat harder to read. + if(damage > damage_malfunction) + P.info = stars(text_to_print, 100-malfunction_probability) + else + P.info = text_to_print + if(paper_title) + P.name = paper_title + P.update_icon() + stored_paper-- + P = null + return 1 + +/obj/item/weapon/computer_hardware/nano_printer/attackby(obj/item/W as obj, mob/user as mob) + if(istype(W, /obj/item/weapon/paper)) + if(stored_paper >= max_paper) + user << "You try to add \the [W] into [src], but it's paper bin is full" + return + + user << "You insert \the [W] into [src]." + qdel(W) + stored_paper++ + +/obj/item/weapon/computer_hardware/nano_printer/Destroy() + if(holder2 && (holder2.nano_printer == src)) + holder2.nano_printer = null + holder2 = null + ..() + +/obj/item/weapon/computer_hardware/nano_printer/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(M.nano_printer) + user << "This computer's nano printer slot is already occupied by \the [M.nano_printer]." + return + found = 1 + M.nano_printer = src + ..(user, M, found) \ No newline at end of file diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm new file mode 100644 index 00000000000..41a69042b4a --- /dev/null +++ b/code/modules/modular_computers/hardware/network_card.dm @@ -0,0 +1,104 @@ +var/global/ntnet_card_uid = 1 + +/obj/item/weapon/computer_hardware/network_card/ + name = "basic NTNet network card" + desc = "A basic network card for usage with standard NTNet frequencies." + power_usage = 50 + origin_tech = list("programming" = 2, TECH_ENGINEERING = 1) + critical = 0 + icon_state = "netcard_basic" + hardware_size = 1 + var/identification_id = null // Identification ID. Technically MAC address of this device. Can't be changed by user. + var/identification_string = "" // Identification string, technically nickname seen in the network. Can be set by user. + var/long_range = 0 + var/ethernet = 0 // Hard-wired, therefore always on, ignores NTNet wireless checks. + malfunction_probability = 1 + +/obj/item/weapon/computer_hardware/network_card/diagnostics(var/mob/user) + ..() + user << "NIX Unique ID: [identification_id]" + user << "NIX User Tag: [identification_string]" + user << "Supported protocols:" + user << "511.m SFS (Subspace) - Standard Frequency Spread" + if(long_range) + user << "511.n WFS/HB (Subspace) - Wide Frequency Spread/High Bandiwdth" + if(ethernet) + user << "OpenEth (Physical Connection) - Physical network connection port" + +/obj/item/weapon/computer_hardware/network_card/New(var/l) + ..(l) + identification_id = ntnet_card_uid + ntnet_card_uid++ + +/obj/item/weapon/computer_hardware/network_card/advanced + name = "advanced NTNet network card" + desc = "An advanced network card for usage with standard NTNet frequencies. It's transmitter is strong enough to connect even off-station." + long_range = 1 + origin_tech = list("programming" = 4, "engineering" = 2) + power_usage = 100 // Better range but higher power usage. + icon_state = "netcard_advanced" + hardware_size = 1 + +/obj/item/weapon/computer_hardware/network_card/wired + name = "wired NTNet network card" + desc = "An advanced network card for usage with standard NTNet frequencies. This one also supports wired connection." + ethernet = 1 + origin_tech = list("programming" = 5, "engineering" = 3) + power_usage = 100 // Better range but higher power usage. + icon_state = "netcard_ethernet" + hardware_size = 3 + +/obj/item/weapon/computer_hardware/network_card/Destroy() + if(holder2 && (holder2.network_card == src)) + holder2.network_card = null + holder2 = null + ..() + +// Returns a string identifier of this network card +/obj/item/weapon/computer_hardware/network_card/proc/get_network_tag() + return "[identification_string] (NID [identification_id])" + +// 0 - No signal, 1 - Low signal, 2 - High signal. 3 - Wired Connection +/obj/item/weapon/computer_hardware/network_card/proc/get_signal(var/specific_action = 0) + if(!holder2) // Hardware is not installed in anything. No signal. How did this even get called? + return 0 + + if(!enabled) + return 0 + + if(!check_functionality()) + return 0 + + if(ethernet) // Computer is connected via wired connection. + return 3 + + if(!ntnet_global || !ntnet_global.check_function(specific_action)) // NTNet is down and we are not connected via wired connection. No signal. + return 0 + + if(holder2) + + var/turf/T = get_turf(holder2) + if((T && istype(T)) && (T.z == ZLEVEL_STATION || T.z == ZLEVEL_MINING)) + // Computer is on station. Low/High signal depending on what type of network card you have + if(long_range) + return 2 + else + return 1 + + if(long_range) // Computer is not on station, but it has upgraded network card. Low signal. + return 1 + + return 0 // Computer is not on station and does not have upgraded network card. No signal. + +/obj/item/weapon/computer_hardware/network_card/Destroy() + if(holder2 && (holder2.network_card == src)) + holder2.network_card = null + ..() + +/obj/item/weapon/computer_hardware/network_card/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(M.network_card) + user << "This computer's network card slot is already occupied by \the [M.network_card]." + return + found = 1 + M.network_card = src + ..(user, M, found) \ No newline at end of file diff --git a/code/modules/modular_computers/hardware/portable_hard_drive.dm b/code/modules/modular_computers/hardware/portable_hard_drive.dm new file mode 100644 index 00000000000..e4ff4d7d09e --- /dev/null +++ b/code/modules/modular_computers/hardware/portable_hard_drive.dm @@ -0,0 +1,41 @@ +// These are basically USB data sticks and may be used to transfer files between devices +/obj/item/weapon/computer_hardware/hard_drive/portable/ + name = "basic data crystal" + desc = "Small crystal with imprinted photonic circuits that can be used to store data. It's capacity is 16 GQ." + power_usage = 10 + icon_state = "flashdrive_basic" + hardware_size = 1 + max_capacity = 16 + origin_tech = list("programming" = 1) + +/obj/item/weapon/computer_hardware/hard_drive/portable/advanced + name = "advanced data crystal" + desc = "Small crystal with imprinted high-density photonic circuits that can be used to store data. It's capacity is 64 GQ." + power_usage = 20 + icon_state = "flashdrive_advanced" + hardware_size = 1 + max_capacity = 64 + origin_tech = list("programming" = 2) + +/obj/item/weapon/computer_hardware/hard_drive/portable/super + name = "super data crystal" + desc = "Small crystal with imprinted ultra-density photonic circuits that can be used to store data. It's capacity is 256 GQ." + power_usage = 40 + icon_state = "flashdrive_super" + hardware_size = 1 + max_capacity = 256 + origin_tech = list("programming" = 4) + +/obj/item/weapon/computer_hardware/hard_drive/portable/New() + ..() + stored_files = list() + recalculate_size() + + +/obj/item/weapon/computer_hardware/hard_drive/portable/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(M.portable_drive) + user << "This computer's portable drive slot is already occupied by \the [M.portable_drive]." + return + found = 1 + M.portable_drive = src + ..(user, M, found) \ No newline at end of file diff --git a/code/modules/modular_computers/hardware/processor_unit.dm b/code/modules/modular_computers/hardware/processor_unit.dm new file mode 100644 index 00000000000..75037392160 --- /dev/null +++ b/code/modules/modular_computers/hardware/processor_unit.dm @@ -0,0 +1,49 @@ +// CPU that allows the computer to run programs. +// Better CPUs are obtainable via research and can run more programs on background. + +/obj/item/weapon/computer_hardware/processor_unit + name = "standard processor" + desc = "A standard CPU used in most computers. It can run up to three programs simultaneously." + icon_state = "cpu_normal" + hardware_size = 2 + power_usage = 50 + critical = 1 + malfunction_probability = 1 + origin_tech = list("programming" = 3, "engineering" = 2) + + var/max_idle_programs = 2 // 2 idle, + 1 active = 3 as said in description. + +/obj/item/weapon/computer_hardware/processor_unit/small + name = "standard microprocessor" + desc = "A standard miniaturised CPU used in portable devices. It can run up to two programs simultaneously." + icon_state = "cpu_small" + hardware_size = 1 + power_usage = 25 + max_idle_programs = 1 + origin_tech = list("programming" = 2, "engineering" = 2) + +/obj/item/weapon/computer_hardware/processor_unit/photonic + name = "photonic processor" + desc = "An advanced experimental CPU that uses photonic core instead of regular circuitry. It can run up to five programs simultaneously, but uses a lot of power." + icon_state = "cpu_normal_photonic" + hardware_size = 2 + power_usage = 250 + max_idle_programs = 4 + origin_tech = list("programming" = 5, "engineering" = 4) + +/obj/item/weapon/computer_hardware/processor_unit/photonic/small + name = "photonic microprocessor" + desc = "An advanced miniaturised CPU for use in portable devices. It uses photonic core instead of regular circuitry. It can run up to three programs simultaneously." + icon_state = "cpu_small_photonic" + hardware_size = 1 + power_usage = 75 + max_idle_programs = 2 + origin_tech = list("programming" = 4, "engineering" = 3) + +/obj/item/weapon/computer_hardware/processor_unit/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(M.processor_unit) + user << "This computer's processor slot is already occupied by \the [M.processor_unit]." + return + found = 1 + M.processor_unit = src + ..(user, M, found) \ No newline at end of file diff --git a/code/modules/modular_computers/hardware/tesla_link.dm b/code/modules/modular_computers/hardware/tesla_link.dm new file mode 100644 index 00000000000..deae760d467 --- /dev/null +++ b/code/modules/modular_computers/hardware/tesla_link.dm @@ -0,0 +1,31 @@ +/obj/item/weapon/computer_hardware/tesla_link + name = "tesla link" + desc = "An advanced tesla link that wirelessly recharges connected device from nearby area power controller." + critical = 0 + enabled = 1 + icon_state = "teslalink" + hardware_size = 2 // Can't be installed into tablets + origin_tech = list("programming" = 2, "powerstorage" = 3, "engineering" = 2) + var/obj/machinery/modular_computer/holder + +/obj/item/weapon/computer_hardware/tesla_link/New(var/obj/L) + if(istype(L, /obj/machinery/modular_computer)) + holder = L + return + ..(L) + +/obj/item/weapon/computer_hardware/tesla_link/Destroy() + if(holder && (holder.tesla_link == src)) + holder.tesla_link = null + ..() + +/obj/item/weapon/computer_hardware/tesla_link/try_install_component(mob/living/user, obj/item/modular_computer/M, found = 0) + if(istype(M, /obj/item/modular_computer/processor)) + var/obj/item/modular_computer/processor/P = M + if(P.machinery_computer.tesla_link) + user << "This computer's tesla link slot is already occupied by \the [P.machinery_computer.tesla_link]." + return + holder = P.machinery_computer + P.machinery_computer.tesla_link = src + found = 1 + ..(user, M, found) \ No newline at end of file diff --git a/code/modules/ninja/ninja_event.dm b/code/modules/ninja/ninja_event.dm index 6693981e0fa..1df674feb32 100644 --- a/code/modules/ninja/ninja_event.dm +++ b/code/modules/ninja/ninja_event.dm @@ -55,7 +55,7 @@ Contents: if(!candidates.len) return NOT_ENOUGH_PLAYERS - var/mob/dead/selected_candidate = popleft(candidates) + var/mob/dead/selected_candidate = pick_n_take(candidates) var/key = selected_candidate.key //Prepare ninja player mind diff --git a/code/modules/power/antimatter/shielding.dm b/code/modules/power/antimatter/shielding.dm index 21191f9fb0f..7524010b166 100644 --- a/code/modules/power/antimatter/shielding.dm +++ b/code/modules/power/antimatter/shielding.dm @@ -91,20 +91,6 @@ /obj/machinery/am_shielding/emp_act()//Immune due to not really much in the way of electronics. return 0 - -/obj/machinery/am_shielding/blob_act() - stability -= 20 - if(prob(100-stability)) - if(prob(10))//Might create a node - new /obj/effect/blob/node(src.loc,150) - else - new /obj/effect/blob(src.loc,60) - qdel(src) - return - check_stability() - return - - /obj/machinery/am_shielding/ex_act(severity, target) stability -= (80 - (severity * 20)) check_stability() diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index fb20f6c8ee1..a5e754cd015 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -93,6 +93,10 @@ By design, d1 is the smallest direction and d2 is the highest cable_list -= src //remove it from global cable list return ..() // then go ahead and delete the cable +/obj/structure/cable/blob_act(obj/effect/blob/B) + if(invisibility != INVISIBILITY_MAXIMUM) + qdel(src) + /obj/structure/cable/Deconstruct() var/turf/T = loc stored.loc = T diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index 97855e2e162..7b2f9cab0f8 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -116,6 +116,10 @@ return return ..() +/obj/machinery/light_construct/blob_act(obj/effect/blob/B) + if(B && B.loc == loc) + qdel(src) + /obj/machinery/light_construct/small name = "small light fixture frame" @@ -522,6 +526,15 @@ if(prob(25)) broken() +/obj/machinery/light/blob_act(obj/effect/blob/B) + if(B && B.loc == loc) + broken() + qdel(src) + else if(prob(50)) + broken() + else + flicker() + // called when area power state changes /obj/machinery/light/power_change() var/area/A = get_area(src) diff --git a/code/modules/projectiles/ammunition/energy.dm b/code/modules/projectiles/ammunition/energy.dm index 8d37a225f4a..abe46e6732d 100644 --- a/code/modules/projectiles/ammunition/energy.dm +++ b/code/modules/projectiles/ammunition/energy.dm @@ -111,18 +111,6 @@ projectile_type = /obj/item/projectile/meteor select_name = "goddamn meteor" -/obj/item/ammo_casing/energy/kinetic - projectile_type = /obj/item/projectile/kinetic - select_name = "kinetic" - e_cost = 500 - fire_sound = 'sound/weapons/Kenetic_accel.ogg' // fine spelling there chap - -/obj/item/ammo_casing/energy/kinetic/super - projectile_type = /obj/item/projectile/kinetic/super - -/obj/item/ammo_casing/energy/kinetic/hyper - projectile_type = /obj/item/projectile/kinetic/hyper - /obj/item/ammo_casing/energy/disabler projectile_type = /obj/item/projectile/beam/disabler select_name = "disable" diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm new file mode 100644 index 00000000000..4d5d8ffa6a2 --- /dev/null +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -0,0 +1,238 @@ +/obj/item/weapon/gun/energy/kinetic_accelerator + name = "proto-kinetic accelerator" + desc = "A self recharging, " + icon_state = "kineticgun" + item_state = "kineticgun" + ammo_type = list(/obj/item/ammo_casing/energy/kinetic) + cell_type = /obj/item/weapon/stock_parts/cell/emproof + needs_permit = 0 + unique_rename = 1 + origin_tech = "combat=3;powerstorage=3;engineering=3" + weapon_weight = WEAPON_LIGHT + can_flashlight = 1 + flight_x_offset = 15 + flight_y_offset = 9 + var/overheat_time = 16 + var/holds_charge = FALSE + var/unique_frequency = FALSE // modified by KA modkits + var/overheat = FALSE + + var/list/max_mod_capacity = 2 + var/list/modkits = list() + + +/obj/item/weapon/gun/energy/kinetic_accelerator/attackby(obj/item/A, mob/user) + if(istype(A, /obj/item/weapon/crowbar)) + if(modkits.len) + user << "You pry the modifications out." + for(var/obj/item/modkit/M in modkits) + M.forceMove(get_turf(user)) + else + user << "There are no modifications currently installed." + else if(istype(A, /obj/item/modkit)) + var/obj/item/modkit/MK = A + var/current_mod_capacity = 0 + for(var/obj/item/modkit/M in modkits) + current_mod_capacity += M.cost + if((current_mod_capacity + MK.cost) > max_mod_capacity) + user << "You don't have room to install this modkit. Use a crowbar to remove existing modkits." + return + else + user << "You install the modkit." + MK.install(src) + else + ..() + + +/obj/item/weapon/gun/energy/kinetic_accelerator/proc/modify_projectile(obj/item/projectile/kinetic/K) + for(var/obj/item/modkit/M in src) + M.modify_projectile(K) + +/obj/item/weapon/gun/energy/kinetic_accelerator/cyborg + holds_charge = TRUE + unique_frequency = TRUE + +/obj/item/weapon/gun/energy/kinetic_accelerator/hyper/cyborg + holds_charge = TRUE + unique_frequency = TRUE + +/obj/item/weapon/gun/energy/kinetic_accelerator/New() + . = ..() + if(!holds_charge) + empty() + +/obj/item/weapon/gun/energy/kinetic_accelerator/shoot_live_shot() + . = ..() + attempt_reload() + +/obj/item/weapon/gun/energy/kinetic_accelerator/equipped(mob/user) + . = ..() + if(!can_shoot()) + attempt_reload() + +/obj/item/weapon/gun/energy/kinetic_accelerator/dropped() + . = ..() + if(!holds_charge) + // Put it on a delay because moving item from slot to hand + // calls dropped(). + sleep(1) + if(!ismob(loc)) + empty() + +/obj/item/weapon/gun/energy/kinetic_accelerator/proc/empty() + power_supply.use(500) + update_icon() + +/obj/item/weapon/gun/energy/kinetic_accelerator/proc/attempt_reload() + if(overheat) + return + overheat = TRUE + + var/carried = 0 + if(!unique_frequency) + for(var/obj/item/weapon/gun/energy/kinetic_accelerator/K in \ + loc.GetAllContents()) + + carried++ + + carried = max(carried, 1) + else + carried = 1 + + addtimer(src, "reload", overheat_time * carried) + +/obj/item/weapon/gun/energy/kinetic_accelerator/emp_act(severity) + return + +/obj/item/weapon/gun/energy/kinetic_accelerator/proc/reload() + power_supply.give(500) + if(!suppressed) + playsound(src.loc, 'sound/weapons/kenetic_reload.ogg', 60, 1) + else + loc << "[src] silently charges up." + update_icon() + overheat = FALSE + +/obj/item/weapon/gun/energy/kinetic_accelerator/update_icon() + cut_overlays() + if(!can_shoot()) + add_overlay("kineticgun_empty") + + if(F && can_flashlight) + var/iconF = "flight" + if(F.on) + iconF = "flight_on" + add_overlay(image(icon = icon, icon_state = iconF, pixel_x = flight_x_offset, pixel_y = flight_y_offset)) + + + + + + + + + + +//Casing + + +/obj/item/ammo_casing/energy/kinetic + projectile_type = /obj/item/projectile/kinetic + select_name = "kinetic" + e_cost = 500 + fire_sound = 'sound/weapons/Kenetic_accel.ogg' // fine spelling there chap + +/obj/item/ammo_casing/energy/kinetic/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") + ..() + if(loc && istype(loc, /obj/item/weapon/gun/energy/kinetic_accelerator)) + var/obj/item/weapon/gun/energy/kinetic_accelerator/KA = loc + KA.modify_projectile(BB) + +/obj/item/ammo_casing/energy/kinetic/hyper + projectile_type = /obj/item/projectile/kinetic/hyper + + + +//Projectiles + +/obj/item/projectile/kinetic + name = "kinetic force" + icon_state = null + damage = 10 + damage_type = BRUTE + flag = "bomb" + range = 3 + var/damage_multiplier = 4 + +/obj/item/projectile/kinetic/super + damage = 11 + range = 4 + +/obj/item/projectile/kinetic/New() + var/turf/proj_turf = get_turf(src) + if(!istype(proj_turf, /turf)) + return + var/datum/gas_mixture/environment = proj_turf.return_air() + var/pressure = environment.return_pressure() + if(pressure < 50) + name = "full strength [name]" + damage *= damage_multiplier + ..() + +/obj/item/projectile/kinetic/on_range() + PoolOrNew(/obj/effect/overlay/temp/kinetic_blast, loc) + ..() + +/obj/item/projectile/kinetic/on_hit(atom/target) + var/turf/target_turf= get_turf(target) + if(istype(target_turf, /turf/closed/mineral)) + var/turf/closed/mineral/M = target_turf + M.gets_drilled(firer) + PoolOrNew(/obj/effect/overlay/temp/kinetic_blast, target_turf) + . = ..() + + +//AoE + +/obj/item/projectile/kinetic/hyper + name = "kinetic explosion" + damage = 10 + range = 3 + +/obj/item/projectile/kinetic/hyper/proc/aoe_blast(atom/target) + var/turf/target_turf = get_turf(target) + if(!target_turf) + target_turf = get_turf(src) + PoolOrNew(/obj/effect/overlay/temp/explosion/fast, target_turf) + for(var/T in RANGE_TURFS(1, target_turf) - target_turf) + if(istype(T, /turf/closed/mineral)) + var/turf/closed/mineral/M = T + M.gets_drilled(firer) + +/obj/item/projectile/kinetic/hyper/on_range() + aoe_blast() + ..() + +/obj/item/projectile/kinetic/hyper/on_hit(atom/target) + aoe_blast(target) + . = ..() + + + + + +//Modkits + +/obj/item/modkit + name = "modification kit" + desc = "An upgrade for kinetic accelerators.." + icon = 'icons/obj/objects.dmi' + icon_state = "modkit" + origin_tech = "programming=2;materials=2;magnets=4" + var/cost = 2 + +/obj/item/modkit/proc/install(obj/item/weapon/gun/energy/kinetic_accelerator/KA) + return + +/obj/item/modkit/proc/modify_projectile(/obj/item/projectile/kinetic/K) + return \ No newline at end of file diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index 7154426dae5..7757df71821 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -82,146 +82,6 @@ ammo_type = list(/obj/item/ammo_casing/energy/mindflayer) ammo_x_offset = 2 -/obj/item/weapon/gun/energy/kinetic_accelerator - name = "proto-kinetic accelerator" - desc = "In the year 2544, only a year after the discovery of a potentially \ - world-changing substance, now colloquially referred to as plasma, the \ - Nanotrasen-UEG mining conglomerate introduced a prototype of a gun-like \ - device intended for quick, effective mining of plasma in the low \ - pressures of the solar system. Included in this presentation were \ - demonstrations of the gun being fired at collections of rocks contained \ - in vacuumed environments, obliterating them instantly while maintaining \ - the structure of the ores buried within them. Additionally, volunteers \ - were called from the crowd to have the gun used on them, only proving that \ - the gun caused little harm to objects in standard pressure. \n\ - An official from an unnamed, now long dissipated company observed this \ - presentation and offered to share their self-recharger cells, powered \ - by the user's bioelectrical field, another new and unknown technology. \ - They warned that the cells were incredibly experimental and several times \ - had injured workers, but the scientists as Nanotrasen were unable to resist \ - the money-saving potential of self recharging cells. Upon accepting this \ - offer, it took only a matter of days to prove the volatility of these cells, \ - as they exploded left and right whenever inserted into the prototype devices, \ - only throwing more money in the bin. \n\ - Whenever the Nanotrasen scientists were on the edge of giving up, a \ - breakthrough was made by head researcher Miles Parks McCollum, who \ - demonstrated that the cells could be stabilized when exposed to radium \ - then cooled with cryostylane. After this discovery, the low pressure gun, \ - now named the Kinetic Accelerator, was hastily completed and made compatible \ - with the self-recharging cells. As a result of poor testing, the currently \ - used guns lose their charge when not in use, and when two Kinetic Accelerators \ - come in proximity of one another, they will interfere with each other. Despite \ - this, the shoddy guns still see use in the mining of plasma to this day." - icon_state = "kineticgun" - item_state = "kineticgun" - ammo_type = list(/obj/item/ammo_casing/energy/kinetic) - cell_type = /obj/item/weapon/stock_parts/cell/emproof - // Apparently these are safe to carry? I'm sure goliaths would disagree. - needs_permit = 0 - unique_rename = 1 - origin_tech = "combat=3;powerstorage=3;engineering=3" - weapon_weight = WEAPON_LIGHT - can_flashlight = 1 - flight_x_offset = 15 - flight_y_offset = 9 - var/overheat_time = 16 - var/holds_charge = FALSE - var/unique_frequency = FALSE // modified by KA modkits - var/overheat = FALSE - -/obj/item/weapon/gun/energy/kinetic_accelerator/super - name = "super-kinetic accelerator" - desc = "An upgraded, superior version of the proto-kinetic accelerator." - icon_state = "kineticgun_u" - ammo_type = list(/obj/item/ammo_casing/energy/kinetic/super) - overheat_time = 15 - origin_tech = "materials=5;powerstorage=3;engineering=4;magnets=3;combat=3" - -/obj/item/weapon/gun/energy/kinetic_accelerator/hyper - name = "hyper-kinetic accelerator" - desc = "An upgraded, even more superior version of the proto-kinetic accelerator." - icon_state = "kineticgun_h" - ammo_type = list(/obj/item/ammo_casing/energy/kinetic/hyper) - overheat_time = 14 - origin_tech = "materials=6;powerstorage=4;engineering=4;magnets=4;combat=4" - -/obj/item/weapon/gun/energy/kinetic_accelerator/cyborg - holds_charge = TRUE - unique_frequency = TRUE - -/obj/item/weapon/gun/energy/kinetic_accelerator/hyper/cyborg - holds_charge = TRUE - unique_frequency = TRUE - -/obj/item/weapon/gun/energy/kinetic_accelerator/New() - . = ..() - if(!holds_charge) - empty() - -/obj/item/weapon/gun/energy/kinetic_accelerator/shoot_live_shot() - . = ..() - attempt_reload() - -/obj/item/weapon/gun/energy/kinetic_accelerator/equipped(mob/user) - . = ..() - if(!can_shoot()) - attempt_reload() - -/obj/item/weapon/gun/energy/kinetic_accelerator/dropped() - . = ..() - if(!holds_charge) - // Put it on a delay because moving item from slot to hand - // calls dropped(). - sleep(1) - if(!ismob(loc)) - empty() - -/obj/item/weapon/gun/energy/kinetic_accelerator/proc/empty() - power_supply.use(500) - update_icon() - -/obj/item/weapon/gun/energy/kinetic_accelerator/proc/attempt_reload() - if(overheat) - return - overheat = TRUE - - var/carried = 0 - if(!unique_frequency) - for(var/obj/item/weapon/gun/energy/kinetic_accelerator/K in \ - loc.GetAllContents()) - - carried++ - - carried = max(carried, 1) - else - carried = 1 - - addtimer(src, "reload", overheat_time * carried) - -/obj/item/weapon/gun/energy/kinetic_accelerator/emp_act(severity) - return - -/obj/item/weapon/gun/energy/kinetic_accelerator/proc/reload() - power_supply.give(500) - if(!suppressed) - playsound(src.loc, 'sound/weapons/kenetic_reload.ogg', 60, 1) - else - loc << "[src] silently charges up." - update_icon() - overheat = FALSE - -/obj/item/weapon/gun/energy/kinetic_accelerator/update_icon() - cut_overlays() - if(!can_shoot()) - add_overlay("kineticgun_empty") - - if(F && can_flashlight) - var/iconF = "flight" - if(F.on) - iconF = "flight_on" - add_overlay(image(icon = icon, icon_state = iconF, pixel_x = flight_x_offset, pixel_y = flight_y_offset)) - - /obj/item/weapon/gun/energy/kinetic_accelerator/crossbow name = "mini energy crossbow" desc = "A weapon favored by syndicate stealth specialists." diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/special.dm index aabb1fd448e..0dadac9de80 100644 --- a/code/modules/projectiles/projectile/special.dm +++ b/code/modules/projectiles/projectile/special.dm @@ -121,64 +121,6 @@ M.adjustBrainLoss(20) M.hallucination += 20 -/obj/item/projectile/kinetic - name = "kinetic force" - icon_state = null - damage = 10 - damage_type = BRUTE - flag = "bomb" - range = 3 - -/obj/item/projectile/kinetic/super - damage = 11 - range = 4 - -/obj/item/projectile/kinetic/New() - var/turf/proj_turf = get_turf(src) - if(!istype(proj_turf, /turf)) - return - var/datum/gas_mixture/environment = proj_turf.return_air() - var/pressure = environment.return_pressure() - if(pressure < 50) - name = "full strength [name]" - damage *= 4 - ..() - -/obj/item/projectile/kinetic/on_range() - PoolOrNew(/obj/effect/overlay/temp/kinetic_blast, loc) - ..() - -/obj/item/projectile/kinetic/on_hit(atom/target) - var/turf/target_turf= get_turf(target) - if(istype(target_turf, /turf/closed/mineral)) - var/turf/closed/mineral/M = target_turf - M.gets_drilled(firer) - PoolOrNew(/obj/effect/overlay/temp/kinetic_blast, target_turf) - . = ..() - -/obj/item/projectile/kinetic/hyper - name = "kinetic explosion" - damage = 12 - range = 5 - -/obj/item/projectile/kinetic/hyper/proc/aoe_blast(atom/target) - var/turf/target_turf = get_turf(target) - if(!target_turf) - target_turf = get_turf(src) - PoolOrNew(/obj/effect/overlay/temp/explosion/fast, target_turf) - for(var/T in RANGE_TURFS(1, target_turf) - target_turf) - if(istype(T, /turf/closed/mineral)) - var/turf/closed/mineral/M = T - M.gets_drilled(firer) - -/obj/item/projectile/kinetic/hyper/on_range() - aoe_blast() - ..() - -/obj/item/projectile/kinetic/hyper/on_hit(atom/target) - aoe_blast(target) - . = ..() - /obj/item/projectile/beam/wormhole name = "bluespace beam" icon_state = "spark" diff --git a/code/modules/reagents/chemistry/reagents/blob_reagents.dm b/code/modules/reagents/chemistry/reagents/blob_reagents.dm index efd4a585fce..51b50bb9397 100644 --- a/code/modules/reagents/chemistry/reagents/blob_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/blob_reagents.dm @@ -5,12 +5,20 @@ color = "#FFFFFF" var/complementary_color = "#000000" //a color that's complementary to the normal blob color var/shortdesc = null //just damage and on_mob effects, doesn't include special, blob-tile only effects + var/effectdesc = null //any long, blob-tile specific effects var/analyzerdescdamage = "Unknown. Report this bug to a coder, or just adminhelp." var/analyzerdesceffect = "N/A" var/blobbernaut_message = "slams" //blobbernaut attack verb var/message = "The blob strikes you" //message sent to any mob hit by the blob var/message_living = null //extension to first mob sent to only living mobs i.e. silicons have no skin to be burnt +/datum/reagent/blob/proc/send_message(mob/living/M) + var/totalmessage = message + if(message_living && !issilicon(M)) + totalmessage += message_living + totalmessage += "!" + M << "[totalmessage]" + /datum/reagent/blob/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) if(M.stat == DEAD || istype(M, /mob/living/simple_animal/hostile/blob)) return 0 //the dead, and blob mobs, don't cause reactions @@ -34,44 +42,13 @@ /datum/reagent/blob/proc/emp_reaction(obj/effect/blob/B, severity) //when the blob is hit with an emp, do this return -//does low toxin damage, but creates fragile spores when expanding or killed by weak attacks -/datum/reagent/blob/sporing_pods - name = "Sporing Pods" - id = "sporing_pods" - description = "will do very low toxin damage and produce fragile spores when killed or on expanding." - shortdesc = "will do very low toxin damage." - analyzerdescdamage = "Does very low toxin damage." - analyzerdesceffect = "Produces spores when expanding and when killed." - color = "#E88D5D" - complementary_color = "#5DB8E8" - message_living = ", and you feel sick" - -/datum/reagent/blob/sporing_pods/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - M.apply_damage(0.2*reac_volume, TOX) - -/datum/reagent/blob/sporing_pods/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) - if(!isnull(cause) && damage <= 20 && original_health - damage <= 0 && prob(30)) //if the cause isn't fire or a bomb, the damage is less than 21, we're going to die from that damage, 30% chance of a shitty spore. - B.visible_message("A spore floats free of the blob!") - var/mob/living/simple_animal/hostile/blob/blobspore/weak/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(B.loc) - BS.overmind = B.overmind - BS.update_icons() - B.overmind.blob_mobs.Add(BS) - return ..() - -/datum/reagent/blob/sporing_pods/expand_reaction(obj/effect/blob/B, obj/effect/blob/newB, turf/T) - if(prob(12)) - var/mob/living/simple_animal/hostile/blob/blobspore/weak/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(T) - BS.overmind = B.overmind - BS.update_icons() - newB.overmind.blob_mobs.Add(BS) - //does brute damage but can replicate when damaged and has a chance of expanding again /datum/reagent/blob/replicating_foam name = "Replicating Foam" id = "replicating_foam" - description = "will do medium brute damage, take increased brute damage, and expand when burned." + description = "will do medium brute damage and occasionally expand again when expanding." shortdesc = "will do medium brute damage." + effectdesc = "will also expand when attacked with burn damage, but takes more brute damage." analyzerdescdamage = "Does medium brute damage." analyzerdesceffect = "Expands when attacked with burn damage, will occasionally expand again when expanding, and is fragile to brute damage." color = "#7B5A57" @@ -79,13 +56,13 @@ /datum/reagent/blob/replicating_foam/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() - M.apply_damage(0.5*reac_volume, BRUTE) + M.apply_damage(0.7*reac_volume, BRUTE) /datum/reagent/blob/replicating_foam/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) var/effectivedamage = damage if(damage_type == BRUTE) effectivedamage = damage * 2 - if(damage_type == BURN && effectivedamage > 0 && original_health - effectivedamage > 0 && prob(75)) + if(damage_type == BURN && effectivedamage > 0 && original_health - effectivedamage > 0 && prob(60)) var/obj/effect/blob/newB = B.expand(null, null, 0) if(newB) newB.health = original_health - effectivedamage @@ -95,14 +72,14 @@ /datum/reagent/blob/replicating_foam/expand_reaction(obj/effect/blob/B, obj/effect/blob/newB, turf/T) if(prob(30)) - newB.expand() //do it again! + newB.expand(null, null, 0) //do it again! //does brute damage, shifts away when damaged /datum/reagent/blob/shifting_fragments name = "Shifting Fragments" id = "shifting_fragments" - description = "will do medium brute damage and shift away from damage." - shortdesc = "will do medium brute damage." + description = "will do medium brute damage." + effectdesc = "will also cause blob parts to shift away when attacked." analyzerdescdamage = "Does medium brute damage." analyzerdesceffect = "When attacked, may shift away from the attacker." color = "#C8963C" @@ -110,18 +87,18 @@ /datum/reagent/blob/shifting_fragments/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() - M.apply_damage(0.6*reac_volume, BRUTE) + M.apply_damage(0.7*reac_volume, BRUTE) /datum/reagent/blob/shifting_fragments/expand_reaction(obj/effect/blob/B, obj/effect/blob/newB, turf/T) - if(istype(B, /obj/effect/blob/normal) || (istype(B, /obj/effect/blob/shield) && prob(20))) + if(istype(B, /obj/effect/blob/normal) || (istype(B, /obj/effect/blob/shield) && prob(25))) newB.forceMove(get_turf(B)) B.forceMove(T) /datum/reagent/blob/shifting_fragments/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) - if(cause && damage > 0 && original_health - damage > 0 && prob(40)) + if(cause && damage > 0 && original_health - damage > 0 && prob(60-damage)) var/list/blobstopick = list() for(var/obj/effect/blob/OB in orange(1, B)) - if((istype(OB, /obj/effect/blob/normal) || istype(OB, /obj/effect/blob/shield)) && OB.overmind && OB.overmind.blob_reagent_datum.id == B.overmind.blob_reagent_datum.id) + if((istype(OB, /obj/effect/blob/normal) || (istype(OB, /obj/effect/blob/shield) && prob(25))) && OB.overmind && OB.overmind.blob_reagent_datum.id == B.overmind.blob_reagent_datum.id) blobstopick += OB //as long as the blob picked is valid; ie, a normal or shield blob that has the same chemical as we do, we can swap with it if(blobstopick.len) var/obj/effect/blob/targeted = pick(blobstopick) //randomize the blob chosen, because otherwise it'd tend to the lower left @@ -130,103 +107,57 @@ B.forceMove(T) //swap the blobs return ..() -//does low burn and a lot of stamina damage, immune to tesla bolts, weak to EMP -/datum/reagent/blob/energized_fibers - name = "Energized Fibers" - id = "energized_fibers" - description = "will do low burn and high stamina damage and conduct electricity, but is weak to EMPs." - shortdesc = "will do low burn and high stamina damage." - analyzerdescdamage = "Does low burn damage and massively drains stamina." - analyzerdesceffect = "Is immune to electricity and will easily conduct it, but is weak to EMPs." - color = "#EFD65A" - complementary_color = "#5A73EF" - blobbernaut_message = "shocks" - message_living = ", and you feel a strong tingling sensation" - -/datum/reagent/blob/energized_fibers/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - M.apply_damage(0.6*reac_volume, BURN) - if(M) - M.adjustStaminaLoss(0.6*reac_volume) - -/datum/reagent/blob/energized_fibers/tesla_reaction(obj/effect/blob/B, power) - return 0 - -/datum/reagent/blob/energized_fibers/emp_reaction(obj/effect/blob/B, severity) - var/damage = rand(30, 50) - severity * rand(10, 15) - B.take_damage(damage, BURN) - -//sets you on fire, does burn damage, weak to water -/datum/reagent/blob/boiling_oil - name = "Boiling Oil" - id = "boiling_oil" - description = "will do medium burn damage and set targets on fire, but is weak to water." - shortdesc = "will do medium burn damage and set targets on fire." +//sets you on fire, does burn damage, explodes into flame when burnt, weak to water +/datum/reagent/blob/blazing_oil + name = "Blazing Oil" + id = "blazing_oil" + description = "will do medium burn damage and set targets on fire." + effectdesc = "will also release bursts of flame when burnt, but takes damage from water." analyzerdescdamage = "Does medium burn damage and sets targets on fire." - analyzerdesceffect = "Takes damage from water and other extinguishing liquids." + analyzerdesceffect = "Releases fire when burnt, but takes damage from water and other extinguishing liquids." color = "#B68D00" - complementary_color = "#0029B6" + complementary_color = "#BE5532" blobbernaut_message = "splashes" message = "The blob splashes you with burning oil" message_living = ", and you feel your skin char and melt" -/datum/reagent/blob/boiling_oil/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) +/datum/reagent/blob/blazing_oil/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() M.adjust_fire_stacks(round(reac_volume/10)) M.IgniteMob() if(M) - M.apply_damage(0.6*reac_volume, BURN) + M.apply_damage(0.8*reac_volume, BURN) if(iscarbon(M)) M.emote("scream") -/datum/reagent/blob/boiling_oil/extinguish_reaction(obj/effect/blob/B) +/datum/reagent/blob/blazing_oil/extinguish_reaction(obj/effect/blob/B) B.take_damage(rand(1, 3), BURN) -//does burn and toxin damage, explodes into flame when hit with burn damage -/datum/reagent/blob/flammable_goo - name = "Flammable Goo" - id = "flammable_goo" - description = "will do medium burn and toxin damage, make targets flammable, and ignite when burned." - shortdesc = "will do medium burn and toxin damage, and make targets flammable." - analyzerdescdamage = "Does medium burn damage, medium toxin damage, and makes targets flammable." - analyzerdesceffect = "Releases bursts of flame when attacked with burn damage, but takes additional burn damage." - color = "#BE5532" - complementary_color = "#329BBE" - blobbernaut_message = "splashes" - message = "The blob splashes you with a thin goo" - message_living = ", and you smell a faint, sweet scent" - -/datum/reagent/blob/flammable_goo/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - M.adjust_fire_stacks(round(reac_volume/8)) //apply, but don't ignite - M.apply_damage(0.4*reac_volume, TOX) - if(M) - M.apply_damage(0.4*reac_volume, BURN) - -/datum/reagent/blob/flammable_goo/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) +/datum/reagent/blob/blazing_oil/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) if(cause && damage_type == BURN) for(var/turf/open/T in range(1, B)) var/obj/effect/blob/C = locate() in T if(!(C && C.overmind && C.overmind.blob_reagent_datum.id == B.overmind.blob_reagent_datum.id) && prob(80)) PoolOrNew(/obj/effect/hotspot, T) - return damage * 1.5 return ..() -//does toxin damage, targets think they're not hurt at all +//does toxin damage, hallucination, targets think they're not hurt at all /datum/reagent/blob/regenerative_materia name = "Regenerative Materia" id = "regenerative_materia" - description = "will do low toxin damage and cause targets to believe they are fully healed." - analyzerdescdamage = "Does low toxin damage and injects a toxin that causes the target to believe they are fully healed." + description = "will do toxin damage and cause targets to believe they are fully healed." + analyzerdescdamage = "Does toxin damage and injects a toxin that causes the target to believe they are fully healed." color = "#C8A5DC" - complementary_color = "#B9DCA5" + complementary_color = "#CD7794" message_living = ", and you feel alive" /datum/reagent/blob/regenerative_materia/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() + M.adjust_drugginess(reac_volume) if(M.reagents) M.reagents.add_reagent("regenerative_materia", 0.2*reac_volume) - M.apply_damage(0.5*reac_volume, TOX) + M.reagents.add_reagent("spore", 0.2*reac_volume) + M.apply_damage(0.7*reac_volume, TOX) /datum/reagent/blob/regenerative_materia/on_mob_life(mob/living/M) M.adjustToxLoss(1*REM) @@ -241,149 +172,110 @@ N.hal_screwyhud = 0 ..() -//toxin, hallucination, and some bonus spore toxin -/datum/reagent/blob/hallucinogenic_nectar - name = "Hallucinogenic Nectar" - id = "hallucinogenic_nectar" - description = "will do low toxin damage, vivid hallucinations, and inject targets with toxins." - analyzerdescdamage = "Does low toxin damage and injects a toxin that causes vivid hallucinations and blurried vision." - color = "#CD7794" - complementary_color = "#77CDB0" - blobbernaut_message = "splashes" - message = "The blob splashes you with sticky nectar" - message_living = ", and you feel really good" - -/datum/reagent/blob/hallucinogenic_nectar/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - M.hallucination += reac_volume - M.adjust_drugginess(reac_volume) - - if(M.reagents) - M.reagents.add_reagent("spore", 0.2*reac_volume) - M.apply_damage(0.5*reac_volume, TOX) - -//kills sleeping targets and turns them into blob zombies -/datum/reagent/blob/zombifying_feelers - name = "Zombifying Feelers" - id = "zombifying_feelers" - description = "will do low toxin damage and turn sleeping targets into blob zombies." - analyzerdescdamage = "Does low toxin damage and kills unconscious humans, turning them into blob zombies." - color = "#828264" - complementary_color = "#646482" +//kills sleeping targets and turns them into blob zombies, produces fragile spores when killed or on expanding +/datum/reagent/blob/zombifying_pods + name = "Zombifying Pods" + id = "zombifying_pods" + description = "will do very low toxin damage and harvest sleeping targets for additional resources and a blob zombie." + effectdesc = "will also produce fragile spores when killed and on expanding." + shortdesc = "will do very low toxin damage and harvest sleeping targets for additional resources(for your overmind) and a blob zombie." + analyzerdescdamage = "Does very low toxin damage and kills unconscious humans, turning them into blob zombies." + analyzerdesceffect = "Produces spores when expanding and when killed." + color = "#E88D5D" + complementary_color = "#823ABB" message_living = ", and you feel tired" -/datum/reagent/blob/zombifying_feelers/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) +/datum/reagent/blob/zombifying_pods/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() + M.apply_damage(0.6*reac_volume, TOX) if(O && ishuman(M) && M.stat == UNCONSCIOUS) M.death() //sleeping in a fight? bad plan. + var/points = rand(5, 10) var/mob/living/simple_animal/hostile/blob/blobspore/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(get_turf(M)) BS.overmind = O BS.update_icons() O.blob_mobs.Add(BS) BS.Zombify(M) - if(M) - M.apply_damage(0.4*reac_volume, TOX) + O.add_points(points) + O << "Gained [points] resources from the zombification of [M]." -//toxin, stamina, and some bonus spore toxin -/datum/reagent/blob/envenomed_filaments - name = "Envenomed Filaments" - id = "envenomed_filaments" - description = "will do medium toxin and stamina damage, and inject targets with toxins." - analyzerdescdamage = "Does medium toxin damage, drains stamina, and injects a weak toxin." - color = "#9ACD32" - complementary_color = "#6532CD" - message_living = ", and you feel sick and nauseated" +/datum/reagent/blob/zombifying_pods/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) + if(!isnull(cause) && damage <= 20 && original_health - damage <= 0 && prob(30)) //if the cause isn't fire or a bomb, the damage is less than 21, we're going to die from that damage, 20% chance of a shitty spore. + B.visible_message("A spore floats free of the blob!") + var/mob/living/simple_animal/hostile/blob/blobspore/weak/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(B.loc) + BS.overmind = B.overmind + BS.update_icons() + B.overmind.blob_mobs.Add(BS) + return ..() -/datum/reagent/blob/envenomed_filaments/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - if(M.reagents) - M.reagents.add_reagent("spore", 0.2*reac_volume) - M.apply_damage(0.4*reac_volume, TOX) - if(M) - M.adjustStaminaLoss(0.4*reac_volume) +/datum/reagent/blob/zombifying_pods/expand_reaction(obj/effect/blob/B, obj/effect/blob/newB, turf/T) + if(prob(10)) + var/mob/living/simple_animal/hostile/blob/blobspore/weak/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(T) + BS.overmind = B.overmind + BS.update_icons() + newB.overmind.blob_mobs.Add(BS) -//does brute, fire, and toxin over a few seconds -/datum/reagent/blob/poisonous_strands - name = "Poisonous Strands" - id = "poisonous_strands" - description = "will inject targets with poison." - analyzerdescdamage = "Injects a highly lethal poison that will gradually liquify the target's internal organs." - color = "#7D6EB4" - complementary_color = "#A5B46E" - blobbernaut_message = "injects" - message_living = ", and you feel like your insides are melting" +//does tons of oxygen damage and a little stamina, immune to tesla bolts, weak to EMP +/datum/reagent/blob/energized_jelly + name = "Energized Jelly" + id = "energized_jelly" + description = "will cause low stamina and high oxygen damage, and cause targets to be unable to breathe." + effectdesc = "will also conduct electricity, but takes damage from EMPs." + analyzerdescdamage = "Does low stamina damage, high oxygen damage, and prevents targets from breathing." + analyzerdesceffect = "Is immune to electricity and will easily conduct it, but is weak to EMPs." + color = "#EFD65A" + complementary_color = "#00E5B1" + message_living = ", and you feel a horrible tingling sensation" + var/datum/effect_system/spark_spread/spark_system = new/datum/effect_system/spark_spread() -/datum/reagent/blob/poisonous_strands/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - if(M.reagents) - M.reagents.add_reagent("poisonous_strands", 0.12*reac_volume) - -/datum/reagent/blob/poisonous_strands/on_mob_life(mob/living/M) - M.adjustBruteLoss(1.5*REM) - M.adjustFireLoss(1.5*REM) - M.adjustToxLoss(1.5*REM) - ..() - -//does oxygen damage, randomly pushes or pulls targets -/datum/reagent/blob/cyclonic_grid - name = "Cyclonic Grid" - id = "cyclonic_grid" - description = "will cause high oxygen damage and randomly throw targets to or from it." - analyzerdescdamage = "Does high oxygen damage and randomly throws targets at or away from it." - color = "#9BCD9B" - complementary_color = "#CD9BCD" - message = "The blob blasts you with a gust of air" - message_living = ", and you can't catch your breath" - -/datum/reagent/blob/cyclonic_grid/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - reagent_vortex(M, rand(0, 1), reac_volume) - M.losebreath += round(0.05*reac_volume) - M.apply_damage(0.6*reac_volume, OXY) - -//does tons of oxygen damage and a little brute -/datum/reagent/blob/lexorin_jelly - name = "Lexorin Jelly" - id = "lexorin_jelly" - description = "will cause low brute and high oxygen damage, and cause targets to be unable to breathe." - analyzerdescdamage = "Does low brute damage, high oxygen damage, and prevents targets from breathing." - color = "#00E5B1" - complementary_color = "#E50034" - message_living = ", and your lungs feel heavy and weak" - -/datum/reagent/blob/lexorin_jelly/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) +/datum/reagent/blob/energized_jelly/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() M.losebreath += round(0.2*reac_volume) - M.apply_damage(0.2*reac_volume, BRUTE) + M.adjustStaminaLoss(0.4*reac_volume) if(M) M.apply_damage(0.6*reac_volume, OXY) +/datum/reagent/blob/energized_jelly/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) + if(!isnull(cause) && original_health - damage <= 0 && prob(10)) + spark_system.set_up(rand(2, 4), 0, B) + spark_system.start() + return ..() + +/datum/reagent/blob/energized_jelly/tesla_reaction(obj/effect/blob/B, power) + return 0 + +/datum/reagent/blob/energized_jelly/emp_reaction(obj/effect/blob/B, severity) + var/damage = rand(30, 50) - severity * rand(10, 15) + B.take_damage(damage, BURN) + //does aoe brute damage when hitting targets, is immune to explosions /datum/reagent/blob/explosive_lattice name = "Explosive Lattice" id = "explosive_lattice" - description = "will do brute damage in an area around targets and is resistant to explosions." - shortdesc = "will do brute damage in an area around targets." + description = "will do brute damage in an area around targets." + effectdesc = "will also resist explosions, but takes increased damage from fire and other energy sources." analyzerdescdamage = "Does medium brute damage and causes damage to everyone near its targets." - analyzerdesceffect = "Is highly resistant to explosions." + analyzerdesceffect = "Is highly resistant to explosions, but takes increased damage from fire and other energy sources." color = "#8B2500" complementary_color = "#00668B" blobbernaut_message = "blasts" message = "The blob blasts you" /datum/reagent/blob/explosive_lattice/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) + var/initial_volume = reac_volume reac_volume = ..() if(reac_volume >= 10) //if it's not a spore cloud, bad time incoming - var/obj/effect/overlay/temp/explosion/E = PoolOrNew(/obj/effect/overlay/temp/explosion, get_turf(M)) + var/obj/effect/overlay/temp/explosion/fast/E = PoolOrNew(/obj/effect/overlay/temp/explosion/fast, get_turf(M)) E.alpha = 150 - for(var/mob/living/L in orange(M, 1)) + for(var/mob/living/L in orange(get_turf(M), 1)) if("blob" in L.faction) //no friendly fire continue - L.apply_damage(0.6*reac_volume, BRUTE) + var/aoe_volume = ..(L, TOUCH, initial_volume, 0, L.get_permeability_protection(), O) + L.apply_damage(0.4*aoe_volume, BRUTE) if(M) M.apply_damage(0.6*reac_volume, BRUTE) else - M.apply_damage(0.8*reac_volume, BRUTE) + M.apply_damage(0.6*reac_volume, BRUTE) /datum/reagent/blob/explosive_lattice/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) if(isnull(cause)) @@ -393,59 +285,39 @@ return damage * 1.5 //take more from fire, tesla, and flashbangs return ..() -//does semi-random brute damage and reacts to brute damage -/datum/reagent/blob/reactive_gelatin - name = "Reactive Gelatin" - id = "reactive_gelatin" - description = "will do random brute damage and react to brute damage." - shortdesc = "will do random brute damage." - analyzerdescdamage = "Does random brute damage." - analyzerdesceffect = "When attacked with brute damage, will lash out, attacking everything near it." - color = "#FFA500" - complementary_color = "#005AFF" - blobbernaut_message = "pummels" - message = "The blob pummels you" - -/datum/reagent/blob/reactive_gelatin/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - var/damage = rand(5, 35)/25 - M.apply_damage(damage*reac_volume, BRUTE) - -/datum/reagent/blob/reactive_gelatin/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) - if(damage && damage_type == BRUTE && original_health - damage > 0) //is there any damage, is it brute, and will we be alive - if(isliving(cause)) - B.visible_message("The blob retaliates, lashing out!") - for(var/atom/A in range(1, B)) - A.blob_act(B) - return ..() - -//does low burn damage and stamina damage and cools targets down -/datum/reagent/blob/cryogenic_liquid - name = "Cryogenic Liquid" - id = "cryogenic_liquid" - description = "will do low burn and stamina damage, and cause targets to freeze." - analyzerdescdamage = "Does low burn damage, drains stamina, and injects targets with a freezing liquid." +//does brute, burn, and toxin damage, and cools targets down +/datum/reagent/blob/cryogenic_poison + name = "Cryogenic Poison" + id = "cryogenic_poison" + description = "will inject targets with a freezing poison that does high damage over time." + analyzerdescdamage = "Injects targets with a freezing poison that will gradually solidify the target's internal organs." color = "#8BA6E9" - complementary_color = "#E9CE8B" - blobbernaut_message = "splashes" - message = "The blob splashes you with an icy liquid" - message_living = ", and you feel cold and tired" + complementary_color = "#7D6EB4" + blobbernaut_message = "injects" + message = "The blob stabs you" + message_living = ", and you feel like your insides are solidifying" -/datum/reagent/blob/cryogenic_liquid/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) +/datum/reagent/blob/cryogenic_poison/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() if(M.reagents) M.reagents.add_reagent("frostoil", 0.3*reac_volume) M.reagents.add_reagent("ice", 0.3*reac_volume) - M.apply_damage(0.4*reac_volume, BURN) - if(M) - M.adjustStaminaLoss(0.3*reac_volume) + M.reagents.add_reagent("cryogenic_poison", 0.3*reac_volume) + M.apply_damage(0.2*reac_volume, BRUTE) + +/datum/reagent/blob/cryogenic_poison/on_mob_life(mob/living/M) + M.adjustBruteLoss(0.3*REM, 0) + M.adjustFireLoss(0.3*REM, 0) + M.adjustToxLoss(0.3*REM, 0) + . = 1 + ..() //does burn damage and EMPs, slightly fragile /datum/reagent/blob/electromagnetic_web name = "Electromagnetic Web" id = "electromagnetic_web" - description = "will do low burn damage and EMP targets, but is very fragile." - shortdesc = "will do low burn damage and EMP targets." + description = "will do high burn damage and EMP targets." + effectdesc = "will also take massively increased damage and release an EMP when killed." analyzerdescdamage = "Does low burn damage and EMPs targets." analyzerdesceffect = "Is fragile to all types of damage, but takes massive damage from brute. In addition, releases a small EMP when killed." color = "#83ECEC" @@ -459,7 +331,7 @@ if(prob(reac_volume*2)) M.emp_act(2) if(M) - M.apply_damage(0.6*reac_volume, BURN) + M.apply_damage(reac_volume, BURN) /datum/reagent/blob/electromagnetic_web/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) if(damage_type == BRUTE) //take full brute @@ -480,8 +352,8 @@ /datum/reagent/blob/synchronous_mesh name = "Synchronous Mesh" id = "synchronous_mesh" - description = "will do brute damage for each nearby blob and spread damage between nearby blobs." - shortdesc = "will do brute damage for each nearby blob." + description = "will do massively increased brute damage for each blob near the target." + effectdesc = "will also spread damage between each blob near the attacked blob." analyzerdescdamage = "Does brute damage, increasing for each blob near the target." analyzerdesceffect = "When attacked, spreads damage between all blobs near the attacked blob." color = "#65ADA2" @@ -491,9 +363,9 @@ /datum/reagent/blob/synchronous_mesh/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() - M.apply_damage(0.1*reac_volume, BRUTE) + M.apply_damage(0.2*reac_volume, BRUTE) if(M && reac_volume) - for(var/obj/effect/blob/B in range(1, M)) //if the target is completely surrounded, this is 2.4*reac_volume bonus damage, total of 2.5*reac_volume + for(var/obj/effect/blob/B in range(1, M)) //if the target is completely surrounded, this is 2.4*reac_volume bonus damage, total of 2.6*reac_volume if(M) B.blob_attack_animation(M) //show them they're getting a bad time M.apply_damage(0.3*reac_volume, BRUTE) @@ -512,47 +384,37 @@ return damage * 1.25 //does brute damage through armor and bio resistance -/datum/reagent/blob/penetrating_spines - name = "Penetrating Spines" - id = "penetrating_spines" +/datum/reagent/blob/reactive_spines + name = "Reactive Spines" + id = "reactive_spines" description = "will do medium brute damage through armor and bio resistance." + effectdesc = "will also react when attacked with brute damage, attacking all near the attacked blob." analyzerdescdamage = "Does medium brute damage, ignoring armor and bio resistance." - color = "#6E4664" - complementary_color = "#466E50" + analyzerdesceffect = "When attacked with brute damage, will lash out, attacking everything near it." + color = "#9ACD32" + complementary_color = "#FFA500" blobbernaut_message = "stabs" message = "The blob stabs you" -/datum/reagent/blob/penetrating_spines/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) +/datum/reagent/blob/reactive_spines/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) if(M.stat == DEAD || istype(M, /mob/living/simple_animal/hostile/blob)) return 0 //the dead, and blob mobs, don't cause reactions - M.adjustBruteLoss(0.7*reac_volume) + M.adjustBruteLoss(0.8*reac_volume) -/datum/reagent/blob/adaptive_nexuses - name = "Adaptive Nexuses" - id = "adaptive_nexuses" - description = "will do medium brute damage and kill unconscious targets, giving you bonus resources." - shortdesc = "will do medium brute damage and kill unconscious targets, giving your overmind bonus resources." - analyzerdescdamage = "Does medium brute damage and kills unconscious humans, reaping resources from them." - color = "#4A64C0" - complementary_color = "#823ABB" - -/datum/reagent/blob/adaptive_nexuses/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - if(O && ishuman(M) && M.stat == UNCONSCIOUS) - PoolOrNew(/obj/effect/overlay/temp/revenant, get_turf(M)) - var/points = rand(5, 10) - O.add_points(points) - O << "Gained [points] resources from the death of [M]." - M.death() - if(M) - M.apply_damage(0.5*reac_volume, BRUTE) +/datum/reagent/blob/reactive_spines/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) + if(damage && damage_type == BRUTE && original_health - damage > 0) //is there any damage, is it brute, and will we be alive + if(isliving(cause)) + B.visible_message("The blob retaliates, lashing out!") + for(var/atom/A in range(1, B)) + A.blob_act(B) + return ..() //does low brute damage, oxygen damage, and stamina damage and wets tiles when damaged /datum/reagent/blob/pressurized_slime name = "Pressurized Slime" id = "pressurized_slime" - description = "will do low brute, oxygen, and stamina damage, and wet tiles when damaged or killed." - shortdesc = "will do low brute, oxygen, and stamina damage, and wet tiles under targets." + description = "will do low brute, oxygen, and stamina damage, and wet tiles under targets." + effectdesc = "will also wet tiles near blobs that are attacked or killed." analyzerdescdamage = "Does low brute damage, low oxygen damage, drains stamina, and wets tiles under targets, extinguishing them." analyzerdesceffect = "When attacked or killed, wets nearby tiles, extinguishing anything on them." color = "#AAAABB" @@ -568,20 +430,21 @@ T.MakeSlippery(min_wet_time = 10, wet_time_to_add = 5) M.adjust_fire_stacks(-(reac_volume / 10)) M.ExtinguishMob() - M.apply_damage(0.1*reac_volume, BRUTE) + M.apply_damage(0.4*reac_volume, BRUTE) if(M) - M.apply_damage(0.3*reac_volume, OXY) + M.apply_damage(0.4*reac_volume, OXY) if(M) - M.adjustStaminaLoss(0.3*reac_volume) + M.adjustStaminaLoss(0.2*reac_volume) /datum/reagent/blob/pressurized_slime/damage_reaction(obj/effect/blob/B, original_health, damage, damage_type, cause) - extinguisharea(B, damage) + if(cause || damage_type != BURN) + extinguisharea(B, damage) return ..() /datum/reagent/blob/pressurized_slime/death_reaction(obj/effect/blob/B, cause) if(!isnull(cause)) - B.visible_message("The blob ruptures, spraying the area with liquid!") - extinguisharea(B, 50) + B.visible_message("The blob ruptures, spraying the area with liquid!") + extinguisharea(B, 50) /datum/reagent/blob/pressurized_slime/proc/extinguisharea(obj/effect/blob/B, probchance) for(var/turf/open/T in range(1, B)) @@ -592,75 +455,3 @@ for(var/mob/living/L in T) L.adjust_fire_stacks(-2.5) L.ExtinguishMob() - -//does brute damage and throws or pulls nearby objects at the target -/datum/reagent/blob/dark_matter - name = "Dark Matter" - id = "dark_matter" - description = "will do medium brute damage and pull nearby objects and enemies at the target." - analyzerdescdamage = "Does medium brute damage and pulls nearby objects and creatures at the target." - color = "#61407E" - complementary_color = "#5D7E40" - message = "You feel a thrum as the blob strikes you, and everything flies at you" - -/datum/reagent/blob/dark_matter/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - reagent_vortex(M, 0, reac_volume) - M.apply_damage(0.3*reac_volume, BRUTE) - -//does brute damage and throws or pushes nearby objects away from the target -/datum/reagent/blob/b_sorium - name = "Sorium" - id = "b_sorium" - description = "will do medium brute damage and throw nearby objects and enemies away from the target." - analyzerdescdamage = "Does medium brute damage and pushes nearby objects and creatures away from the target." - color = "#808000" - complementary_color = "#000080" - message = "The blob slams into you and sends you flying" - -/datum/reagent/blob/b_sorium/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) - reac_volume = ..() - reagent_vortex(M, 1, reac_volume) - M.apply_damage(0.3*reac_volume, BRUTE) - -/datum/reagent/blob/proc/reagent_vortex(mob/living/M, setting_type, reac_volume) - if(M && reac_volume) - var/turf/pull = get_turf(M) - var/range_power = Clamp(round(reac_volume/5, 1), 1, 5) - for(var/atom/movable/X in range(range_power,pull)) - if(istype(X, /obj/effect)) - continue - if(isliving(X)) - var/mob/living/L = X - if("blob" in L.faction) //no friendly throwpulling - continue - if(!X.anchored) - var/distance = get_dist(X, pull) - var/moving_power = max(range_power - distance, 1) - if(moving_power > 2) //if the vortex is powerful and we're close, we get thrown - if(setting_type) - var/atom/throw_target = get_edge_target_turf(X, get_dir(X, get_step_away(X, pull))) - var/throw_range = 5 - distance - X.throw_at_fast(throw_target, throw_range, 1) - else - X.throw_at_fast(pull, distance, 1) - else - spawn(0) - if(setting_type) - for(var/i in 0 to moving_power-1) - sleep(2) - if(!step_away(X, pull)) - break - else - for(var/i in 0 to moving_power-1) - sleep(2) - if(!step_towards(X, pull)) - break - - -/datum/reagent/blob/proc/send_message(mob/living/M) - var/totalmessage = message - if(message_living && !issilicon(M)) - totalmessage += message_living - totalmessage += "!" - M << "[totalmessage]" diff --git a/code/modules/research/circuitprinter.dm b/code/modules/research/circuitprinter.dm index 41b58622d1b..010a4105883 100644 --- a/code/modules/research/circuitprinter.dm +++ b/code/modules/research/circuitprinter.dm @@ -23,12 +23,13 @@ using metal and glass, it uses glass and reagents (usually sulfuric acis). "Hydroponics Machinery", "Subspace Telecomms", "Research Machinery", - "Misc. Machinery" + "Misc. Machinery", + "Computer Parts" ) /obj/machinery/r_n_d/circuit_imprinter/New() ..() - materials = new(src, list(MAT_GLASS, MAT_GOLD, MAT_DIAMOND)) + materials = new(src, list(MAT_GLASS, MAT_GOLD, MAT_DIAMOND, MAT_METAL)) create_reagents(0) var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/circuit_imprinter(null) B.apply_default_parts(src) diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index 8d2f17106e7..3f20d597265 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -159,7 +159,7 @@ other types of metals and chemistry for reagents). build_path = /obj/item/weapon/pickaxe/drill/jackhammer category = list("Mining Designs") -/datum/design/modkit +/*/datum/design/modkit name = "Modification Kit" desc = "A device which allows kinetic accelerators to be wielded with one hand, and by any organism." id = "modkit" @@ -188,7 +188,7 @@ other types of metals and chemistry for reagents). materials = list(MAT_METAL = 8000, MAT_GLASS = 1500, MAT_SILVER = 2000, MAT_GOLD = 2000, MAT_DIAMOND = 2000) build_path = /obj/item/weapon/gun/energy/kinetic_accelerator/hyper category = list("Mining Designs") - +*/ /datum/design/superresonator name = "Upgraded Resonator" desc = "An upgraded version of the resonator that allows more fields to be active at once." diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm index 8e5bfa55e6d..565ec192dfb 100644 --- a/code/modules/research/designs/autolathe_designs.dm +++ b/code/modules/research/designs/autolathe_designs.dm @@ -728,3 +728,18 @@ build_path = /obj/item/conveyor_switch_construct category = list("initial", "Construction") +/datum/design/laptop + name = "Laptop Frame" + id = "laptop" + build_type = AUTOLATHE + materials = list(MAT_METAL = 1200, MAT_GLASS = 200) + build_path = /obj/machinery/modular_computer/laptop/buildable + category = list("initial","Misc") + +/datum/design/tablet + name = "Tablet Frame" + id = "tablet" + build_type = AUTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 1000) + build_path = /obj/item/modular_computer/tablet + category = list("initial","Misc") \ No newline at end of file diff --git a/code/modules/research/designs/computer_part_designs.dm b/code/modules/research/designs/computer_part_designs.dm new file mode 100644 index 00000000000..ae91050960e --- /dev/null +++ b/code/modules/research/designs/computer_part_designs.dm @@ -0,0 +1,245 @@ +//////////////////////////////////////// +///////////Computer Parts/////////////// +//////////////////////////////////////// + +/datum/design/disk/normal + name = "basic hard drive" + id = "hdd_basic" + req_tech = list("programming" = 1, "engineering" = 1) + build_type = PROTOLATHE + materials = list(MAT_METAL = 400, MAT_GLASS = 100) + build_path = /obj/item/weapon/computer_hardware/hard_drive/ + category = list("Computer Parts") + +/datum/design/disk/advanced + name = "advanced hard drive" + id = "hdd_advanced" + req_tech = list("programming" = 2, "engineering" = 2) + build_type = PROTOLATHE + materials = list(MAT_METAL = 800, MAT_GLASS = 200) + build_path = /obj/item/weapon/computer_hardware/hard_drive/advanced + category = list("Computer Parts") + +/datum/design/disk/super + name = "super hard drive" + id = "hdd_super" + req_tech = list("programming" = 3, "engineering" = 3) + build_type = PROTOLATHE + materials = list(MAT_METAL = 1600, MAT_GLASS = 400) + build_path = /obj/item/weapon/computer_hardware/hard_drive/super + category = list("Computer Parts") + +/datum/design/disk/cluster + name = "cluster hard drive" + id = "hdd_cluster" + req_tech = list("programming" = 4, "engineering" = 4) + build_type = PROTOLATHE + materials = list(MAT_METAL = 3200, MAT_GLASS = 800) + build_path = /obj/item/weapon/computer_hardware/hard_drive/cluster + category = list("Computer Parts") + +/datum/design/disk/small + name = "small hard drive" + id = "hdd_small" + req_tech = list("programming" = 2, "engineering" = 2) + build_type = PROTOLATHE + materials = list(MAT_METAL = 800, MAT_GLASS = 200) + build_path = /obj/item/weapon/computer_hardware/hard_drive/small + category = list("Computer Parts") + +/datum/design/disk/micro + name = "micro hard drive" + id = "hdd_micro" + req_tech = list("programming" = 1, "engineering" = 1) + build_type = PROTOLATHE + materials = list(MAT_METAL = 400, MAT_GLASS = 100) + build_path = /obj/item/weapon/computer_hardware/hard_drive/micro + category = list("Computer Parts") + +// Network cards +/datum/design/netcard/basic + name = "basic network card" + id = "netcard_basic" + req_tech = list("programming" = 2, "engineering" = 1) + build_type = IMPRINTER + materials = list(MAT_METAL = 250, MAT_GLASS = 100) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/network_card + category = list("Computer Parts") + +/datum/design/netcard/advanced + name = "advanced network card" + id = "netcard_advanced" + req_tech = list("programming" = 4, "engineering" = 2) + build_type = IMPRINTER + materials = list(MAT_METAL = 500, MAT_GLASS = 200) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/network_card/advanced + category = list("Computer Parts") + +/datum/design/netcard/wired + name = "wired network card" + id = "netcard_wired" + req_tech = list("programming" = 5, "engineering" = 3) + build_type = IMPRINTER + materials = list(MAT_METAL = 2500, MAT_GLASS = 400) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/network_card/wired + category = list("Computer Parts") + +// Data crystals (USB flash drives) +/datum/design/portabledrive/basic + name = "basic data crystal" + id = "portadrive_basic" + req_tech = list("programming" = 1) + build_type = IMPRINTER + materials = list(MAT_GLASS = 800) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/hard_drive/portable + category = list("Computer Parts") + +/datum/design/portabledrive/advanced + name = "advanced data crystal" + id = "portadrive_advanced" + req_tech = list("programming" = 2) + build_type = IMPRINTER + materials = list(MAT_GLASS = 1600) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/hard_drive/portable/advanced + category = list("Computer Parts") + +/datum/design/portabledrive/super + name = "super data crystal" + id = "portadrive_super" + req_tech = list("programming" = 4) + build_type = IMPRINTER + materials = list(MAT_GLASS = 3200) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/hard_drive/portable/super + category = list("Computer Parts") + +// Card slot +/datum/design/cardslot + name = "RFID card slot" + id = "cardslot" + req_tech = list("programming" = 2) + build_type = PROTOLATHE + materials = list(MAT_METAL = 600) + build_path = /obj/item/weapon/computer_hardware/card_slot + category = list("Computer Parts") + +// Nano printer +/datum/design/nanoprinter + name = "nano printer" + id = "nanoprinter" + req_tech = list("programming" = 2, "engineering" = 2) + build_type = PROTOLATHE + materials = list(MAT_METAL = 600) + build_path = /obj/item/weapon/computer_hardware/nano_printer + category = list("Computer Parts") + +// Tesla Link +/datum/design/teslalink + name = "tesla link" + id = "teslalink" + req_tech = list("programming" = 2, "powerstorage" = 3, "engineering" = 2) + build_type = PROTOLATHE + materials = list(MAT_METAL = 2000) + build_path = /obj/item/weapon/computer_hardware/tesla_link + category = list("Computer Parts") + +// Batteries +/datum/design/battery/normal + name = "standard battery module" + id = "bat_normal" + req_tech = list("powerstorage" = 1, "engineering" = 1) + build_type = PROTOLATHE + materials = list(MAT_METAL = 400) + build_path = /obj/item/weapon/computer_hardware/battery_module + category = list("Computer Parts") + +/datum/design/battery/advanced + name = "advanced battery module" + id = "bat_advanced" + req_tech = list("powerstorage" = 2, "engineering" = 2) + build_type = PROTOLATHE + materials = list(MAT_METAL = 800) + build_path = /obj/item/weapon/computer_hardware/battery_module/advanced + category = list("Computer Parts") + +/datum/design/battery/super + name = "super battery module" + id = "bat_super" + req_tech = list("powerstorage" = 3, "engineering" = 3) + build_type = PROTOLATHE + materials = list(MAT_METAL = 1600) + build_path = /obj/item/weapon/computer_hardware/battery_module/super + category = list("Computer Parts") + +/datum/design/battery/ultra + name = "ultra battery module" + id = "bat_ultra" + req_tech = list("powerstorage" = 5, "engineering" = 4) + build_type = PROTOLATHE + materials = list(MAT_METAL = 3200) + build_path = /obj/item/weapon/computer_hardware/battery_module/ultra + category = list("Computer Parts") + +/datum/design/battery/nano + name = "nano battery module" + id = "bat_nano" + req_tech = list("powerstorage" = 1, "engineering" = 1) + build_type = PROTOLATHE + materials = list(MAT_METAL = 200) + build_path = /obj/item/weapon/computer_hardware/battery_module/nano + category = list("Computer Parts") + +/datum/design/battery/micro + name = "micro battery module" + id = "bat_micro" + req_tech = list("powerstorage" = 2, "engineering" = 2) + build_type = PROTOLATHE + materials = list(MAT_METAL = 400) + build_path = /obj/item/weapon/computer_hardware/battery_module/micro + category = list("Computer Parts") + +// Processor unit +/datum/design/cpu + name = "computer processor unit" + id = "cpu_normal" + req_tech = list("programming" = 3, "engineering" = 2) + build_type = IMPRINTER + materials = list(MAT_GLASS = 1600) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/processor_unit + category = list("Computer Parts") + +/datum/design/cpu/small + name = "computer microprocessor unit" + id = "cpu_small" + req_tech = list("programming" = 2, "engineering" = 2) + build_type = IMPRINTER + materials = list(MAT_GLASS = 800) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/processor_unit/small + category = list("Computer Parts") + +/datum/design/cpu/photonic + name = "computer photonic processor unit" + id = "pcpu_normal" + req_tech = list("programming" = 5, "engineering" = 4) + build_type = IMPRINTER + materials = list(MAT_GLASS= 6400, MAT_GOLD = 2000) + reagents = list("sacid" = 40) + build_path = /obj/item/weapon/computer_hardware/processor_unit/photonic + category = list("Computer Parts") + +/datum/design/cpu/photonic/small + name = "computer photonic microprocessor unit" + id = "pcpu_small" + req_tech = list("programming" = 4, "engineering" = 3) + build_type = IMPRINTER + materials = list(MAT_GLASS = 3200, MAT_GOLD = 1000) + reagents = list("sacid" = 20) + build_path = /obj/item/weapon/computer_hardware/processor_unit/photonic/small + category = list("Computer Parts") \ No newline at end of file diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 3ed57ca5fea..3b510ae40ba 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -386,3 +386,11 @@ req_tech = list("programming" = 4, "biotech" = 3) build_path = /obj/item/weapon/circuitboard/machine/plantgenes category = list ("Misc. Machinery") + +/datum/design/board/ntnet_relay + name = "Machine Design (NTNet Relay Board)" + desc = "The circuit board for a wireless network relay." + id = "ntnet_relay" + req_tech = list("programming" = 2, "engineering" = 2, "bluespace" = 2) + build_path = /obj/item/weapon/circuitboard/machine/ntnet_relay + category = list("Subspace Telecomms") \ No newline at end of file diff --git a/code/modules/research/protolathe.dm b/code/modules/research/protolathe.dm index 6a02b6b0f3b..1b526830d0c 100644 --- a/code/modules/research/protolathe.dm +++ b/code/modules/research/protolathe.dm @@ -26,7 +26,8 @@ Note: Must be placed west/left of and R&D console to function. "Electronics", "Weapons", "Ammo", - "Firing Pins" + "Firing Pins", + "Computer Parts" ) diff --git a/code/modules/stock_market/events.dm b/code/modules/stock_market/events.dm index 1e34fddf03d..72f9858ea3d 100644 --- a/code/modules/stock_market/events.dm +++ b/code/modules/stock_market/events.dm @@ -165,7 +165,7 @@ "public indecency", "public intoxication", "trespassing", "loitering", "littering", "vigilantism", "squatting", "panhandling", "arson", "spacepodjacking", "shuttlejacking", "carjacking", "singularityjacking", "dereliction of duty", "spacecraft piracy", "music piracy", "tabletop game piracy", "software piracy", "escaping from space prison", "seniornapping", "clownnapping", "corginapping", "catnapping", "sleeping on the job", "terrorism", "counterterrorism", "drug distribution", "insubordination", "jaywalking", "owning a computer", "owning a cellphone", "owning a PDA", "owning a pAI", "adultery", - "committing an unnatural act with another person", "corrupting public morals", "skateboarding without a license", "shitcurity", "bestiality", "erotic roleplay") + "committing an unnatural act with another person", "corrupting public morals", "skateboarding without a license", "shitcurity", "bestiality", "erotic roleplay", "accidentally strangling a prostitute") while (prob(60) && O.len > 2) var/offense = pick(O) O -= offense @@ -225,4 +225,4 @@ else article += "[tname] has a prior history of similar misdeeds and we're confident the charges will stand. For investors, now would be an ideal time to %sell%" A.article = A.detokenize(article, company.industry.tokens) - return A \ No newline at end of file + return A diff --git a/config/tips.txt b/config/tips.txt index 4985387309a..917443afefa 100644 --- a/config/tips.txt +++ b/config/tips.txt @@ -130,15 +130,15 @@ As an Alien, you take double damage from all burn attacks, such as lasers, weldi As an Alien, resin floors not only regenerate your plasma supply, but also passively heal you. Fight on resin floors to gain a home turf advantage! As an Alien, the facehugger is by far your most powerful weapon because of its ability to instantly win a fight. Remember however that certain helmets, such as biohoods or space helmets will completely block facehugger attacks. As an Alien, you are unable to pick up or use any human items or machinery. Instead, you should focus on sabotaging APCs, computers, cameras and either stowing, spacing, or melting any weapons you find. -When fighting the Blob, you can hit it with an analyzer to check its chemical effects and the type of blob analyzed. Examining it with a research scanner active will also reveal this information. +When fighting the Blob, you can hit it with an analyzer to check its chemical effects and the type of blob analyzed. Examining it with a research scanner or medical hud active will also reveal this information. As the Blob, you can place your core at any clear, unobserved, spot you can move to after 3 minutes have passed. If you wait too long, you will be automatically placed at a random location. Use this wait time to coordinate with any other overminds. As the Blob, keep your core some distance from space, as it is both expensive to expand onto space, easy to be attacked from, and does not count towards your win condition. Emitter platforms built in space are especially dangerous. As the Blob, you can randomly repick your reagent type if the crew has adapted and protected themselves against your current one. As the Blob, you fight a war of attrition: Take out medbay and fight in chokepoints to prevent continued assaults and coordinated burst damage attacks! -As the Blob, remember that you can only expand onto tiles from cardinal directions, while the crew can attack you diagonally. Fight in narrow corridors or expand and widen across the width of the hallway to force confrontations where you can hit them! +As the Blob, you can attack anything next to a blob, but can only expand in cardinal directions, and diagonal attacks are slightly weaker. As the Blob, don't neglect the creation of factories. These create spores that carry your reagent and can chase crewmembers far further than you. Spores can also be rallied to swarm the crew and cause panic, and can even take over corpses to create much more dangerous blob zombies! As the Blob, you can produce a Blobbernaut from a factory for 40 resources. Blobbernauts are smart, gradually regenerate near nodes, and apply your chemical when attacking, but die when too far from the blob or if their factory is killed. However, the factory used becomes fragile and stops spawning spores. -As the Blob, you can expand by clicking, create strong blobs with ctrl-click, rally spores with middle-click, and remove blobs with alt-click. +As the Blob, you can expand by clicking, create strong blobs with ctrl-click, rally spores with middle-click, and remove blobs with alt-click. You do not need to have your camera over the tile to do this. As the Blob, removing strong blobs, resource nodes, factories, and nodes will give you 4, 15, 25, and 25 resources back, respectively. As the Blob, talking will send a message to all other overminds and all Blobbernauts, allowing you to direct attacks and coordinate. As a Blobbernaut, you can communicate with overminds and other Blobbernauts via :b. diff --git a/html/changelog.html b/html/changelog.html index dd9d02f6e96..84fd2470d95 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -55,6 +55,75 @@ -->
    +

    17 August 2016

    +

    Basilman updated:

    +
      +
    • Added an admin-spawnable only "Cosmohonk" hardsuit that has the same protection values of an engineering hardsuit. Comes with built-in lights and requires clown roundstart role to be equipped.
    • +
    +

    Ergovisavi updated:

    +
      +
    • Adds the "Proto-Kinetic Crusher", a new melee mining weapon. Available at a mining vendor near you!
    • +
    +

    Gun Hog updated:

    +
      +
    • Nanotrasen has improved the coolant system in the stations automated robots. They will no longer violently explode in hot rooms.
    • +
    +

    Joan updated:

    +
      +
    • Once the blob alert message is sent in the blob game mode, all mobs get to see how many tiles the blob has until it wins, via the Status tab.
    • +
    • Removed/merged a bunch of blob chems, you probably don't care about the specifics.
    • +
    • The remaining blob chems should, overall, be more powerful.
    • +
    • Shield blobs soak brute damage less well.
    • +
    • Flashbangs do higher damage to blobs up close, but their damage falls off faster.
    • +
    • Shield blobs now cost 15 resources to make instead of 10. Node blobs now cost 50 resources to make instead of 60.
    • +
    • Expanding/attacking now costs 4 resources instead of 5, and blobs can now ATTACK DIAGONALLY. Diagonal attacks are weaker than normal attacks, especially against cyborgs(which may be entirely immune, depending), and they remain unable to expand diagonally.
    • +
    • Shield blobs no longer block atmos while under half health. Shield blobs are still immune to fire, even if they can't block atmos.
    • +
    • Blobs should block explosions less well.
    • +
    • Blob cores and nodes are no longer immune to fire and no longer block atmos.
    • +
    • Blobs can only auto-expand one tile at a time per expanding thing, and should be easier to beat back in general.
    • +
    • Blobbernauts now attack faster.
    • +
    • Blob Overminds attack mobs slower but can attack non-mobs much faster.
    • +
    • Blob Overminds start with some amount of resources; in the gamemode, it's 80 divided by the number of overminds, in the event, it's 20 plus the number of active players, and otherwise, it's 60.
    • +
    • You can no longer move blob cores into space, onto the mining shuttle, white ship, gulag shuttle, or solars.
    • +
    • Blob rounds might be less laggy, if they were laggy?
    • +
    • Blobs don't heal as fast, excluding the core.
    • +
    • Blobs are marginally less destructive to their environment.
    • +
    +

    MrStonedOne updated:

    +
      +
    • The server will no longer wait until the next tick to process movement key presses. Movements should now be ~25-50ms more responsive.
    • +
    +

    Shadowlight213 updated:

    +
      +
    • Adds modular computers
    • +
    + +

    16 August 2016

    +

    MrStonedOne updated:

    +
      +
    • Automated the granting of profiler access to admins
    • +
    +

    oranges updated:

    +
      +
    • Nanotrasen is an all right kind of company
    • +
    + +

    15 August 2016

    +

    Joan updated:

    +
      +
    • The Ark of the Clockwork Justicar can now be constructed even if Ratvar's rise is not the objective.
    • +
    • Accordingly, its behavior is different; instead of summoning Ratvar, it will convert a massive amount of the station, and should effectively instantly complete the clockwork cult's objectives.
    • +
    +

    MrStonedOne updated:

    +
      +
    • Tweaked the lag stop numbers to hopefully reduce lag during 80+ population
    • +
    • Fixed the blackbox not blackboxing.
    • +
    +

    OSCAR MIKE updated:

    +
      +
    • CONTACT ON THE LEFT SIDE!
    • +
    +

    14 August 2016

    Cheridan updated:

      @@ -1146,40 +1215,6 @@
      • Polymorphed mobs now have the same name, and where possible, the same equipment as their previous form.
      - -

      14 June 2016

      -

      Joan updated:

      -
        -
      • The Gateway to the Celestial Derelict now takes exactly 5 minutes to successfully summon Ratvar and has 500 health, from 1000.
      • -
      • You can now actually hit people adjacent to the Gateway; only the center of the Gateway can be attacked. The Gateway is also now dense, allowing you to more easily shoot at it.
      • -
      • All Scripture in the Clockwork Slab's recital has a simple description.
      • -
      -

      Razharas updated:

      -
        -
      • Wiki now has the method of cultivating kudzu vines that was in game for over a year. https://tgstation13.org/wiki/Guide_to_hydroponics#Kudzu
      • -
      -

      kevinz000 updated:

      -
        -
      • Gravity Guns should no longer destroy local reality when set to attract and shot in an east cardinal direction. Added safety checks has slightly increased the cost of the gun's fabrication. Nanotrasen apologizes for the inconvenience.
      • -
      • Gravity guns now have an inhand sprite.
      • -
      - -

      13 June 2016

      -

      Joan updated:

      -
        -
      • Sigils of Transgression will now only stun for about 5 seconds, from around 10 seconds.
      • -
      • Sigils of Submission take about 5 seconds to convert a target, from around 3 seconds, and will glow faintly.
      • -
      • Sigils of any type can be attacked with an open hand to destroy them, instead of requiring harm intent. Servants still require harm intent to do so.
      • -
      -

      Lati updated:

      -
        -
      • Nanotrasen has added one of their rare machine prototypes to cargo's selection. It might be valuable to research.
      • -
      -

      Xhuis updated:

      -
        -
      • You can now create living cake/cat hybrids through a slightly expensive crafting recipe. These "caks" are remarkably resilient, quickly regenerating any brute damage dealt, and can be attacked on harm intent to take a bite to be fed by a small amount. They serve as mobile sources of food and make great pets.
      • -
      • If a brain occupied by a player is used in the crafting recipe for caks, that player then takes control of the cak!
      • -
    GoonStation 13 Development Team diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml index 39da5c1bb70..fec90c2c882 100644 --- a/html/changelogs/.all_changelog.yml +++ b/html/changelogs/.all_changelog.yml @@ -6615,3 +6615,68 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. gun, and the DRAGnet capture and recovery system. optional name here: - bugfix: The syndicate uplink ui has been refactored to majorly reduce lag +2016-08-15: + Joan: + - rscadd: The Ark of the Clockwork Justicar can now be constructed even if Ratvar's + rise is not the objective. + - experiment: Accordingly, its behavior is different; instead of summoning Ratvar, + it will convert a massive amount of the station, and should effectively instantly + complete the clockwork cult's objectives. + MrStonedOne: + - tweak: Tweaked the lag stop numbers to hopefully reduce lag during 80+ population + - tweak: Fixed the blackbox not blackboxing. + OSCAR MIKE: + - rscadd: CONTACT ON THE LEFT SIDE! +2016-08-16: + MrStonedOne: + - rscadd: Automated the granting of profiler access to admins + oranges: + - rscadd: Nanotrasen is an all right kind of company +2016-08-17: + Basilman: + - rscadd: Added an admin-spawnable only "Cosmohonk" hardsuit that has the same protection + values of an engineering hardsuit. Comes with built-in lights and requires clown + roundstart role to be equipped. + Ergovisavi: + - rscadd: Adds the "Proto-Kinetic Crusher", a new melee mining weapon. Available + at a mining vendor near you! + Gun Hog: + - tweak: Nanotrasen has improved the coolant system in the stations automated robots. + They will no longer violently explode in hot rooms. + Joan: + - rscadd: Once the blob alert message is sent in the blob game mode, all mobs get + to see how many tiles the blob has until it wins, via the Status tab. + - rscdel: Removed/merged a bunch of blob chems, you probably don't care about the + specifics. + - tweak: The remaining blob chems should, overall, be more powerful. + - tweak: Shield blobs soak brute damage less well. + - tweak: Flashbangs do higher damage to blobs up close, but their damage falls off + faster. + - experiment: Shield blobs now cost 15 resources to make instead of 10. Node blobs + now cost 50 resources to make instead of 60. + - experiment: Expanding/attacking now costs 4 resources instead of 5, and blobs + can now ATTACK DIAGONALLY. Diagonal attacks are weaker than normal attacks, + especially against cyborgs(which may be entirely immune, depending), and they + remain unable to expand diagonally. + - rscadd: Shield blobs no longer block atmos while under half health. Shield blobs + are still immune to fire, even if they can't block atmos. + - tweak: Blobs should block explosions less well. + - rscadd: Blob cores and nodes are no longer immune to fire and no longer block + atmos. + - rscadd: Blobs can only auto-expand one tile at a time per expanding thing, and + should be easier to beat back in general. + - tweak: Blobbernauts now attack faster. + - tweak: Blob Overminds attack mobs slower but can attack non-mobs much faster. + - rscadd: Blob Overminds start with some amount of resources; in the gamemode, it's + 80 divided by the number of overminds, in the event, it's 20 plus the number + of active players, and otherwise, it's 60. + - bugfix: You can no longer move blob cores into space, onto the mining shuttle, + white ship, gulag shuttle, or solars. + - bugfix: Blob rounds might be less laggy, if they were laggy? + - tweak: Blobs don't heal as fast, excluding the core. + - experiment: Blobs are marginally less destructive to their environment. + MrStonedOne: + - rscdel: The server will no longer wait until the next tick to process movement + key presses. Movements should now be ~25-50ms more responsive. + Shadowlight213: + - rscadd: Adds modular computers diff --git a/html/changelogs/AutoChangeLog-pr-19866.yml b/html/changelogs/AutoChangeLog-pr-19866.yml deleted file mode 100644 index 0c7a151a562..00000000000 --- a/html/changelogs/AutoChangeLog-pr-19866.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "OSCAR MIKE" -delete-after: True -changes: - - rscadd: "CONTACT ON THE LEFT SIDE!" diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 52e8dad7371..a0c21d4f6b5 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi index 8c7a8365e7e..0c494d78020 100644 Binary files a/icons/mob/actions.dmi and b/icons/mob/actions.dmi differ diff --git a/icons/mob/back.dmi b/icons/mob/back.dmi index f4bee86ddc6..c9e23c00f74 100644 Binary files a/icons/mob/back.dmi and b/icons/mob/back.dmi differ diff --git a/icons/mob/blob.dmi b/icons/mob/blob.dmi index f91ed38dff3..115629dd479 100644 Binary files a/icons/mob/blob.dmi and b/icons/mob/blob.dmi differ diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi index ef46ca2181d..2b50ed5e98b 100644 Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ diff --git a/icons/mob/inhands/clothing_lefthand.dmi b/icons/mob/inhands/clothing_lefthand.dmi index aa11f4e5279..3c8c1767724 100644 Binary files a/icons/mob/inhands/clothing_lefthand.dmi and b/icons/mob/inhands/clothing_lefthand.dmi differ diff --git a/icons/mob/inhands/clothing_righthand.dmi b/icons/mob/inhands/clothing_righthand.dmi index edd47e107f3..1d9bd454129 100644 Binary files a/icons/mob/inhands/clothing_righthand.dmi and b/icons/mob/inhands/clothing_righthand.dmi differ diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi index b09c66643c3..ad7dffd2fe0 100644 Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi index 6c762beacff..142b7a058ca 100644 Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ diff --git a/icons/mob/suit.dmi b/icons/mob/suit.dmi index f26aa3f1b01..faef55f663d 100644 Binary files a/icons/mob/suit.dmi and b/icons/mob/suit.dmi differ diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi index 4efdf2e78f6..e79c447f95c 100644 Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi index ac79ab23659..1180a8957ac 100644 Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ diff --git a/icons/obj/mining.dmi b/icons/obj/mining.dmi index 585db394acf..2e4d4dd2730 100644 Binary files a/icons/obj/mining.dmi and b/icons/obj/mining.dmi differ diff --git a/icons/obj/modular_components.dmi b/icons/obj/modular_components.dmi new file mode 100644 index 00000000000..3594a12c17f Binary files /dev/null and b/icons/obj/modular_components.dmi differ diff --git a/icons/obj/modular_console.dmi b/icons/obj/modular_console.dmi new file mode 100644 index 00000000000..a157462f57a Binary files /dev/null and b/icons/obj/modular_console.dmi differ diff --git a/icons/obj/modular_laptop.dmi b/icons/obj/modular_laptop.dmi new file mode 100644 index 00000000000..b3e0cb53648 Binary files /dev/null and b/icons/obj/modular_laptop.dmi differ diff --git a/icons/obj/modular_tablet.dmi b/icons/obj/modular_tablet.dmi new file mode 100644 index 00000000000..1872315f49f Binary files /dev/null and b/icons/obj/modular_tablet.dmi differ diff --git a/icons/program_icons/alarm_green.gif b/icons/program_icons/alarm_green.gif new file mode 100644 index 00000000000..7c2570c8ce3 Binary files /dev/null and b/icons/program_icons/alarm_green.gif differ diff --git a/icons/program_icons/alarm_red.gif b/icons/program_icons/alarm_red.gif new file mode 100644 index 00000000000..327d58e4db8 Binary files /dev/null and b/icons/program_icons/alarm_red.gif differ diff --git a/icons/program_icons/batt_100.gif b/icons/program_icons/batt_100.gif new file mode 100644 index 00000000000..ccdbd4333e9 Binary files /dev/null and b/icons/program_icons/batt_100.gif differ diff --git a/icons/program_icons/batt_20.gif b/icons/program_icons/batt_20.gif new file mode 100644 index 00000000000..1d5e38fc888 Binary files /dev/null and b/icons/program_icons/batt_20.gif differ diff --git a/icons/program_icons/batt_40.gif b/icons/program_icons/batt_40.gif new file mode 100644 index 00000000000..d5cedb99675 Binary files /dev/null and b/icons/program_icons/batt_40.gif differ diff --git a/icons/program_icons/batt_5.gif b/icons/program_icons/batt_5.gif new file mode 100644 index 00000000000..ceac8f3a945 Binary files /dev/null and b/icons/program_icons/batt_5.gif differ diff --git a/icons/program_icons/batt_60.gif b/icons/program_icons/batt_60.gif new file mode 100644 index 00000000000..d39534c8cb0 Binary files /dev/null and b/icons/program_icons/batt_60.gif differ diff --git a/icons/program_icons/batt_80.gif b/icons/program_icons/batt_80.gif new file mode 100644 index 00000000000..9c299773f01 Binary files /dev/null and b/icons/program_icons/batt_80.gif differ diff --git a/icons/program_icons/charging.gif b/icons/program_icons/charging.gif new file mode 100644 index 00000000000..cf19ca72876 Binary files /dev/null and b/icons/program_icons/charging.gif differ diff --git a/icons/program_icons/downloader_finished.gif b/icons/program_icons/downloader_finished.gif new file mode 100644 index 00000000000..f01b7c42af8 Binary files /dev/null and b/icons/program_icons/downloader_finished.gif differ diff --git a/icons/program_icons/downloader_running.gif b/icons/program_icons/downloader_running.gif new file mode 100644 index 00000000000..68fb977c86d Binary files /dev/null and b/icons/program_icons/downloader_running.gif differ diff --git a/icons/program_icons/ntnrc_idle.gif b/icons/program_icons/ntnrc_idle.gif new file mode 100644 index 00000000000..d47c01d5800 Binary files /dev/null and b/icons/program_icons/ntnrc_idle.gif differ diff --git a/icons/program_icons/ntnrc_new.gif b/icons/program_icons/ntnrc_new.gif new file mode 100644 index 00000000000..af72a8b332f Binary files /dev/null and b/icons/program_icons/ntnrc_new.gif differ diff --git a/icons/program_icons/power_norm.gif b/icons/program_icons/power_norm.gif new file mode 100644 index 00000000000..2b8d60edfa5 Binary files /dev/null and b/icons/program_icons/power_norm.gif differ diff --git a/icons/program_icons/power_warn.gif b/icons/program_icons/power_warn.gif new file mode 100644 index 00000000000..0c85b3a85a1 Binary files /dev/null and b/icons/program_icons/power_warn.gif differ diff --git a/icons/program_icons/sig_high.gif b/icons/program_icons/sig_high.gif new file mode 100644 index 00000000000..efb20f67306 Binary files /dev/null and b/icons/program_icons/sig_high.gif differ diff --git a/icons/program_icons/sig_lan.gif b/icons/program_icons/sig_lan.gif new file mode 100644 index 00000000000..07ba929f7f3 Binary files /dev/null and b/icons/program_icons/sig_lan.gif differ diff --git a/icons/program_icons/sig_low.gif b/icons/program_icons/sig_low.gif new file mode 100644 index 00000000000..08f98baea8e Binary files /dev/null and b/icons/program_icons/sig_low.gif differ diff --git a/icons/program_icons/sig_none.gif b/icons/program_icons/sig_none.gif new file mode 100644 index 00000000000..79840d8a2bd Binary files /dev/null and b/icons/program_icons/sig_none.gif differ diff --git a/interface/skin.dmf b/interface/skin.dmf index 8fa4df97371..f9fa3f4a69a 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -22,25 +22,25 @@ macro "default" command = "westface" elem name = "WEST+REP" - command = ".west" + command = ".moveleft" elem name = "CTRL+NORTH" command = "northface" elem name = "NORTH+REP" - command = ".north" + command = ".moveup" elem name = "CTRL+EAST" command = "eastface" elem name = "EAST+REP" - command = ".east" + command = ".moveright" elem name = "CTRL+SOUTH" command = "southface" elem name = "SOUTH+REP" - command = ".south" + command = ".movedown" elem name = "INSERT" command = "a-intent right" @@ -61,13 +61,13 @@ macro "default" command = "a-intent harm" elem name = "CTRL+A+REP" - command = ".west" + command = ".moveleft" elem name = "CTRL+B" command = "resist" elem name = "CTRL+D+REP" - command = ".east" + command = ".moveright" elem name = "CTRL+E" command = "quick-equip" @@ -85,10 +85,10 @@ macro "default" command = ".southwest" elem name = "CTRL+S+REP" - command = ".south" + command = ".movedown" elem name = "CTRL+W+REP" - command = ".north" + command = ".moveup" elem name = "CTRL+X" command = ".northeast" @@ -153,25 +153,25 @@ macro "hotkeys" command = "westface" elem name = "WEST+REP" - command = ".west" + command = ".moveleft" elem name = "CTRL+NORTH" command = "northface" elem name = "NORTH+REP" - command = ".north" + command = ".moveup" elem name = "CTRL+EAST" command = "eastface" elem name = "EAST+REP" - command = ".east" + command = ".moveright" elem name = "CTRL+SOUTH" command = "southface" elem name = "SOUTH+REP" - command = ".south" + command = ".movedown" elem name = "INSERT" command = "a-intent right" @@ -204,10 +204,10 @@ macro "hotkeys" command = "a-intent harm" elem name = "A+REP" - command = ".west" + command = ".moveleft" elem name = "CTRL+A+REP" - command = ".west" + command = ".moveleft" elem name = "B" command = "resist" @@ -216,10 +216,10 @@ macro "hotkeys" command = "resist" elem name = "D+REP" - command = ".east" + command = ".moveright" elem name = "CTRL+D+REP" - command = ".east" + command = ".moveright" elem name = "E" command = "quick-equip" @@ -261,19 +261,19 @@ macro "hotkeys" command = ".southwest" elem "s_key" name = "S+REP" - command = ".south" + command = ".movedown" elem name = "CTRL+S+REP" - command = ".south" + command = ".movedown" elem name = "T" command = "say" elem "w_key" name = "W+REP" - command = ".north" + command = ".moveup" elem name = "CTRL+W+REP" - command = ".north" + command = ".moveup" elem name = "X" command = ".northeast" @@ -342,25 +342,25 @@ macro "robot-default" command = "westface" elem name = "WEST+REP" - command = ".west" + command = ".moveleft" elem name = "CTRL+NORTH" command = "northface" elem name = "NORTH+REP" - command = ".north" + command = ".moveup" elem name = "CTRL+EAST" command = "eastface" elem name = "EAST+REP" - command = ".east" + command = ".moveright" elem name = "CTRL+SOUTH" command = "southface" elem name = "SOUTH+REP" - command = ".south" + command = ".movedown" elem name = "INSERT" command = "a-intent right" @@ -381,7 +381,7 @@ macro "robot-default" command = "a-intent left" elem name = "CTRL+A+REP" - command = ".west" + command = ".moveleft" elem name = "CTRL+B" command = "resist" @@ -390,7 +390,7 @@ macro "robot-default" command = "ooc" elem name = "CTRL+D+REP" - command = ".east" + command = ".moveright" elem name = "CTRL+F" command = "a-intent left" @@ -402,10 +402,10 @@ macro "robot-default" command = "unequip-module" elem name = "CTRL+S+REP" - command = ".south" + command = ".movedown" elem name = "CTRL+W+REP" - command = ".north" + command = ".moveup" elem name = "CTRL+X" command = ".northeast" @@ -464,25 +464,25 @@ macro "robot-hotkeys" command = "westface" elem name = "WEST+REP" - command = ".west" + command = ".moveleft" elem name = "CTRL+NORTH" command = "northface" elem name = "NORTH+REP" - command = ".north" + command = ".moveup" elem name = "CTRL+EAST" command = "eastface" elem name = "EAST+REP" - command = ".east" + command = ".moveright" elem name = "CTRL+SOUTH" command = "southface" elem name = "SOUTH+REP" - command = ".south" + command = ".movedown" elem name = "INSERT" command = "a-intent right" @@ -515,10 +515,10 @@ macro "robot-hotkeys" command = "a-intent left" elem name = "A+REP" - command = ".west" + command = ".moveleft" elem name = "CTRL+A+REP" - command = ".west" + command = ".moveleft" elem name = "B" command = "resist" @@ -533,10 +533,10 @@ macro "robot-hotkeys" command = "ooc" elem name = "D+REP" - command = ".east" + command = ".moveright" elem name = "CTRL+D+REP" - command = ".east" + command = ".moveright" elem name = "F" command = "a-intent left" @@ -557,19 +557,19 @@ macro "robot-hotkeys" command = "unequip-module" elem "s_key" name = "S+REP" - command = ".south" + command = ".movedown" elem name = "CTRL+S+REP" - command = ".south" + command = ".movedown" elem name = "T" command = "say" elem "w_key" name = "W+REP" - command = ".north" + command = ".moveup" elem name = "CTRL+W+REP" - command = ".north" + command = ".moveup" elem name = "X" command = ".northeast" diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm index b99addac573..cd5f268c7d4 100644 --- a/interface/stylesheet.dm +++ b/interface/stylesheet.dm @@ -91,6 +91,7 @@ h1.alert, h2.alert {color: #000000;} .heavy_brass {color: #BE8700; font-weight: bold; font-style: italic;} .large_brass {color: #BE8700; font-size: 3;} .big_brass {color: #BE8700; font-size: 3; font-weight: bold; font-style: italic;} +.ratvar {color: #BE8700; font-size: 6; font-weight: bold; font-style: italic;} .alloy {color: #42474D;} .heavy_alloy {color: #42474D; font-weight: bold; font-style: italic;} .large_alloy {color: #42474D; font-size: 3;} diff --git a/tgstation.dme b/tgstation.dme index 8012abce338..27f90292ede 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -612,7 +612,6 @@ #include "code\game\objects\items\devices\lightreplacer.dm" #include "code\game\objects\items\devices\machineprototype.dm" #include "code\game\objects\items\devices\megaphone.dm" -#include "code\game\objects\items\devices\modificationkit.dm" #include "code\game\objects\items\devices\multitool.dm" #include "code\game\objects\items\devices\paicard.dm" #include "code\game\objects\items\devices\pipe_painter.dm" @@ -1520,6 +1519,41 @@ #include "code\modules\mob\new_player\poll.dm" #include "code\modules\mob\new_player\preferences_setup.dm" #include "code\modules\mob\new_player\sprite_accessories.dm" +#include "code\modules\modular_computers\computers\item\modular_computer.dm" +#include "code\modules\modular_computers\computers\item\processor.dm" +#include "code\modules\modular_computers\computers\item\tablet.dm" +#include "code\modules\modular_computers\computers\item\tablet_presets.dm" +#include "code\modules\modular_computers\computers\machinery\console_presets.dm" +#include "code\modules\modular_computers\computers\machinery\modular_computer.dm" +#include "code\modules\modular_computers\computers\machinery\modular_console.dm" +#include "code\modules\modular_computers\computers\machinery\modular_laptop.dm" +#include "code\modules\modular_computers\file_system\computer_file.dm" +#include "code\modules\modular_computers\file_system\data.dm" +#include "code\modules\modular_computers\file_system\program.dm" +#include "code\modules\modular_computers\file_system\program_events.dm" +#include "code\modules\modular_computers\file_system\programs\alarm.dm" +#include "code\modules\modular_computers\file_system\programs\card.dm" +#include "code\modules\modular_computers\file_system\programs\configurator.dm" +#include "code\modules\modular_computers\file_system\programs\file_browser.dm" +#include "code\modules\modular_computers\file_system\programs\ntdownloader.dm" +#include "code\modules\modular_computers\file_system\programs\ntmonitor.dm" +#include "code\modules\modular_computers\file_system\programs\ntnrc_client.dm" +#include "code\modules\modular_computers\file_system\programs\nttransfer.dm" +#include "code\modules\modular_computers\file_system\programs\powermonitor.dm" +#include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm" +#include "code\modules\modular_computers\file_system\programs\antagonist\revelation.dm" +#include "code\modules\modular_computers\hardware\battery_module.dm" +#include "code\modules\modular_computers\hardware\card_slot.dm" +#include "code\modules\modular_computers\hardware\hard_drive.dm" +#include "code\modules\modular_computers\hardware\hardware.dm" +#include "code\modules\modular_computers\hardware\nano_printer.dm" +#include "code\modules\modular_computers\hardware\network_card.dm" +#include "code\modules\modular_computers\hardware\portable_hard_drive.dm" +#include "code\modules\modular_computers\hardware\processor_unit.dm" +#include "code\modules\modular_computers\hardware\tesla_link.dm" +#include "code\modules\modular_computers\NTNet\NTNet.dm" +#include "code\modules\modular_computers\NTNet\NTNet_relay.dm" +#include "code\modules\modular_computers\NTNet\NTNRC\conversation.dm" #include "code\modules\ninja\__ninjaDefines.dm" #include "code\modules\ninja\admin_ninja_verbs.dm" #include "code\modules\ninja\energy_katana.dm" @@ -1625,6 +1659,7 @@ #include "code\modules\projectiles\guns\mounted.dm" #include "code\modules\projectiles\guns\projectile.dm" #include "code\modules\projectiles\guns\syringe_gun.dm" +#include "code\modules\projectiles\guns\energy\kinetic_accelerator.dm" #include "code\modules\projectiles\guns\energy\laser.dm" #include "code\modules\projectiles\guns\energy\nuclear.dm" #include "code\modules\projectiles\guns\energy\pulse.dm" @@ -1704,6 +1739,7 @@ #include "code\modules\research\designs\autolathe_designs.dm" #include "code\modules\research\designs\biogenerator_designs.dm" #include "code\modules\research\designs\comp_board_designs.dm" +#include "code\modules\research\designs\computer_part_designs.dm" #include "code\modules\research\designs\machine_designs.dm" #include "code\modules\research\designs\mecha_designs.dm" #include "code\modules\research\designs\mechfabricator_designs.dm" diff --git a/tgui/assets/tgui.css b/tgui/assets/tgui.css index 6156a956a45..38c1495d003 100644 --- a/tgui/assets/tgui.css +++ b/tgui/assets/tgui.css @@ -1 +1 @@ -@charset "utf-8";body,html{box-sizing:border-box;height:100%;margin:0}html{overflow:hidden;cursor:default}body{overflow:auto;font-family:Verdana,Geneva,sans-serif;font-size:12px;color:#fff;background-color:#2a2a2a;background-image:linear-gradient(180deg,#2a2a2a 0,#202020);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2a2a2a',endColorstr='#ff202020',GradientType=0)}*,:after,:before{box-sizing:inherit}h1,h2,h3,h4{display:inline-block;margin:0;padding:6px 0}h1{font-size:18px}h2{font-size:16px}h3{font-size:14px}h4{font-size:12px}body.nanotrasen{background:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4wIiB2aWV3Qm94PSIwIDAgNDI1IDIwMCIgb3BhY2l0eT0iLjMzIj4NCiAgPHBhdGggZD0ibSAxNzguMDAzOTksMC4wMzg2OSAtNzEuMjAzOTMsMCBhIDYuNzYxMzQyMiw2LjAyNTU0OTUgMCAwIDAgLTYuNzYxMzQsNi4wMjU1NSBsIDAsMTg3Ljg3MTQ3IGEgNi43NjEzNDIyLDYuMDI1NTQ5NSAwIDAgMCA2Ljc2MTM0LDYuMDI1NTQgbCA1My4xMDcyLDAgYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIDYuNzYxMzUsLTYuMDI1NTQgbCAwLC0xMDEuNTQ0MDE4IDcyLjIxNjI4LDEwNC42OTkzOTggYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIDUuNzYwMTUsMi44NzAxNiBsIDczLjU1NDg3LDAgYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIDYuNzYxMzUsLTYuMDI1NTQgbCAwLC0xODcuODcxNDcgYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIC02Ljc2MTM1LC02LjAyNTU1IGwgLTU0LjcxNjQ0LDAgYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIC02Ljc2MTMzLDYuMDI1NTUgbCAwLDEwMi42MTkzNSBMIDE4My43NjQxMywyLjkwODg2IGEgNi43NjEzNDIyLDYuMDI1NTQ5NSAwIDAgMCAtNS43NjAxNCwtMi44NzAxNyB6IiAvPg0KICA8cGF0aCBkPSJNIDQuODQ0NjMzMywyMi4xMDg3NSBBIDEzLjQxMjAzOSwxMi41MDE4NDIgMCAwIDEgMTMuNDc3NTg4LDAuMDM5MjQgbCA2Ni4xMTgzMTUsMCBhIDUuMzY0ODE1OCw1LjAwMDczNyAwIDAgMSA1LjM2NDgyMyw1LjAwMDczIGwgMCw3OS44NzkzMSB6IiAvPg0KICA8cGF0aCBkPSJtIDQyMC4xNTUzNSwxNzcuODkxMTkgYSAxMy40MTIwMzgsMTIuNTAxODQyIDAgMCAxIC04LjYzMjk1LDIyLjA2OTUxIGwgLTY2LjExODMyLDAgYSA1LjM2NDgxNTIsNS4wMDA3MzcgMCAwIDEgLTUuMzY0ODIsLTUuMDAwNzQgbCAwLC03OS44NzkzMSB6IiAvPg0KPC9zdmc+DQo8IS0tIFRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciBhIENyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24tU2hhcmVBbGlrZSA0LjAgSW50ZXJuYXRpb25hbCBMaWNlbnNlLiAtLT4NCjwhLS0gaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktc2EvNC4wLyAtLT4NCg==") no-repeat fixed 50%/70% 70%,linear-gradient(180deg,#2a2a2a 0,#202020);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2a2a2a',endColorstr='#ff202020',GradientType=0)}body.nanotrasen .normal{color:#40628a}body.nanotrasen .good{color:#537d29}body.nanotrasen .average{color:#be6209}body.nanotrasen .bad{color:#b00e0e}body.nanotrasen .highlight{color:#8ba5c4}body.nanotrasen main{display:block;margin-top:32px;padding:2px 6px 0}body.nanotrasen hr{height:2px;background-color:#40628a;border:none}body.nanotrasen .hidden{display:none}body.nanotrasen .bar .barText,body.nanotrasen span.button{color:#fff;font-size:12px;font-weight:400;font-style:normal;text-decoration:none}body.nanotrasen .bold{font-weight:700}body.nanotrasen .italic{font-style:italic}body.nanotrasen [unselectable=on]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body.nanotrasen div[data-tooltip],body.nanotrasen span[data-tooltip]{position:relative}body.nanotrasen div[data-tooltip]:after,body.nanotrasen span[data-tooltip]:after{position:absolute;display:block;z-index:2;width:250px;padding:10px;-ms-transform:translateX(-50%);transform:translateX(-50%);visibility:hidden;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";white-space:normal;text-align:left;content:attr(data-tooltip);transition:all .5s;border:1px solid #272727;background-color:#363636}body.nanotrasen div[data-tooltip]:hover:after,body.nanotrasen span[data-tooltip]:hover:after{visibility:visible;opacity:1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"}body.nanotrasen div[data-tooltip].tooltip-top:after,body.nanotrasen span[data-tooltip].tooltip-top:after{bottom:100%;left:50%;-ms-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}body.nanotrasen div[data-tooltip].tooltip-top:hover:after,body.nanotrasen span[data-tooltip].tooltip-top:hover:after{-ms-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}body.nanotrasen div[data-tooltip].tooltip-bottom:after,body.nanotrasen span[data-tooltip].tooltip-bottom:after{top:100%;left:50%;-ms-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}body.nanotrasen div[data-tooltip].tooltip-bottom:hover:after,body.nanotrasen span[data-tooltip].tooltip-bottom:hover:after{-ms-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}body.nanotrasen div[data-tooltip].tooltip-left:after,body.nanotrasen span[data-tooltip].tooltip-left:after{top:50%;right:100%;-ms-transform:translateX(8px) translateY(-50%);transform:translateX(8px) translateY(-50%)}body.nanotrasen div[data-tooltip].tooltip-left:hover:after,body.nanotrasen span[data-tooltip].tooltip-left:hover:after{-ms-transform:translateX(-8px) translateY(-50%);transform:translateX(-8px) translateY(-50%)}body.nanotrasen div[data-tooltip].tooltip-right:after,body.nanotrasen span[data-tooltip].tooltip-right:after{top:50%;left:100%;-ms-transform:translateX(-8px) translateY(-50%);transform:translateX(-8px) translateY(-50%)}body.nanotrasen div[data-tooltip].tooltip-right:hover:after,body.nanotrasen span[data-tooltip].tooltip-right:hover:after{-ms-transform:translateX(8px) translateY(-50%);transform:translateX(8px) translateY(-50%)}body.nanotrasen .bar{display:inline-block;position:relative;vertical-align:middle;width:100%;height:20px;line-height:17px;padding:1px;border:1px solid #40628a;background:#272727}body.nanotrasen .bar .barText{position:absolute;top:0;right:3px}body.nanotrasen .bar .barFill{display:block;height:100%;transition:background-color 1s;background-color:#40628a}body.nanotrasen .bar .barFill.good{background-color:#537d29}body.nanotrasen .bar .barFill.average{background-color:#be6209}body.nanotrasen .bar .barFill.bad{background-color:#b00e0e}body.nanotrasen span.button{display:inline-block;vertical-align:middle;min-height:20px;line-height:17px;padding:0 5px;white-space:nowrap;border:1px solid #272727}body.nanotrasen span.button .fa{padding-right:2px}body.nanotrasen span.button.normal{transition:background-color .5s;background-color:#40628a}body.nanotrasen span.button.normal.active:focus,body.nanotrasen span.button.normal.active:hover{transition:background-color .25s;background-color:#4f78aa;outline:0}body.nanotrasen span.button.disabled{transition:background-color .5s;background-color:#999}body.nanotrasen span.button.disabled.active:focus,body.nanotrasen span.button.disabled.active:hover{transition:background-color .25s;background-color:#a8a8a8;outline:0}body.nanotrasen span.button.selected{transition:background-color .5s;background-color:#2f943c}body.nanotrasen span.button.selected.active:focus,body.nanotrasen span.button.selected.active:hover{transition:background-color .25s;background-color:#3ab84b;outline:0}body.nanotrasen span.button.caution{transition:background-color .5s;background-color:#9a9d00}body.nanotrasen span.button.caution.active:focus,body.nanotrasen span.button.caution.active:hover{transition:background-color .25s;background-color:#ced200;outline:0}body.nanotrasen span.button.danger{transition:background-color .5s;background-color:#9d0808}body.nanotrasen span.button.danger.active:focus,body.nanotrasen span.button.danger.active:hover{transition:background-color .25s;background-color:#ce0b0b;outline:0}body.nanotrasen span.button.gridable{width:125px;margin:2px 0}body.nanotrasen span.button+span:not(.button),body.nanotrasen span:not(.button)+span.button{margin-left:5px}body.nanotrasen div.display{width:100%;padding:4px;margin:6px 0;background-color:#000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=#54000000,endColorStr=#54000000)";filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#54000000,endColorStr=#54000000);background-color:rgba(0,0,0,.33);box-shadow:inset 0 0 5px rgba(0,0,0,.5)}body.nanotrasen div.display header,body.nanotrasen div.subdisplay header{display:block;position:relative;width:100%;padding:0 4px;margin-bottom:6px;color:#fff;border-bottom:2px solid #40628a}body.nanotrasen div.display header .buttonRight,body.nanotrasen div.subdisplay header .buttonRight{position:absolute;bottom:6px;right:4px}body.nanotrasen div.display article,body.nanotrasen div.subdisplay article{display:table;width:100%;border-collapse:collapse}body.nanotrasen input{display:inline-block;vertical-align:middle;height:20px;line-height:17px;padding:0 5px;white-space:nowrap;color:#000;background-color:#fff;border:1px solid #272727}body.nanotrasen input::-webkit-input-placeholder{color:#999}body.nanotrasen input::-moz-placeholder{color:#999}body.nanotrasen input:-ms-input-placeholder{color:#999}body.nanotrasen input::placeholder{color:#999}body.nanotrasen input::-ms-clear{display:none}body.nanotrasen svg.linegraph{overflow:hidden}body.nanotrasen div.notice{margin:8px 0;padding:4px;box-shadow:none;color:#000;font-weight:700;font-style:italic;background-color:#bb9b68;background-image:repeating-linear-gradient(-45deg,#bb9b68,#bb9b68 10px,#b1905d 0,#b1905d 20px)}body.nanotrasen div.notice .label{color:#000}body.nanotrasen div.notice .content:only-of-type{padding:0}body.nanotrasen div.notice hr{background-color:#272727}body.nanotrasen div.resize{position:fixed;bottom:0;right:0;width:0;height:0;border-style:solid;border-width:0 0 45px 45px;border-color:transparent transparent #363636;-ms-transform:rotate(1turn);transform:rotate(1turn)}body.nanotrasen section .content,body.nanotrasen section .label,body.nanotrasen section .line,body.syndicate section .content,body.syndicate section .label,body.syndicate section .line{display:table-cell;margin:0;text-align:left;vertical-align:middle;padding:3px 2px}body.nanotrasen section{display:table-row;width:100%}body.nanotrasen section:not(:first-child){padding-top:4px}body.nanotrasen section.candystripe:nth-child(even){background-color:#000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=#33000000,endColorStr=#33000000)";filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#33000000,endColorStr=#33000000);background-color:rgba(0,0,0,.2)}body.nanotrasen section .label{width:1%;padding-right:32px;white-space:nowrap;color:#8ba5c4}body.nanotrasen section .content:not(:last-child){padding-right:16px}body.nanotrasen section .line{width:100%}body.nanotrasen div.subdisplay{width:100%;margin:0}body.nanotrasen header.titlebar .close,body.nanotrasen header.titlebar .minimize{display:inline-block;position:relative;padding:7px;margin:-7px;color:#8ba5c4}body.nanotrasen header.titlebar .close:hover,body.nanotrasen header.titlebar .minimize:hover{color:#9cb2cd}body.nanotrasen header.titlebar{position:fixed;z-index:1;top:0;left:0;width:100%;height:32px;background-color:#363636;border-bottom:1px solid #161616;box-shadow:0 3px 3px rgba(0,0,0,.1)}body.nanotrasen header.titlebar .statusicon{position:absolute;top:4px;left:12px;transition:color .5s}body.nanotrasen header.titlebar .title{position:absolute;top:6px;left:46px;color:#8ba5c4;font-size:16px;white-space:nowrap}body.nanotrasen header.titlebar .minimize{position:absolute;top:6px;right:46px}body.nanotrasen header.titlebar .close{position:absolute;top:4px;right:12px}body.syndicate{background:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4wIiB2aWV3Qm94PSIwIDAgMjAwIDI4OS43NDIiIG9wYWNpdHk9Ii4zMyI+DQogIDxwYXRoIGQ9Im0gOTMuNTM3Njc3LDAgYyAtMTguMTEzMTI1LDAgLTM0LjIyMDEzMywzLjExMTY0IC00OC4zMjM0ODQsOS4zMzQzNyAtMTMuOTY1MDkyLDYuMjIxNjcgLTI0LjYxMjQ0MiwxNS4wNzExNCAtMzEuOTQwNjUxLDI2LjU0NzEgLTcuMTg5OTM5OCwxMS4zMzc4OSAtMTAuMzAxMjI2NiwyNC43NDkxMSAtMTAuMzAxMjI2Niw0MC4yMzQ3OCAwLDEwLjY0NjYyIDIuNzI1MDAyNiwyMC40NjQ2NSA4LjE3NTExMTYsMjkuNDUyNTggNS42MTUyNzcsOC45ODY4NiAxNC4wMzgyNzcsMTcuMzUyMDQgMjUuMjY4ODIxLDI1LjA5NDM2IDExLjIzMDU0NCw3LjYwNTMxIDI2LjUwNzQyMSwxNS40MTgzNSA0NS44MzA1MTQsMjMuNDM3ODIgMTkuOTgzNzQ4LDguMjk1NTcgMzQuODQ4ODQ4LDE1LjU1NDcxIDQ0LjU5Mjk5OCwyMS43NzYzOCA5Ljc0NDE0LDYuMjIyNzMgMTYuNzYxNywxMi44NTg1IDIxLjA1NTcyLDE5LjkwOTUxIDQuMjk0MDQsNy4wNTIwOCA2LjQ0MTkzLDE1Ljc2NDA4IDYuNDQxOTMsMjYuMTM0NTkgMCwxNi4xNzcwMiAtNS4yMDE5NiwyOC40ODIyMiAtMTUuNjA2NzMsMzYuOTE2ODIgLTEwLjIzOTYsOC40MzQ3IC0yNS4wMjIwMywxMi42NTIzIC00NC4zNDUxNjksMTIuNjUyMyAtMTQuMDM4MTcxLDAgLTI1LjUxNTI0NywtMS42NTk0IC0zNC40MzM2MTgsLTQuOTc3NyAtOC45MTgzNywtMy40NTY2IC0xNi4xODU1NzIsLTguNzExMyAtMjEuODAwODM5LC0xNS43NjMzIC01LjYxNTI3NywtNy4wNTIxIC0xMC4wNzQ3OTUsLTE2LjY2MDg4IC0xMy4zNzc4OTksLTI4LjgyODEyIGwgLTI0Ljc3MzE2MjYyOTM5NDUsMCAwLDU2LjgyNjMyIEMgMzMuODU2NzY5LDI4Ni4wNzYwMSA2My43NDkwNCwyODkuNzQyMDEgODkuNjc4MzgzLDI4OS43NDIwMSBjIDE2LjAyMDAyNywwIDMwLjcxOTc4NywtMS4zODI3IDQ0LjA5NzMzNywtNC4xNDc5IDEzLjU0MjcyLC0yLjkwNDMgMjUuMTA0MSwtNy40Njc2IDM0LjY4MzA5LC0xMy42ODkzIDkuNzQ0MTMsLTYuMzU5NyAxNy4zNDA0MiwtMTQuNTE5NSAyMi43OTA1MiwtMjQuNDc0OCA1LjQ1MDEsLTEwLjA5MzMyIDguMTc1MTEsLTIyLjM5OTU5IDguMTc1MTEsLTM2LjkxNjgyIDAsLTEyLjk5NzY0IC0zLjMwMjEsLTI0LjMzNTM5IC05LjkwODI5LC0zNC4wMTQ2IC02LjQ0MTA1LC05LjgxNzI1IC0xNS41MjU0NSwtMTguNTI3MDcgLTI3LjI1MTQ2LC0yNi4xMzEzMyAtMTEuNTYwODUsLTcuNjA0MjcgLTI3LjkxMDgzLC0xNS44MzE0MiAtNDkuMDUwNjYsLTI0LjY4MDIyIC0xNy41MDY0NCwtNy4xOTAxMiAtMzAuNzE5NjY4LC0xMy42ODk0OCAtMzkuNjM4MDM4LC0xOS40OTcwMSAtOC45MTgzNzEsLTUuODA3NTIgLTE4LjYwNzQ3NCwtMTIuNDM0MDkgLTI0LjA5NjUyNCwtMTguODc0MTcgLTUuNDI2MDQzLC02LjM2NjE2IC05LjY1ODgyNiwtMTUuMDcwMDMgLTkuNjU4ODI2LC0yNC44ODcyOSAwLC05LjI2NDAxIDIuMDc1NDE0LC0xNy4yMTM0NSA2LjIyMzQ1NCwtMjMuODUwMzMgMTEuMDk4Mjk4LC0xNC4zOTc0OCA0MS4yODY2MzgsLTEuNzk1MDcgNDUuMDc1NjA5LDI0LjM0NzYyIDQuODM5MzkyLDYuNzc0OTEgOC44NDkzNSwxNi4yNDcyOSAxMi4wMjk1MTUsMjguNDE1NiBsIDIwLjUzMjM0LDAgMCwtNTUuOTk5NjcgYyAtNC40NzgyNSwtNS45MjQ0OCAtOS45NTQ4OCwtMTAuNjMyMjIgLTE1LjkwODM3LC0xNC4zNzQxMSAxLjY0MDU1LDAuNDc5MDUgMy4xOTAzOSwxLjAyMzc2IDQuNjM4NjUsMS42NDAyNCA2LjQ5ODYxLDIuNjI2MDcgMTIuMTY3OTMsNy4zMjc0NyAxNy4wMDczLDE0LjEwMzQ1IDQuODM5MzksNi43NzQ5MSA4Ljg0OTM1LDE2LjI0NTY3IDEyLjAyOTUyLDI4LjQxMzk3IDAsMCA4LjQ4MTI4LC0wLjEyODk0IDguNDg5NzgsLTAuMDAyIDAuNDE3NzYsNi40MTQ5NCAtMS43NTMzOSw5LjQ1Mjg2IC00LjEyMzQyLDEyLjU2MTA0IC0yLjQxNzQsMy4xNjk3OCAtNS4xNDQ4Niw2Ljc4OTczIC00LjAwMjc4LDEzLjAwMjkgMS41MDc4Niw4LjIwMzE4IDEwLjE4MzU0LDEwLjU5NjQyIDE0LjYyMTk0LDkuMzExNTQgLTMuMzE4NDIsLTAuNDk5MTEgLTUuMzE4NTUsLTEuNzQ5NDggLTUuMzE4NTUsLTEuNzQ5NDggMCwwIDEuODc2NDYsMC45OTg2OCA1LjY1MTE3LC0xLjM1OTgxIC0zLjI3Njk1LDAuOTU1NzEgLTEwLjcwNTI5LC0wLjc5NzM4IC0xMS44MDEyNSwtNi43NjMxMyAtMC45NTc1MiwtNS4yMDg2MSAwLjk0NjU0LC03LjI5NTE0IDMuNDAxMTMsLTEwLjUxNDgyIDIuNDU0NjIsLTMuMjE5NjggNS4yODQyNiwtNi45NTgzMSA0LjY4NDMsLTE0LjQ4ODI0IGwgMC4wMDMsMC4wMDIgOC45MjY3NiwwIDAsLTU1Ljk5OTY3IGMgLTE1LjA3MTI1LC0zLjg3MTY4IC0yNy42NTMxNCwtNi4zNjA0MiAtMzcuNzQ2NzEsLTcuNDY1ODYgLTkuOTU1MzEsLTEuMTA3NTUgLTIwLjE4ODIzLC0xLjY1OTgxIC0zMC42OTY2MTMsLTEuNjU5ODEgeiBtIDcwLjMyMTYwMywxNy4zMDg5MyAwLjIzODA1LDQwLjMwNDkgYyAxLjMxODA4LDEuMjI2NjYgMi40Mzk2NSwyLjI3ODE1IDMuMzQwODEsMy4xMDYwMiA0LjgzOTM5LDYuNzc0OTEgOC44NDkzNCwxNi4yNDU2NiAxMi4wMjk1MSwyOC40MTM5NyBsIDIwLjUzMjM0LDAgMCwtNTUuOTk5NjcgYyAtNi42NzczMSwtNC41OTM4MSAtMTkuODM2NDMsLTEwLjQ3MzA5IC0zNi4xNDA3MSwtMTUuODI1MjIgeiBtIC0yOC4xMjA0OSw1LjYwNTUxIDguNTY0NzksMTcuNzE2NTUgYyAtMTEuOTcwMzcsLTYuNDY2OTcgLTEzLjg0Njc4LC05LjcxNzI2IC04LjU2NDc5LC0xNy43MTY1NSB6IG0gMjIuNzk3MDUsMCBjIDIuNzcxNSw3Ljk5OTI5IDEuNzg3NDEsMTEuMjQ5NTggLTQuNDkzNTQsMTcuNzE2NTUgbCA0LjQ5MzU0LC0xNy43MTY1NSB6IG0gMTUuMjIxOTUsMjQuMDA4NDggOC41NjQ3OSwxNy43MTY1NSBjIC0xMS45NzAzOCwtNi40NjY5NyAtMTMuODQ2NzksLTkuNzE3MjYgLTguNTY0NzksLTE3LjcxNjU1IHogbSAyMi43OTcwNCwwIGMgMi43NzE1LDcuOTk5MjkgMS43ODc0MSwxMS4yNDk1OCAtNC40OTM1NCwxNy43MTY1NSBsIDQuNDkzNTQsLTE3LjcxNjU1IHogbSAtOTkuMTEzODQsMi4yMDc2NCA4LjU2NDc5LDE3LjcxNjU1IGMgLTExLjk3MDM4MiwtNi40NjY5NyAtMTMuODQ2NzgyLC05LjcxNzI2IC04LjU2NDc5LC0xNy43MTY1NSB6IG0gMjIuNzk1NDIsMCBjIDIuNzcxNSw3Ljk5OTI5IDEuNzg3NDEsMTEuMjQ5NTggLTQuNDkzNTQsMTcuNzE2NTUgbCA0LjQ5MzU0LC0xNy43MTY1NSB6IiAvPg0KPC9zdmc+DQo8IS0tIFRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciBhIENyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24tU2hhcmVBbGlrZSA0LjAgSW50ZXJuYXRpb25hbCBMaWNlbnNlLiAtLT4NCjwhLS0gaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktc2EvNC4wLyAtLT4NCg==") no-repeat fixed 50%/70% 70%,linear-gradient(180deg,#750000 0,#340404);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff750000',endColorstr='#ff340404',GradientType=0)}body.syndicate .normal{color:#40628a}body.syndicate .good{color:#73e573}body.syndicate .average{color:#be6209}body.syndicate .bad{color:#b00e0e}body.syndicate .highlight{color:#000}body.syndicate main{display:block;margin-top:32px;padding:2px 6px 0}body.syndicate hr{height:2px;background-color:#272727;border:none}body.syndicate .hidden{display:none}body.syndicate .bar .barText,body.syndicate span.button{color:#fff;font-size:12px;font-weight:400;font-style:normal;text-decoration:none}body.syndicate .bold{font-weight:700}body.syndicate .italic{font-style:italic}body.syndicate [unselectable=on]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body.syndicate div[data-tooltip],body.syndicate span[data-tooltip]{position:relative}body.syndicate div[data-tooltip]:after,body.syndicate span[data-tooltip]:after{position:absolute;display:block;z-index:2;width:250px;padding:10px;-ms-transform:translateX(-50%);transform:translateX(-50%);visibility:hidden;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";white-space:normal;text-align:left;content:attr(data-tooltip);transition:all .5s;border:1px solid #272727;background-color:#363636}body.syndicate div[data-tooltip]:hover:after,body.syndicate span[data-tooltip]:hover:after{visibility:visible;opacity:1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"}body.syndicate div[data-tooltip].tooltip-top:after,body.syndicate span[data-tooltip].tooltip-top:after{bottom:100%;left:50%;-ms-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}body.syndicate div[data-tooltip].tooltip-top:hover:after,body.syndicate span[data-tooltip].tooltip-top:hover:after{-ms-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}body.syndicate div[data-tooltip].tooltip-bottom:after,body.syndicate span[data-tooltip].tooltip-bottom:after{top:100%;left:50%;-ms-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}body.syndicate div[data-tooltip].tooltip-bottom:hover:after,body.syndicate span[data-tooltip].tooltip-bottom:hover:after{-ms-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}body.syndicate div[data-tooltip].tooltip-left:after,body.syndicate span[data-tooltip].tooltip-left:after{top:50%;right:100%;-ms-transform:translateX(8px) translateY(-50%);transform:translateX(8px) translateY(-50%)}body.syndicate div[data-tooltip].tooltip-left:hover:after,body.syndicate span[data-tooltip].tooltip-left:hover:after{-ms-transform:translateX(-8px) translateY(-50%);transform:translateX(-8px) translateY(-50%)}body.syndicate div[data-tooltip].tooltip-right:after,body.syndicate span[data-tooltip].tooltip-right:after{top:50%;left:100%;-ms-transform:translateX(-8px) translateY(-50%);transform:translateX(-8px) translateY(-50%)}body.syndicate div[data-tooltip].tooltip-right:hover:after,body.syndicate span[data-tooltip].tooltip-right:hover:after{-ms-transform:translateX(8px) translateY(-50%);transform:translateX(8px) translateY(-50%)}body.syndicate .bar{display:inline-block;position:relative;vertical-align:middle;width:100%;height:20px;line-height:17px;padding:1px;border:1px solid #000;background:#272727}body.syndicate .bar .barText{position:absolute;top:0;right:3px}body.syndicate .bar .barFill{display:block;height:100%;transition:background-color 1s;background-color:#000}body.syndicate .bar .barFill.good{background-color:#73e573}body.syndicate .bar .barFill.average{background-color:#be6209}body.syndicate .bar .barFill.bad{background-color:#b00e0e}body.syndicate span.button{display:inline-block;vertical-align:middle;min-height:20px;line-height:17px;padding:0 5px;white-space:nowrap;border:1px solid #272727}body.syndicate span.button .fa{padding-right:2px}body.syndicate span.button.normal{transition:background-color .5s;background-color:#397439}body.syndicate span.button.normal.active:focus,body.syndicate span.button.normal.active:hover{transition:background-color .25s;background-color:#4a964a;outline:0}body.syndicate span.button.disabled{transition:background-color .5s;background-color:#363636}body.syndicate span.button.disabled.active:focus,body.syndicate span.button.disabled.active:hover{transition:background-color .25s;background-color:#545454;outline:0}body.syndicate span.button.selected{transition:background-color .5s;background-color:#9d0808}body.syndicate span.button.selected.active:focus,body.syndicate span.button.selected.active:hover{transition:background-color .25s;background-color:#ce0b0b;outline:0}body.syndicate span.button.caution{transition:background-color .5s;background-color:#be6209}body.syndicate span.button.caution.active:focus,body.syndicate span.button.caution.active:hover{transition:background-color .25s;background-color:#eb790b;outline:0}body.syndicate span.button.danger{transition:background-color .5s;background-color:#9a9d00}body.syndicate span.button.danger.active:focus,body.syndicate span.button.danger.active:hover{transition:background-color .25s;background-color:#ced200;outline:0}body.syndicate span.button.gridable{width:125px;margin:2px 0}body.syndicate span.button+span:not(.button),body.syndicate span:not(.button)+span.button{margin-left:5px}body.syndicate div.display{width:100%;padding:4px;margin:6px 0;background-color:#000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=#80000000,endColorStr=#80000000)";filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#80000000,endColorStr=#80000000);background-color:rgba(0,0,0,.5);box-shadow:inset 0 0 5px rgba(0,0,0,.75)}body.syndicate div.display header,body.syndicate div.subdisplay header{display:block;position:relative;width:100%;padding:0 4px;margin-bottom:6px;color:#fff;border-bottom:2px solid #272727}body.syndicate div.display header .buttonRight,body.syndicate div.subdisplay header .buttonRight{position:absolute;bottom:6px;right:4px}body.syndicate div.display article,body.syndicate div.subdisplay article{display:table;width:100%;border-collapse:collapse}body.syndicate input{display:inline-block;vertical-align:middle;height:20px;line-height:17px;padding:0 5px;white-space:nowrap;color:#fff;background-color:#9d0808;border:1px solid #272727}body.syndicate input::-webkit-input-placeholder{color:#999}body.syndicate input::-moz-placeholder{color:#999}body.syndicate input:-ms-input-placeholder{color:#999}body.syndicate input::placeholder{color:#999}body.syndicate input::-ms-clear{display:none}body.syndicate svg.linegraph{overflow:hidden}body.syndicate div.notice{margin:8px 0;padding:4px;box-shadow:none;color:#000;font-weight:700;font-style:italic;background-color:#750000;background-image:repeating-linear-gradient(-45deg,#750000,#750000 10px,#910101 0,#910101 20px)}body.syndicate div.notice .label{color:#000}body.syndicate div.notice .content:only-of-type{padding:0}body.syndicate div.notice hr{background-color:#272727}body.syndicate div.resize{position:fixed;bottom:0;right:0;width:0;height:0;border-style:solid;border-width:0 0 45px 45px;border-color:transparent transparent #363636;-ms-transform:rotate(1turn);transform:rotate(1turn)}body.syndicate section .content,body.syndicate section .label,body.syndicate section .line{display:table-cell;margin:0;text-align:left;vertical-align:middle;padding:3px 2px}body.syndicate section{display:table-row;width:100%}body.syndicate section:not(:first-child){padding-top:4px}body.syndicate section.candystripe:nth-child(even){background-color:#000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=#33000000,endColorStr=#33000000)";filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#33000000,endColorStr=#33000000);background-color:rgba(0,0,0,.2)}body.syndicate section .label{width:1%;padding-right:32px;white-space:nowrap;color:#fff}body.syndicate section .content:not(:last-child){padding-right:16px}body.syndicate section .line{width:100%}body.syndicate div.subdisplay{width:100%;margin:0}body.syndicate header.titlebar .close,body.syndicate header.titlebar .minimize{display:inline-block;position:relative;padding:7px;margin:-7px;color:#e74242}body.syndicate header.titlebar .close:hover,body.syndicate header.titlebar .minimize:hover{color:#eb5e5e}body.syndicate header.titlebar{position:fixed;z-index:1;top:0;left:0;width:100%;height:32px;background-color:#363636;border-bottom:1px solid #161616;box-shadow:0 3px 3px rgba(0,0,0,.1)}body.syndicate header.titlebar .statusicon{position:absolute;top:4px;left:12px;transition:color .5s}body.syndicate header.titlebar .title{position:absolute;top:6px;left:46px;color:#e74242;font-size:16px;white-space:nowrap}body.syndicate header.titlebar .minimize{position:absolute;top:6px;right:46px}body.syndicate header.titlebar .close{position:absolute;top:4px;right:12px}.no-icons header.titlebar .statusicon{font-size:20px}.no-icons header.titlebar .statusicon:after{content:"O"}.no-icons header.titlebar .minimize{top:-2px;font-size:20px}.no-icons header.titlebar .minimize:after{content:"—"}.no-icons header.titlebar .close{font-size:20px}.no-icons header.titlebar .close:after{content:"X"} \ No newline at end of file +@charset "utf-8";body,html{box-sizing:border-box;height:100%;margin:0}html{overflow:hidden;cursor:default}body{overflow:auto;font-family:Verdana,Geneva,sans-serif;font-size:12px;color:#fff;background-color:#2a2a2a;background-image:linear-gradient(180deg,#2a2a2a 0,#202020);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2a2a2a',endColorstr='#ff202020',GradientType=0)}*,:after,:before{box-sizing:inherit}h1,h2,h3,h4{display:inline-block;margin:0;padding:6px 0}h1{font-size:18px}h2{font-size:16px}h3{font-size:14px}h4{font-size:12px}body.nanotrasen{background:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4wIiB2aWV3Qm94PSIwIDAgNDI1IDIwMCIgb3BhY2l0eT0iLjMzIj4NCiAgPHBhdGggZD0ibSAxNzguMDAzOTksMC4wMzg2OSAtNzEuMjAzOTMsMCBhIDYuNzYxMzQyMiw2LjAyNTU0OTUgMCAwIDAgLTYuNzYxMzQsNi4wMjU1NSBsIDAsMTg3Ljg3MTQ3IGEgNi43NjEzNDIyLDYuMDI1NTQ5NSAwIDAgMCA2Ljc2MTM0LDYuMDI1NTQgbCA1My4xMDcyLDAgYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIDYuNzYxMzUsLTYuMDI1NTQgbCAwLC0xMDEuNTQ0MDE4IDcyLjIxNjI4LDEwNC42OTkzOTggYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIDUuNzYwMTUsMi44NzAxNiBsIDczLjU1NDg3LDAgYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIDYuNzYxMzUsLTYuMDI1NTQgbCAwLC0xODcuODcxNDcgYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIC02Ljc2MTM1LC02LjAyNTU1IGwgLTU0LjcxNjQ0LDAgYSA2Ljc2MTM0MjIsNi4wMjU1NDk1IDAgMCAwIC02Ljc2MTMzLDYuMDI1NTUgbCAwLDEwMi42MTkzNSBMIDE4My43NjQxMywyLjkwODg2IGEgNi43NjEzNDIyLDYuMDI1NTQ5NSAwIDAgMCAtNS43NjAxNCwtMi44NzAxNyB6IiAvPg0KICA8cGF0aCBkPSJNIDQuODQ0NjMzMywyMi4xMDg3NSBBIDEzLjQxMjAzOSwxMi41MDE4NDIgMCAwIDEgMTMuNDc3NTg4LDAuMDM5MjQgbCA2Ni4xMTgzMTUsMCBhIDUuMzY0ODE1OCw1LjAwMDczNyAwIDAgMSA1LjM2NDgyMyw1LjAwMDczIGwgMCw3OS44NzkzMSB6IiAvPg0KICA8cGF0aCBkPSJtIDQyMC4xNTUzNSwxNzcuODkxMTkgYSAxMy40MTIwMzgsMTIuNTAxODQyIDAgMCAxIC04LjYzMjk1LDIyLjA2OTUxIGwgLTY2LjExODMyLDAgYSA1LjM2NDgxNTIsNS4wMDA3MzcgMCAwIDEgLTUuMzY0ODIsLTUuMDAwNzQgbCAwLC03OS44NzkzMSB6IiAvPg0KPC9zdmc+DQo8IS0tIFRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciBhIENyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24tU2hhcmVBbGlrZSA0LjAgSW50ZXJuYXRpb25hbCBMaWNlbnNlLiAtLT4NCjwhLS0gaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktc2EvNC4wLyAtLT4NCg==") no-repeat fixed 50%/70% 70%,linear-gradient(180deg,#2a2a2a 0,#202020);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2a2a2a',endColorstr='#ff202020',GradientType=0)}body.nanotrasen .normal{color:#40628a}body.nanotrasen .good{color:#537d29}body.nanotrasen .average{color:#be6209}body.nanotrasen .bad{color:#b00e0e}body.nanotrasen .highlight{color:#8ba5c4}body.nanotrasen main{display:block;margin-top:32px;padding:2px 6px 0}body.nanotrasen hr{height:2px;background-color:#40628a;border:none}body.nanotrasen .hidden{display:none}body.nanotrasen .bar .barText,body.nanotrasen span.button{color:#fff;font-size:12px;font-weight:400;font-style:normal;text-decoration:none}body.nanotrasen .bold{font-weight:700}body.nanotrasen .italic{font-style:italic}body.nanotrasen [unselectable=on]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body.nanotrasen div[data-tooltip],body.nanotrasen span[data-tooltip]{position:relative}body.nanotrasen div[data-tooltip]:after,body.nanotrasen span[data-tooltip]:after{position:absolute;display:block;z-index:2;width:250px;padding:10px;-ms-transform:translateX(-50%);transform:translateX(-50%);visibility:hidden;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";white-space:normal;text-align:left;content:attr(data-tooltip);transition:all .5s;border:1px solid #272727;background-color:#363636}body.nanotrasen div[data-tooltip]:hover:after,body.nanotrasen span[data-tooltip]:hover:after{visibility:visible;opacity:1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"}body.nanotrasen div[data-tooltip].tooltip-top:after,body.nanotrasen span[data-tooltip].tooltip-top:after{bottom:100%;left:50%;-ms-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}body.nanotrasen div[data-tooltip].tooltip-top:hover:after,body.nanotrasen span[data-tooltip].tooltip-top:hover:after{-ms-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}body.nanotrasen div[data-tooltip].tooltip-bottom:after,body.nanotrasen span[data-tooltip].tooltip-bottom:after{top:100%;left:50%;-ms-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}body.nanotrasen div[data-tooltip].tooltip-bottom:hover:after,body.nanotrasen span[data-tooltip].tooltip-bottom:hover:after{-ms-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}body.nanotrasen div[data-tooltip].tooltip-left:after,body.nanotrasen span[data-tooltip].tooltip-left:after{top:50%;right:100%;-ms-transform:translateX(8px) translateY(-50%);transform:translateX(8px) translateY(-50%)}body.nanotrasen div[data-tooltip].tooltip-left:hover:after,body.nanotrasen span[data-tooltip].tooltip-left:hover:after{-ms-transform:translateX(-8px) translateY(-50%);transform:translateX(-8px) translateY(-50%)}body.nanotrasen div[data-tooltip].tooltip-right:after,body.nanotrasen span[data-tooltip].tooltip-right:after{top:50%;left:100%;-ms-transform:translateX(-8px) translateY(-50%);transform:translateX(-8px) translateY(-50%)}body.nanotrasen div[data-tooltip].tooltip-right:hover:after,body.nanotrasen span[data-tooltip].tooltip-right:hover:after{-ms-transform:translateX(8px) translateY(-50%);transform:translateX(8px) translateY(-50%)}body.nanotrasen .bar{display:inline-block;position:relative;vertical-align:middle;width:100%;height:20px;line-height:17px;padding:1px;border:1px solid #40628a;background:#272727}body.nanotrasen .bar .barText{position:absolute;top:0;right:3px}body.nanotrasen .bar .barFill{display:block;height:100%;transition:background-color 1s;background-color:#40628a}body.nanotrasen .bar .barFill.good{background-color:#537d29}body.nanotrasen .bar .barFill.average{background-color:#be6209}body.nanotrasen .bar .barFill.bad{background-color:#b00e0e}body.nanotrasen span.button{display:inline-block;vertical-align:middle;min-height:20px;line-height:17px;padding:0 5px;white-space:nowrap;border:1px solid #272727}body.nanotrasen span.button .fa{padding-right:2px}body.nanotrasen span.button.normal{transition:background-color .5s;background-color:#40628a}body.nanotrasen span.button.normal.active:focus,body.nanotrasen span.button.normal.active:hover{transition:background-color .25s;background-color:#4f78aa;outline:0}body.nanotrasen span.button.disabled{transition:background-color .5s;background-color:#999}body.nanotrasen span.button.disabled.active:focus,body.nanotrasen span.button.disabled.active:hover{transition:background-color .25s;background-color:#a8a8a8;outline:0}body.nanotrasen span.button.selected{transition:background-color .5s;background-color:#2f943c}body.nanotrasen span.button.selected.active:focus,body.nanotrasen span.button.selected.active:hover{transition:background-color .25s;background-color:#3ab84b;outline:0}body.nanotrasen span.button.toggle{transition:background-color .5s;background-color:#2f943c}body.nanotrasen span.button.toggle.active:focus,body.nanotrasen span.button.toggle.active:hover{transition:background-color .25s;background-color:#3ab84b;outline:0}body.nanotrasen span.button.caution{transition:background-color .5s;background-color:#9a9d00}body.nanotrasen span.button.caution.active:focus,body.nanotrasen span.button.caution.active:hover{transition:background-color .25s;background-color:#ced200;outline:0}body.nanotrasen span.button.danger{transition:background-color .5s;background-color:#9d0808}body.nanotrasen span.button.danger.active:focus,body.nanotrasen span.button.danger.active:hover{transition:background-color .25s;background-color:#ce0b0b;outline:0}body.nanotrasen span.button.gridable{width:125px;margin:2px 0}body.nanotrasen span.button+span:not(.button),body.nanotrasen span:not(.button)+span.button{margin-left:5px}body.nanotrasen div.display{width:100%;padding:4px;margin:6px 0;background-color:#000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=#54000000,endColorStr=#54000000)";filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#54000000,endColorStr=#54000000);background-color:rgba(0,0,0,.33);box-shadow:inset 0 0 5px rgba(0,0,0,.5)}body.nanotrasen div.display header,body.nanotrasen div.subdisplay header{display:block;position:relative;width:100%;padding:0 4px;margin-bottom:6px;color:#fff;border-bottom:2px solid #40628a}body.nanotrasen div.display header .buttonRight,body.nanotrasen div.subdisplay header .buttonRight{position:absolute;bottom:6px;right:4px}body.nanotrasen div.display article,body.nanotrasen div.subdisplay article{display:table;width:100%;border-collapse:collapse}body.nanotrasen input{display:inline-block;vertical-align:middle;height:20px;line-height:17px;padding:0 5px;white-space:nowrap;color:#000;background-color:#fff;border:1px solid #272727}body.nanotrasen input::-webkit-input-placeholder{color:#999}body.nanotrasen input::-moz-placeholder{color:#999}body.nanotrasen input:-ms-input-placeholder{color:#999}body.nanotrasen input::placeholder{color:#999}body.nanotrasen input::-ms-clear{display:none}body.nanotrasen svg.linegraph{overflow:hidden}body.nanotrasen div.notice{margin:8px 0;padding:4px;box-shadow:none;color:#000;font-weight:700;font-style:italic;background-color:#bb9b68;background-image:repeating-linear-gradient(-45deg,#bb9b68,#bb9b68 10px,#b1905d 0,#b1905d 20px)}body.nanotrasen div.notice .label{color:#000}body.nanotrasen div.notice .content:only-of-type{padding:0}body.nanotrasen div.notice hr{background-color:#272727}body.nanotrasen div.resize{position:fixed;bottom:0;right:0;width:0;height:0;border-style:solid;border-width:0 0 45px 45px;border-color:transparent transparent #363636;-ms-transform:rotate(1turn);transform:rotate(1turn)}body.nanotrasen section .content,body.nanotrasen section .label,body.nanotrasen section .line,body.syndicate section .content,body.syndicate section .label,body.syndicate section .line{display:table-cell;margin:0;text-align:left;vertical-align:middle;padding:3px 2px}body.nanotrasen section{display:table-row;width:100%}body.nanotrasen section:not(:first-child){padding-top:4px}body.nanotrasen section.candystripe:nth-child(even){background-color:#000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=#33000000,endColorStr=#33000000)";filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#33000000,endColorStr=#33000000);background-color:rgba(0,0,0,.2)}body.nanotrasen section .label{width:1%;padding-right:32px;white-space:nowrap;color:#8ba5c4}body.nanotrasen section .content:not(:last-child){padding-right:16px}body.nanotrasen section .line{width:100%}body.nanotrasen div.subdisplay{width:100%;margin:0}body.nanotrasen header.titlebar .close,body.nanotrasen header.titlebar .minimize{display:inline-block;position:relative;padding:7px;margin:-7px;color:#8ba5c4}body.nanotrasen header.titlebar .close:hover,body.nanotrasen header.titlebar .minimize:hover{color:#9cb2cd}body.nanotrasen header.titlebar{position:fixed;z-index:1;top:0;left:0;width:100%;height:32px;background-color:#363636;border-bottom:1px solid #161616;box-shadow:0 3px 3px rgba(0,0,0,.1)}body.nanotrasen header.titlebar .statusicon{position:absolute;top:4px;left:12px;transition:color .5s}body.nanotrasen header.titlebar .title{position:absolute;top:6px;left:46px;color:#8ba5c4;font-size:16px;white-space:nowrap}body.nanotrasen header.titlebar .minimize{position:absolute;top:6px;right:46px}body.nanotrasen header.titlebar .close{position:absolute;top:4px;right:12px}body.syndicate{background:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4wIiB2aWV3Qm94PSIwIDAgMjAwIDI4OS43NDIiIG9wYWNpdHk9Ii4zMyI+DQogIDxwYXRoIGQ9Im0gOTMuNTM3Njc3LDAgYyAtMTguMTEzMTI1LDAgLTM0LjIyMDEzMywzLjExMTY0IC00OC4zMjM0ODQsOS4zMzQzNyAtMTMuOTY1MDkyLDYuMjIxNjcgLTI0LjYxMjQ0MiwxNS4wNzExNCAtMzEuOTQwNjUxLDI2LjU0NzEgLTcuMTg5OTM5OCwxMS4zMzc4OSAtMTAuMzAxMjI2NiwyNC43NDkxMSAtMTAuMzAxMjI2Niw0MC4yMzQ3OCAwLDEwLjY0NjYyIDIuNzI1MDAyNiwyMC40NjQ2NSA4LjE3NTExMTYsMjkuNDUyNTggNS42MTUyNzcsOC45ODY4NiAxNC4wMzgyNzcsMTcuMzUyMDQgMjUuMjY4ODIxLDI1LjA5NDM2IDExLjIzMDU0NCw3LjYwNTMxIDI2LjUwNzQyMSwxNS40MTgzNSA0NS44MzA1MTQsMjMuNDM3ODIgMTkuOTgzNzQ4LDguMjk1NTcgMzQuODQ4ODQ4LDE1LjU1NDcxIDQ0LjU5Mjk5OCwyMS43NzYzOCA5Ljc0NDE0LDYuMjIyNzMgMTYuNzYxNywxMi44NTg1IDIxLjA1NTcyLDE5LjkwOTUxIDQuMjk0MDQsNy4wNTIwOCA2LjQ0MTkzLDE1Ljc2NDA4IDYuNDQxOTMsMjYuMTM0NTkgMCwxNi4xNzcwMiAtNS4yMDE5NiwyOC40ODIyMiAtMTUuNjA2NzMsMzYuOTE2ODIgLTEwLjIzOTYsOC40MzQ3IC0yNS4wMjIwMywxMi42NTIzIC00NC4zNDUxNjksMTIuNjUyMyAtMTQuMDM4MTcxLDAgLTI1LjUxNTI0NywtMS42NTk0IC0zNC40MzM2MTgsLTQuOTc3NyAtOC45MTgzNywtMy40NTY2IC0xNi4xODU1NzIsLTguNzExMyAtMjEuODAwODM5LC0xNS43NjMzIC01LjYxNTI3NywtNy4wNTIxIC0xMC4wNzQ3OTUsLTE2LjY2MDg4IC0xMy4zNzc4OTksLTI4LjgyODEyIGwgLTI0Ljc3MzE2MjYyOTM5NDUsMCAwLDU2LjgyNjMyIEMgMzMuODU2NzY5LDI4Ni4wNzYwMSA2My43NDkwNCwyODkuNzQyMDEgODkuNjc4MzgzLDI4OS43NDIwMSBjIDE2LjAyMDAyNywwIDMwLjcxOTc4NywtMS4zODI3IDQ0LjA5NzMzNywtNC4xNDc5IDEzLjU0MjcyLC0yLjkwNDMgMjUuMTA0MSwtNy40Njc2IDM0LjY4MzA5LC0xMy42ODkzIDkuNzQ0MTMsLTYuMzU5NyAxNy4zNDA0MiwtMTQuNTE5NSAyMi43OTA1MiwtMjQuNDc0OCA1LjQ1MDEsLTEwLjA5MzMyIDguMTc1MTEsLTIyLjM5OTU5IDguMTc1MTEsLTM2LjkxNjgyIDAsLTEyLjk5NzY0IC0zLjMwMjEsLTI0LjMzNTM5IC05LjkwODI5LC0zNC4wMTQ2IC02LjQ0MTA1LC05LjgxNzI1IC0xNS41MjU0NSwtMTguNTI3MDcgLTI3LjI1MTQ2LC0yNi4xMzEzMyAtMTEuNTYwODUsLTcuNjA0MjcgLTI3LjkxMDgzLC0xNS44MzE0MiAtNDkuMDUwNjYsLTI0LjY4MDIyIC0xNy41MDY0NCwtNy4xOTAxMiAtMzAuNzE5NjY4LC0xMy42ODk0OCAtMzkuNjM4MDM4LC0xOS40OTcwMSAtOC45MTgzNzEsLTUuODA3NTIgLTE4LjYwNzQ3NCwtMTIuNDM0MDkgLTI0LjA5NjUyNCwtMTguODc0MTcgLTUuNDI2MDQzLC02LjM2NjE2IC05LjY1ODgyNiwtMTUuMDcwMDMgLTkuNjU4ODI2LC0yNC44ODcyOSAwLC05LjI2NDAxIDIuMDc1NDE0LC0xNy4yMTM0NSA2LjIyMzQ1NCwtMjMuODUwMzMgMTEuMDk4Mjk4LC0xNC4zOTc0OCA0MS4yODY2MzgsLTEuNzk1MDcgNDUuMDc1NjA5LDI0LjM0NzYyIDQuODM5MzkyLDYuNzc0OTEgOC44NDkzNSwxNi4yNDcyOSAxMi4wMjk1MTUsMjguNDE1NiBsIDIwLjUzMjM0LDAgMCwtNTUuOTk5NjcgYyAtNC40NzgyNSwtNS45MjQ0OCAtOS45NTQ4OCwtMTAuNjMyMjIgLTE1LjkwODM3LC0xNC4zNzQxMSAxLjY0MDU1LDAuNDc5MDUgMy4xOTAzOSwxLjAyMzc2IDQuNjM4NjUsMS42NDAyNCA2LjQ5ODYxLDIuNjI2MDcgMTIuMTY3OTMsNy4zMjc0NyAxNy4wMDczLDE0LjEwMzQ1IDQuODM5MzksNi43NzQ5MSA4Ljg0OTM1LDE2LjI0NTY3IDEyLjAyOTUyLDI4LjQxMzk3IDAsMCA4LjQ4MTI4LC0wLjEyODk0IDguNDg5NzgsLTAuMDAyIDAuNDE3NzYsNi40MTQ5NCAtMS43NTMzOSw5LjQ1Mjg2IC00LjEyMzQyLDEyLjU2MTA0IC0yLjQxNzQsMy4xNjk3OCAtNS4xNDQ4Niw2Ljc4OTczIC00LjAwMjc4LDEzLjAwMjkgMS41MDc4Niw4LjIwMzE4IDEwLjE4MzU0LDEwLjU5NjQyIDE0LjYyMTk0LDkuMzExNTQgLTMuMzE4NDIsLTAuNDk5MTEgLTUuMzE4NTUsLTEuNzQ5NDggLTUuMzE4NTUsLTEuNzQ5NDggMCwwIDEuODc2NDYsMC45OTg2OCA1LjY1MTE3LC0xLjM1OTgxIC0zLjI3Njk1LDAuOTU1NzEgLTEwLjcwNTI5LC0wLjc5NzM4IC0xMS44MDEyNSwtNi43NjMxMyAtMC45NTc1MiwtNS4yMDg2MSAwLjk0NjU0LC03LjI5NTE0IDMuNDAxMTMsLTEwLjUxNDgyIDIuNDU0NjIsLTMuMjE5NjggNS4yODQyNiwtNi45NTgzMSA0LjY4NDMsLTE0LjQ4ODI0IGwgMC4wMDMsMC4wMDIgOC45MjY3NiwwIDAsLTU1Ljk5OTY3IGMgLTE1LjA3MTI1LC0zLjg3MTY4IC0yNy42NTMxNCwtNi4zNjA0MiAtMzcuNzQ2NzEsLTcuNDY1ODYgLTkuOTU1MzEsLTEuMTA3NTUgLTIwLjE4ODIzLC0xLjY1OTgxIC0zMC42OTY2MTMsLTEuNjU5ODEgeiBtIDcwLjMyMTYwMywxNy4zMDg5MyAwLjIzODA1LDQwLjMwNDkgYyAxLjMxODA4LDEuMjI2NjYgMi40Mzk2NSwyLjI3ODE1IDMuMzQwODEsMy4xMDYwMiA0LjgzOTM5LDYuNzc0OTEgOC44NDkzNCwxNi4yNDU2NiAxMi4wMjk1MSwyOC40MTM5NyBsIDIwLjUzMjM0LDAgMCwtNTUuOTk5NjcgYyAtNi42NzczMSwtNC41OTM4MSAtMTkuODM2NDMsLTEwLjQ3MzA5IC0zNi4xNDA3MSwtMTUuODI1MjIgeiBtIC0yOC4xMjA0OSw1LjYwNTUxIDguNTY0NzksMTcuNzE2NTUgYyAtMTEuOTcwMzcsLTYuNDY2OTcgLTEzLjg0Njc4LC05LjcxNzI2IC04LjU2NDc5LC0xNy43MTY1NSB6IG0gMjIuNzk3MDUsMCBjIDIuNzcxNSw3Ljk5OTI5IDEuNzg3NDEsMTEuMjQ5NTggLTQuNDkzNTQsMTcuNzE2NTUgbCA0LjQ5MzU0LC0xNy43MTY1NSB6IG0gMTUuMjIxOTUsMjQuMDA4NDggOC41NjQ3OSwxNy43MTY1NSBjIC0xMS45NzAzOCwtNi40NjY5NyAtMTMuODQ2NzksLTkuNzE3MjYgLTguNTY0NzksLTE3LjcxNjU1IHogbSAyMi43OTcwNCwwIGMgMi43NzE1LDcuOTk5MjkgMS43ODc0MSwxMS4yNDk1OCAtNC40OTM1NCwxNy43MTY1NSBsIDQuNDkzNTQsLTE3LjcxNjU1IHogbSAtOTkuMTEzODQsMi4yMDc2NCA4LjU2NDc5LDE3LjcxNjU1IGMgLTExLjk3MDM4MiwtNi40NjY5NyAtMTMuODQ2NzgyLC05LjcxNzI2IC04LjU2NDc5LC0xNy43MTY1NSB6IG0gMjIuNzk1NDIsMCBjIDIuNzcxNSw3Ljk5OTI5IDEuNzg3NDEsMTEuMjQ5NTggLTQuNDkzNTQsMTcuNzE2NTUgbCA0LjQ5MzU0LC0xNy43MTY1NSB6IiAvPg0KPC9zdmc+DQo8IS0tIFRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciBhIENyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24tU2hhcmVBbGlrZSA0LjAgSW50ZXJuYXRpb25hbCBMaWNlbnNlLiAtLT4NCjwhLS0gaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktc2EvNC4wLyAtLT4NCg==") no-repeat fixed 50%/70% 70%,linear-gradient(180deg,#750000 0,#340404);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff750000',endColorstr='#ff340404',GradientType=0)}body.syndicate .normal{color:#40628a}body.syndicate .good{color:#73e573}body.syndicate .average{color:#be6209}body.syndicate .bad{color:#b00e0e}body.syndicate .highlight{color:#000}body.syndicate main{display:block;margin-top:32px;padding:2px 6px 0}body.syndicate hr{height:2px;background-color:#272727;border:none}body.syndicate .hidden{display:none}body.syndicate .bar .barText,body.syndicate span.button{color:#fff;font-size:12px;font-weight:400;font-style:normal;text-decoration:none}body.syndicate .bold{font-weight:700}body.syndicate .italic{font-style:italic}body.syndicate [unselectable=on]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body.syndicate div[data-tooltip],body.syndicate span[data-tooltip]{position:relative}body.syndicate div[data-tooltip]:after,body.syndicate span[data-tooltip]:after{position:absolute;display:block;z-index:2;width:250px;padding:10px;-ms-transform:translateX(-50%);transform:translateX(-50%);visibility:hidden;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";white-space:normal;text-align:left;content:attr(data-tooltip);transition:all .5s;border:1px solid #272727;background-color:#363636}body.syndicate div[data-tooltip]:hover:after,body.syndicate span[data-tooltip]:hover:after{visibility:visible;opacity:1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"}body.syndicate div[data-tooltip].tooltip-top:after,body.syndicate span[data-tooltip].tooltip-top:after{bottom:100%;left:50%;-ms-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}body.syndicate div[data-tooltip].tooltip-top:hover:after,body.syndicate span[data-tooltip].tooltip-top:hover:after{-ms-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}body.syndicate div[data-tooltip].tooltip-bottom:after,body.syndicate span[data-tooltip].tooltip-bottom:after{top:100%;left:50%;-ms-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}body.syndicate div[data-tooltip].tooltip-bottom:hover:after,body.syndicate span[data-tooltip].tooltip-bottom:hover:after{-ms-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}body.syndicate div[data-tooltip].tooltip-left:after,body.syndicate span[data-tooltip].tooltip-left:after{top:50%;right:100%;-ms-transform:translateX(8px) translateY(-50%);transform:translateX(8px) translateY(-50%)}body.syndicate div[data-tooltip].tooltip-left:hover:after,body.syndicate span[data-tooltip].tooltip-left:hover:after{-ms-transform:translateX(-8px) translateY(-50%);transform:translateX(-8px) translateY(-50%)}body.syndicate div[data-tooltip].tooltip-right:after,body.syndicate span[data-tooltip].tooltip-right:after{top:50%;left:100%;-ms-transform:translateX(-8px) translateY(-50%);transform:translateX(-8px) translateY(-50%)}body.syndicate div[data-tooltip].tooltip-right:hover:after,body.syndicate span[data-tooltip].tooltip-right:hover:after{-ms-transform:translateX(8px) translateY(-50%);transform:translateX(8px) translateY(-50%)}body.syndicate .bar{display:inline-block;position:relative;vertical-align:middle;width:100%;height:20px;line-height:17px;padding:1px;border:1px solid #000;background:#272727}body.syndicate .bar .barText{position:absolute;top:0;right:3px}body.syndicate .bar .barFill{display:block;height:100%;transition:background-color 1s;background-color:#000}body.syndicate .bar .barFill.good{background-color:#73e573}body.syndicate .bar .barFill.average{background-color:#be6209}body.syndicate .bar .barFill.bad{background-color:#b00e0e}body.syndicate span.button{display:inline-block;vertical-align:middle;min-height:20px;line-height:17px;padding:0 5px;white-space:nowrap;border:1px solid #272727}body.syndicate span.button .fa{padding-right:2px}body.syndicate span.button.normal{transition:background-color .5s;background-color:#397439}body.syndicate span.button.normal.active:focus,body.syndicate span.button.normal.active:hover{transition:background-color .25s;background-color:#4a964a;outline:0}body.syndicate span.button.disabled{transition:background-color .5s;background-color:#363636}body.syndicate span.button.disabled.active:focus,body.syndicate span.button.disabled.active:hover{transition:background-color .25s;background-color:#545454;outline:0}body.syndicate span.button.selected{transition:background-color .5s;background-color:#9d0808}body.syndicate span.button.selected.active:focus,body.syndicate span.button.selected.active:hover{transition:background-color .25s;background-color:#ce0b0b;outline:0}body.syndicate span.button.toggle{transition:background-color .5s;background-color:#9d0808}body.syndicate span.button.toggle.active:focus,body.syndicate span.button.toggle.active:hover{transition:background-color .25s;background-color:#ce0b0b;outline:0}body.syndicate span.button.caution{transition:background-color .5s;background-color:#be6209}body.syndicate span.button.caution.active:focus,body.syndicate span.button.caution.active:hover{transition:background-color .25s;background-color:#eb790b;outline:0}body.syndicate span.button.danger{transition:background-color .5s;background-color:#9a9d00}body.syndicate span.button.danger.active:focus,body.syndicate span.button.danger.active:hover{transition:background-color .25s;background-color:#ced200;outline:0}body.syndicate span.button.gridable{width:125px;margin:2px 0}body.syndicate span.button+span:not(.button),body.syndicate span:not(.button)+span.button{margin-left:5px}body.syndicate div.display{width:100%;padding:4px;margin:6px 0;background-color:#000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=#80000000,endColorStr=#80000000)";filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#80000000,endColorStr=#80000000);background-color:rgba(0,0,0,.5);box-shadow:inset 0 0 5px rgba(0,0,0,.75)}body.syndicate div.display header,body.syndicate div.subdisplay header{display:block;position:relative;width:100%;padding:0 4px;margin-bottom:6px;color:#fff;border-bottom:2px solid #272727}body.syndicate div.display header .buttonRight,body.syndicate div.subdisplay header .buttonRight{position:absolute;bottom:6px;right:4px}body.syndicate div.display article,body.syndicate div.subdisplay article{display:table;width:100%;border-collapse:collapse}body.syndicate input{display:inline-block;vertical-align:middle;height:20px;line-height:17px;padding:0 5px;white-space:nowrap;color:#fff;background-color:#9d0808;border:1px solid #272727}body.syndicate input::-webkit-input-placeholder{color:#999}body.syndicate input::-moz-placeholder{color:#999}body.syndicate input:-ms-input-placeholder{color:#999}body.syndicate input::placeholder{color:#999}body.syndicate input::-ms-clear{display:none}body.syndicate svg.linegraph{overflow:hidden}body.syndicate div.notice{margin:8px 0;padding:4px;box-shadow:none;color:#000;font-weight:700;font-style:italic;background-color:#750000;background-image:repeating-linear-gradient(-45deg,#750000,#750000 10px,#910101 0,#910101 20px)}body.syndicate div.notice .label{color:#000}body.syndicate div.notice .content:only-of-type{padding:0}body.syndicate div.notice hr{background-color:#272727}body.syndicate div.resize{position:fixed;bottom:0;right:0;width:0;height:0;border-style:solid;border-width:0 0 45px 45px;border-color:transparent transparent #363636;-ms-transform:rotate(1turn);transform:rotate(1turn)}body.syndicate section .content,body.syndicate section .label,body.syndicate section .line{display:table-cell;margin:0;text-align:left;vertical-align:middle;padding:3px 2px}body.syndicate section{display:table-row;width:100%}body.syndicate section:not(:first-child){padding-top:4px}body.syndicate section.candystripe:nth-child(even){background-color:#000;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorStr=#33000000,endColorStr=#33000000)";filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#33000000,endColorStr=#33000000);background-color:rgba(0,0,0,.2)}body.syndicate section .label{width:1%;padding-right:32px;white-space:nowrap;color:#fff}body.syndicate section .content:not(:last-child){padding-right:16px}body.syndicate section .line{width:100%}body.syndicate div.subdisplay{width:100%;margin:0}body.syndicate header.titlebar .close,body.syndicate header.titlebar .minimize{display:inline-block;position:relative;padding:7px;margin:-7px;color:#e74242}body.syndicate header.titlebar .close:hover,body.syndicate header.titlebar .minimize:hover{color:#eb5e5e}body.syndicate header.titlebar{position:fixed;z-index:1;top:0;left:0;width:100%;height:32px;background-color:#363636;border-bottom:1px solid #161616;box-shadow:0 3px 3px rgba(0,0,0,.1)}body.syndicate header.titlebar .statusicon{position:absolute;top:4px;left:12px;transition:color .5s}body.syndicate header.titlebar .title{position:absolute;top:6px;left:46px;color:#e74242;font-size:16px;white-space:nowrap}body.syndicate header.titlebar .minimize{position:absolute;top:6px;right:46px}body.syndicate header.titlebar .close{position:absolute;top:4px;right:12px}.no-icons header.titlebar .statusicon{font-size:20px}.no-icons header.titlebar .statusicon:after{content:"O"}.no-icons header.titlebar .minimize{top:-2px;font-size:20px}.no-icons header.titlebar .minimize:after{content:"—"}.no-icons header.titlebar .close{font-size:20px}.no-icons header.titlebar .close:after{content:"X"} \ No newline at end of file diff --git a/tgui/assets/tgui.js b/tgui/assets/tgui.js index cb9ce9a5be8..cf1467ea3e4 100644 --- a/tgui/assets/tgui.js +++ b/tgui/assets/tgui.js @@ -1,13 +1,15 @@ -require=function t(e,n,r){function a(o,s){if(!n[o]){if(!e[o]){var u="function"==typeof require&&require;if(!s&&u)return u(o,!0);if(i)return i(o,!0);var c=Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var p=n[o]={exports:{}};e[o][0].call(p.exports,function(t){var n=e[o][1][t];return a(n?n:t)},p,p.exports,t,e,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o2?c[2]:void 0,l=Math.min((void 0===p?o:a(p,o))-u,o-s),f=1;for(s>u&&u+l>s&&(f=-1,u+=l-1,s+=l-1);l-- >0;)u in n?n[s]=n[u]:delete n[s],s+=f,u+=f;return n}},{76:76,79:79,80:80}],6:[function(t,e,n){"use strict";var r=t(80),a=t(76),i=t(79);e.exports=[].fill||function(t){for(var e=r(this),n=i(e.length),o=arguments,s=o.length,u=a(s>1?o[1]:void 0,n),c=s>2?o[2]:void 0,p=void 0===c?n:a(c,n);p>u;)e[u++]=t;return e}},{76:76,79:79,80:80}],7:[function(t,e,n){var r=t(78),a=t(79),i=t(76);e.exports=function(t){return function(e,n,o){var s,u=r(e),c=a(u.length),p=i(o,c);if(t&&n!=n){for(;c>p;)if(s=u[p++],s!=s)return!0}else for(;c>p;p++)if((t||p in u)&&u[p]===n)return t||p;return!t&&-1}}},{76:76,78:78,79:79}],8:[function(t,e,n){var r=t(17),a=t(34),i=t(80),o=t(79),s=t(9);e.exports=function(t){var e=1==t,n=2==t,u=3==t,c=4==t,p=6==t,l=5==t||p;return function(f,d,h){for(var m,v,g=i(f),b=a(g),y=r(d,h,3),x=o(b.length),_=0,w=e?s(f,x):n?s(f,0):void 0;x>_;_++)if((l||_ in b)&&(m=b[_],v=y(m,_,g),t))if(e)w[_]=v;else if(v)switch(t){case 3:return!0;case 5:return m;case 6:return _;case 2:w.push(m)}else if(c)return!1;return p?-1:u||c?c:w}}},{17:17,34:34,79:79,80:80,9:9}],9:[function(t,e,n){var r=t(38),a=t(36),i=t(83)("species");e.exports=function(t,e){var n;return a(t)&&(n=t.constructor,"function"!=typeof n||n!==Array&&!a(n.prototype)||(n=void 0),r(n)&&(n=n[i],null===n&&(n=void 0))),new(void 0===n?Array:n)(e)}},{36:36,38:38,83:83}],10:[function(t,e,n){var r=t(11),a=t(83)("toStringTag"),i="Arguments"==r(function(){return arguments}());e.exports=function(t){var e,n,o;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=(e=Object(t))[a])?n:i?r(e):"Object"==(o=r(e))&&"function"==typeof e.callee?"Arguments":o}},{11:11,83:83}],11:[function(t,e,n){var r={}.toString;e.exports=function(t){return r.call(t).slice(8,-1)}},{}],12:[function(t,e,n){"use strict";var r=t(46),a=t(31),i=t(60),o=t(17),s=t(69),u=t(18),c=t(27),p=t(42),l=t(44),f=t(82)("id"),d=t(30),h=t(38),m=t(65),v=t(19),g=Object.isExtensible||h,b=v?"_s":"size",y=0,x=function(t,e){if(!h(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!d(t,f)){if(!g(t))return"F";if(!e)return"E";a(t,f,++y)}return"O"+t[f]},_=function(t,e){var n,r=x(e);if("F"!==r)return t._i[r];for(n=t._f;n;n=n.n)if(n.k==e)return n};e.exports={getConstructor:function(t,e,n,a){var p=t(function(t,i){s(t,p,e),t._i=r.create(null),t._f=void 0,t._l=void 0,t[b]=0,void 0!=i&&c(i,n,t[a],t)});return i(p.prototype,{clear:function(){for(var t=this,e=t._i,n=t._f;n;n=n.n)n.r=!0,n.p&&(n.p=n.p.n=void 0),delete e[n.i];t._f=t._l=void 0,t[b]=0},"delete":function(t){var e=this,n=_(e,t);if(n){var r=n.n,a=n.p;delete e._i[n.i],n.r=!0,a&&(a.n=r),r&&(r.p=a),e._f==n&&(e._f=r),e._l==n&&(e._l=a),e[b]--}return!!n},forEach:function(t){for(var e,n=o(t,arguments.length>1?arguments[1]:void 0,3);e=e?e.n:this._f;)for(n(e.v,e.k,this);e&&e.r;)e=e.p},has:function(t){return!!_(this,t)}}),v&&r.setDesc(p.prototype,"size",{get:function(){return u(this[b])}}),p},def:function(t,e,n){var r,a,i=_(t,e);return i?i.v=n:(t._l=i={i:a=x(e,!0),k:e,v:n,p:r=t._l,n:void 0,r:!1},t._f||(t._f=i),r&&(r.n=i),t[b]++,"F"!==a&&(t._i[a]=i)),t},getEntry:_,setStrong:function(t,e,n){p(t,e,function(t,e){this._t=t,this._k=e,this._l=void 0},function(){for(var t=this,e=t._k,n=t._l;n&&n.r;)n=n.p;return t._t&&(t._l=n=n?n.n:t._t._f)?"keys"==e?l(0,n.k):"values"==e?l(0,n.v):l(0,[n.k,n.v]):(t._t=void 0,l(1))},n?"entries":"values",!n,!0),m(e)}}},{17:17,18:18,19:19,27:27,30:30,31:31,38:38,42:42,44:44,46:46,60:60,65:65,69:69,82:82}],13:[function(t,e,n){var r=t(27),a=t(10);e.exports=function(t){return function(){if(a(this)!=t)throw TypeError(t+"#toJSON isn't generic");var e=[];return r(this,!1,e.push,e),e}}},{10:10,27:27}],14:[function(t,e,n){"use strict";var r=t(31),a=t(60),i=t(4),o=t(38),s=t(69),u=t(27),c=t(8),p=t(30),l=t(82)("weak"),f=Object.isExtensible||o,d=c(5),h=c(6),m=0,v=function(t){return t._l||(t._l=new g)},g=function(){this.a=[]},b=function(t,e){return d(t.a,function(t){return t[0]===e})};g.prototype={get:function(t){var e=b(this,t);return e?e[1]:void 0},has:function(t){return!!b(this,t)},set:function(t,e){var n=b(this,t);n?n[1]=e:this.a.push([t,e])},"delete":function(t){var e=h(this.a,function(e){return e[0]===t});return~e&&this.a.splice(e,1),!!~e}},e.exports={getConstructor:function(t,e,n,r){var i=t(function(t,a){s(t,i,e),t._i=m++,t._l=void 0,void 0!=a&&u(a,n,t[r],t)});return a(i.prototype,{"delete":function(t){return o(t)?f(t)?p(t,l)&&p(t[l],this._i)&&delete t[l][this._i]:v(this)["delete"](t):!1},has:function(t){return o(t)?f(t)?p(t,l)&&p(t[l],this._i):v(this).has(t):!1}}),i},def:function(t,e,n){return f(i(e))?(p(e,l)||r(e,l,{}),e[l][t._i]=n):v(t).set(e,n),t},frozenStore:v,WEAK:l}},{27:27,30:30,31:31,38:38,4:4,60:60,69:69,8:8,82:82}],15:[function(t,e,n){"use strict";var r=t(29),a=t(22),i=t(61),o=t(60),s=t(27),u=t(69),c=t(38),p=t(24),l=t(43),f=t(66);e.exports=function(t,e,n,d,h,m){var v=r[t],g=v,b=h?"set":"add",y=g&&g.prototype,x={},_=function(t){var e=y[t];i(y,t,"delete"==t?function(t){return m&&!c(t)?!1:e.call(this,0===t?0:t)}:"has"==t?function(t){return m&&!c(t)?!1:e.call(this,0===t?0:t)}:"get"==t?function(t){return m&&!c(t)?void 0:e.call(this,0===t?0:t)}:"add"==t?function(t){return e.call(this,0===t?0:t),this}:function(t,n){return e.call(this,0===t?0:t,n),this})};if("function"==typeof g&&(m||y.forEach&&!p(function(){(new g).entries().next()}))){var w,k=new g,E=k[b](m?{}:-0,1)!=k,S=p(function(){k.has(1)}),O=l(function(t){new g(t)});O||(g=e(function(e,n){u(e,g,t);var r=new v;return void 0!=n&&s(n,h,r[b],r),r}),g.prototype=y,y.constructor=g),m||k.forEach(function(t,e){w=1/e===-(1/0)}),(S||w)&&(_("delete"),_("has"),h&&_("get")),(w||E)&&_(b),m&&y.clear&&delete y.clear}else g=d.getConstructor(e,t,h,b),o(g.prototype,n);return f(g,t),x[t]=g,a(a.G+a.W+a.F*(g!=v),x),m||d.setStrong(g,t,h),g}},{22:22,24:24,27:27,29:29,38:38,43:43,60:60,61:61,66:66,69:69}],16:[function(t,e,n){var r=e.exports={version:"1.2.6"};"number"==typeof __e&&(__e=r)},{}],17:[function(t,e,n){var r=t(2);e.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,a){return t.call(e,n,r,a)}}return function(){return t.apply(e,arguments)}}},{2:2}],18:[function(t,e,n){e.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},{}],19:[function(t,e,n){e.exports=!t(24)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},{24:24}],20:[function(t,e,n){var r=t(38),a=t(29).document,i=r(a)&&r(a.createElement);e.exports=function(t){return i?a.createElement(t):{}}},{29:29,38:38}],21:[function(t,e,n){var r=t(46);e.exports=function(t){var e=r.getKeys(t),n=r.getSymbols;if(n)for(var a,i=n(t),o=r.isEnum,s=0;i.length>s;)o.call(t,a=i[s++])&&e.push(a);return e}},{46:46}],22:[function(t,e,n){var r=t(29),a=t(16),i=t(31),o=t(61),s=t(17),u="prototype",c=function(t,e,n){var p,l,f,d,h=t&c.F,m=t&c.G,v=t&c.S,g=t&c.P,b=t&c.B,y=m?r:v?r[e]||(r[e]={}):(r[e]||{})[u],x=m?a:a[e]||(a[e]={}),_=x[u]||(x[u]={});m&&(n=e);for(p in n)l=!h&&y&&p in y,f=(l?y:n)[p],d=b&&l?s(f,r):g&&"function"==typeof f?s(Function.call,f):f,y&&!l&&o(y,p,f),x[p]!=f&&i(x,p,d),g&&_[p]!=f&&(_[p]=f)};r.core=a,c.F=1,c.G=2,c.S=4,c.P=8,c.B=16,c.W=32,e.exports=c},{16:16,17:17,29:29,31:31,61:61}],23:[function(t,e,n){var r=t(83)("match");e.exports=function(t){var e=/./;try{"/./"[t](e)}catch(n){try{return e[r]=!1,!"/./"[t](e)}catch(a){}}return!0}},{83:83}],24:[function(t,e,n){e.exports=function(t){try{return!!t()}catch(e){return!0}}},{}],25:[function(t,e,n){"use strict";var r=t(31),a=t(61),i=t(24),o=t(18),s=t(83);e.exports=function(t,e,n){var u=s(t),c=""[t];i(function(){var e={};return e[u]=function(){return 7},7!=""[t](e)})&&(a(String.prototype,t,n(o,u,c)),r(RegExp.prototype,u,2==e?function(t,e){return c.call(t,this,e)}:function(t){return c.call(t,this)}))}},{18:18,24:24,31:31,61:61,83:83}],26:[function(t,e,n){"use strict";var r=t(4);e.exports=function(){var t=r(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},{4:4}],27:[function(t,e,n){var r=t(17),a=t(40),i=t(35),o=t(4),s=t(79),u=t(84);e.exports=function(t,e,n,c){var p,l,f,d=u(t),h=r(n,c,e?2:1),m=0;if("function"!=typeof d)throw TypeError(t+" is not iterable!");if(i(d))for(p=s(t.length);p>m;m++)e?h(o(l=t[m])[0],l[1]):h(t[m]);else for(f=d.call(t);!(l=f.next()).done;)a(f,h,l.value,e)}},{17:17,35:35,4:4,40:40,79:79,84:84}],28:[function(t,e,n){var r=t(78),a=t(46).getNames,i={}.toString,o="object"==typeof window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],s=function(t){try{return a(t)}catch(e){return o.slice()}};e.exports.get=function(t){return o&&"[object Window]"==i.call(t)?s(t):a(r(t))}},{46:46,78:78}],29:[function(t,e,n){var r=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},{}],30:[function(t,e,n){var r={}.hasOwnProperty;e.exports=function(t,e){return r.call(t,e)}},{}],31:[function(t,e,n){var r=t(46),a=t(59);e.exports=t(19)?function(t,e,n){return r.setDesc(t,e,a(1,n))}:function(t,e,n){return t[e]=n,t}},{19:19,46:46,59:59}],32:[function(t,e,n){e.exports=t(29).document&&document.documentElement},{29:29}],33:[function(t,e,n){e.exports=function(t,e,n){var r=void 0===n;switch(e.length){case 0:return r?t():t.call(n);case 1:return r?t(e[0]):t.call(n,e[0]);case 2:return r?t(e[0],e[1]):t.call(n,e[0],e[1]);case 3:return r?t(e[0],e[1],e[2]):t.call(n,e[0],e[1],e[2]);case 4:return r?t(e[0],e[1],e[2],e[3]):t.call(n,e[0],e[1],e[2],e[3])}return t.apply(n,e)}},{}],34:[function(t,e,n){var r=t(11);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},{11:11}],35:[function(t,e,n){var r=t(45),a=t(83)("iterator"),i=Array.prototype;e.exports=function(t){return void 0!==t&&(r.Array===t||i[a]===t)}},{45:45,83:83}],36:[function(t,e,n){var r=t(11);e.exports=Array.isArray||function(t){return"Array"==r(t)}},{11:11}],37:[function(t,e,n){var r=t(38),a=Math.floor;e.exports=function(t){return!r(t)&&isFinite(t)&&a(t)===t}},{38:38}],38:[function(t,e,n){e.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},{}],39:[function(t,e,n){var r=t(38),a=t(11),i=t(83)("match");e.exports=function(t){var e;return r(t)&&(void 0!==(e=t[i])?!!e:"RegExp"==a(t))}},{11:11,38:38,83:83}],40:[function(t,e,n){var r=t(4);e.exports=function(t,e,n,a){try{return a?e(r(n)[0],n[1]):e(n)}catch(i){var o=t["return"];throw void 0!==o&&r(o.call(t)),i}}},{4:4}],41:[function(t,e,n){"use strict";var r=t(46),a=t(59),i=t(66),o={};t(31)(o,t(83)("iterator"),function(){return this}),e.exports=function(t,e,n){t.prototype=r.create(o,{next:a(1,n)}),i(t,e+" Iterator")}},{31:31,46:46,59:59,66:66,83:83}],42:[function(t,e,n){"use strict";var r=t(48),a=t(22),i=t(61),o=t(31),s=t(30),u=t(45),c=t(41),p=t(66),l=t(46).getProto,f=t(83)("iterator"),d=!([].keys&&"next"in[].keys()),h="@@iterator",m="keys",v="values",g=function(){return this};e.exports=function(t,e,n,b,y,x,_){c(n,e,b);var w,k,E=function(t){if(!d&&t in A)return A[t];switch(t){case m:return function(){return new n(this,t)};case v:return function(){return new n(this,t)}}return function(){return new n(this,t)}},S=e+" Iterator",O=y==v,C=!1,A=t.prototype,P=A[f]||A[h]||y&&A[y],T=P||E(y);if(P){var j=l(T.call(new t));p(j,S,!0),!r&&s(A,h)&&o(j,f,g),O&&P.name!==v&&(C=!0,T=function(){return P.call(this)})}if(r&&!_||!d&&!C&&A[f]||o(A,f,T),u[e]=T,u[S]=g,y)if(w={values:O?T:E(v),keys:x?T:E(m),entries:O?E("entries"):T},_)for(k in w)k in A||i(A,k,w[k]);else a(a.P+a.F*(d||C),e,w);return w}},{22:22,30:30,31:31,41:41,45:45,46:46,48:48,61:61,66:66,83:83}],43:[function(t,e,n){var r=t(83)("iterator"),a=!1;try{var i=[7][r]();i["return"]=function(){a=!0},Array.from(i,function(){throw 2})}catch(o){}e.exports=function(t,e){if(!e&&!a)return!1;var n=!1;try{var i=[7],o=i[r]();o.next=function(){return{done:n=!0}},i[r]=function(){return o},t(i)}catch(s){}return n}},{83:83}],44:[function(t,e,n){e.exports=function(t,e){return{value:e,done:!!t}}},{}],45:[function(t,e,n){e.exports={}},{}],46:[function(t,e,n){var r=Object;e.exports={create:r.create,getProto:r.getPrototypeOf,isEnum:{}.propertyIsEnumerable,getDesc:r.getOwnPropertyDescriptor,setDesc:r.defineProperty,setDescs:r.defineProperties,getKeys:r.keys,getNames:r.getOwnPropertyNames,getSymbols:r.getOwnPropertySymbols,each:[].forEach}},{}],47:[function(t,e,n){var r=t(46),a=t(78);e.exports=function(t,e){for(var n,i=a(t),o=r.getKeys(i),s=o.length,u=0;s>u;)if(i[n=o[u++]]===e)return n}},{46:46,78:78}],48:[function(t,e,n){e.exports=!1},{}],49:[function(t,e,n){e.exports=Math.expm1||function(t){return 0==(t=+t)?t:t>-1e-6&&1e-6>t?t+t*t/2:Math.exp(t)-1}},{}],50:[function(t,e,n){e.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&1e-8>t?t-t*t/2:Math.log(1+t)}},{}],51:[function(t,e,n){e.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:0>t?-1:1}},{}],52:[function(t,e,n){var r,a,i,o=t(29),s=t(75).set,u=o.MutationObserver||o.WebKitMutationObserver,c=o.process,p=o.Promise,l="process"==t(11)(c),f=function(){var t,e,n;for(l&&(t=c.domain)&&(c.domain=null,t.exit());r;)e=r.domain,n=r.fn,e&&e.enter(),n(),e&&e.exit(),r=r.next;a=void 0,t&&t.enter()};if(l)i=function(){c.nextTick(f)};else if(u){var d=1,h=document.createTextNode("");new u(f).observe(h,{characterData:!0}),i=function(){h.data=d=-d}}else i=p&&p.resolve?function(){p.resolve().then(f)}:function(){s.call(o,f)};e.exports=function(t){var e={fn:t,next:void 0,domain:l&&c.domain};a&&(a.next=e),r||(r=e,i()),a=e}},{11:11,29:29,75:75}],53:[function(t,e,n){var r=t(46),a=t(80),i=t(34);e.exports=t(24)(function(){var t=Object.assign,e={},n={},r=Symbol(),a="abcdefghijklmnopqrst";return e[r]=7,a.split("").forEach(function(t){n[t]=t}),7!=t({},e)[r]||Object.keys(t({},n)).join("")!=a})?function(t,e){for(var n=a(t),o=arguments,s=o.length,u=1,c=r.getKeys,p=r.getSymbols,l=r.isEnum;s>u;)for(var f,d=i(o[u++]),h=p?c(d).concat(p(d)):c(d),m=h.length,v=0;m>v;)l.call(d,f=h[v++])&&(n[f]=d[f]);return n}:Object.assign},{24:24,34:34,46:46,80:80}],54:[function(t,e,n){var r=t(22),a=t(16),i=t(24);e.exports=function(t,e){var n=(a.Object||{})[t]||Object[t],o={};o[t]=e(n),r(r.S+r.F*i(function(){n(1)}),"Object",o)}},{16:16,22:22,24:24}],55:[function(t,e,n){var r=t(46),a=t(78),i=r.isEnum;e.exports=function(t){return function(e){for(var n,o=a(e),s=r.getKeys(o),u=s.length,c=0,p=[];u>c;)i.call(o,n=s[c++])&&p.push(t?[n,o[n]]:o[n]);return p}}},{46:46,78:78}],56:[function(t,e,n){var r=t(46),a=t(4),i=t(29).Reflect;e.exports=i&&i.ownKeys||function(t){var e=r.getNames(a(t)),n=r.getSymbols;return n?e.concat(n(t)):e}},{29:29,4:4,46:46}],57:[function(t,e,n){"use strict";var r=t(58),a=t(33),i=t(2);e.exports=function(){for(var t=i(this),e=arguments.length,n=Array(e),o=0,s=r._,u=!1;e>o;)(n[o]=arguments[o++])===s&&(u=!0);return function(){var r,i=this,o=arguments,c=o.length,p=0,l=0;if(!u&&!c)return a(t,n,i);if(r=n.slice(),u)for(;e>p;p++)r[p]===s&&(r[p]=o[l++]);for(;c>l;)r.push(o[l++]);return a(t,r,i)}}},{2:2,33:33,58:58}],58:[function(t,e,n){e.exports=t(29)},{29:29}],59:[function(t,e,n){e.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},{}],60:[function(t,e,n){var r=t(61);e.exports=function(t,e){for(var n in e)r(t,n,e[n]);return t}},{61:61}],61:[function(t,e,n){var r=t(29),a=t(31),i=t(82)("src"),o="toString",s=Function[o],u=(""+s).split(o);t(16).inspectSource=function(t){return s.call(t)},(e.exports=function(t,e,n,o){"function"==typeof n&&(n.hasOwnProperty(i)||a(n,i,t[e]?""+t[e]:u.join(e+"")),n.hasOwnProperty("name")||a(n,"name",e)),t===r?t[e]=n:(o||delete t[e],a(t,e,n))})(Function.prototype,o,function(){return"function"==typeof this&&this[i]||s.call(this)})},{16:16,29:29,31:31,82:82}],62:[function(t,e,n){e.exports=function(t,e){var n=e===Object(e)?function(t){return e[t]}:e;return function(e){return(e+"").replace(t,n)}}},{}],63:[function(t,e,n){e.exports=Object.is||function(t,e){return t===e?0!==t||1/t===1/e:t!=t&&e!=e}},{}],64:[function(t,e,n){var r=t(46).getDesc,a=t(38),i=t(4),o=function(t,e){if(i(t),!a(e)&&null!==e)throw TypeError(e+": can't set as prototype!")};e.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(e,n,a){try{a=t(17)(Function.call,r(Object.prototype,"__proto__").set,2),a(e,[]),n=!(e instanceof Array)}catch(i){n=!0}return function(t,e){return o(t,e),n?t.__proto__=e:a(t,e),t}}({},!1):void 0),check:o}},{17:17,38:38,4:4,46:46}],65:[function(t,e,n){"use strict";var r=t(29),a=t(46),i=t(19),o=t(83)("species");e.exports=function(t){var e=r[t];i&&e&&!e[o]&&a.setDesc(e,o,{configurable:!0,get:function(){return this}})}},{19:19,29:29,46:46,83:83}],66:[function(t,e,n){var r=t(46).setDesc,a=t(30),i=t(83)("toStringTag");e.exports=function(t,e,n){t&&!a(t=n?t:t.prototype,i)&&r(t,i,{configurable:!0,value:e})}},{30:30,46:46,83:83}],67:[function(t,e,n){var r=t(29),a="__core-js_shared__",i=r[a]||(r[a]={});e.exports=function(t){return i[t]||(i[t]={})}},{29:29}],68:[function(t,e,n){var r=t(4),a=t(2),i=t(83)("species");e.exports=function(t,e){var n,o=r(t).constructor;return void 0===o||void 0==(n=r(o)[i])?e:a(n)}},{2:2,4:4,83:83}],69:[function(t,e,n){e.exports=function(t,e,n){if(!(t instanceof e))throw TypeError(n+": use the 'new' operator!");return t}},{}],70:[function(t,e,n){var r=t(77),a=t(18);e.exports=function(t){return function(e,n){var i,o,s=a(e)+"",u=r(n),c=s.length;return 0>u||u>=c?t?"":void 0:(i=s.charCodeAt(u),55296>i||i>56319||u+1===c||(o=s.charCodeAt(u+1))<56320||o>57343?t?s.charAt(u):i:t?s.slice(u,u+2):(i-55296<<10)+(o-56320)+65536)}}},{18:18,77:77}],71:[function(t,e,n){var r=t(39),a=t(18);e.exports=function(t,e,n){if(r(e))throw TypeError("String#"+n+" doesn't accept regex!");return a(t)+""}},{18:18,39:39}],72:[function(t,e,n){var r=t(79),a=t(73),i=t(18);e.exports=function(t,e,n,o){var s=i(t)+"",u=s.length,c=void 0===n?" ":n+"",p=r(e);if(u>=p)return s;""==c&&(c=" ");var l=p-u,f=a.call(c,Math.ceil(l/c.length));return f.length>l&&(f=f.slice(0,l)),o?f+s:s+f}},{18:18,73:73,79:79}],73:[function(t,e,n){"use strict";var r=t(77),a=t(18);e.exports=function(t){var e=a(this)+"",n="",i=r(t);if(0>i||i==1/0)throw RangeError("Count can't be negative");for(;i>0;(i>>>=1)&&(e+=e))1&i&&(n+=e);return n}},{18:18,77:77}],74:[function(t,e,n){var r=t(22),a=t(18),i=t(24),o=" \n\x0B\f\r   ᠎              \u2028\u2029\ufeff",s="["+o+"]",u="​…",c=RegExp("^"+s+s+"*"),p=RegExp(s+s+"*$"),l=function(t,e){var n={};n[t]=e(f),r(r.P+r.F*i(function(){return!!o[t]()||u[t]()!=u}),"String",n)},f=l.trim=function(t,e){return t=a(t)+"",1&e&&(t=t.replace(c,"")),2&e&&(t=t.replace(p,"")),t};e.exports=l},{18:18,22:22,24:24}],75:[function(t,e,n){var r,a,i,o=t(17),s=t(33),u=t(32),c=t(20),p=t(29),l=p.process,f=p.setImmediate,d=p.clearImmediate,h=p.MessageChannel,m=0,v={},g="onreadystatechange",b=function(){var t=+this;if(v.hasOwnProperty(t)){var e=v[t];delete v[t],e()}},y=function(t){b.call(t.data)};f&&d||(f=function(t){for(var e=[],n=1;arguments.length>n;)e.push(arguments[n++]);return v[++m]=function(){s("function"==typeof t?t:Function(t),e)},r(m),m},d=function(t){delete v[t]},"process"==t(11)(l)?r=function(t){l.nextTick(o(b,t,1))}:h?(a=new h,i=a.port2,a.port1.onmessage=y,r=o(i.postMessage,i,1)):p.addEventListener&&"function"==typeof postMessage&&!p.importScripts?(r=function(t){p.postMessage(t+"","*")},p.addEventListener("message",y,!1)):r=g in c("script")?function(t){u.appendChild(c("script"))[g]=function(){u.removeChild(this),b.call(t)}}:function(t){setTimeout(o(b,t,1),0)}),e.exports={set:f,clear:d}},{11:11,17:17,20:20,29:29,32:32,33:33}],76:[function(t,e,n){var r=t(77),a=Math.max,i=Math.min;e.exports=function(t,e){return t=r(t),0>t?a(t+e,0):i(t,e)}},{77:77}],77:[function(t,e,n){var r=Math.ceil,a=Math.floor;e.exports=function(t){return isNaN(t=+t)?0:(t>0?a:r)(t)}},{}],78:[function(t,e,n){var r=t(34),a=t(18);e.exports=function(t){return r(a(t))}},{18:18,34:34}],79:[function(t,e,n){var r=t(77),a=Math.min;e.exports=function(t){return t>0?a(r(t),9007199254740991):0}},{77:77}],80:[function(t,e,n){var r=t(18);e.exports=function(t){return Object(r(t))}},{18:18}],81:[function(t,e,n){var r=t(38);e.exports=function(t,e){if(!r(t))return t;var n,a;if(e&&"function"==typeof(n=t.toString)&&!r(a=n.call(t)))return a;if("function"==typeof(n=t.valueOf)&&!r(a=n.call(t)))return a;if(!e&&"function"==typeof(n=t.toString)&&!r(a=n.call(t)))return a;throw TypeError("Can't convert object to primitive value")}},{38:38}],82:[function(t,e,n){var r=0,a=Math.random();e.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+a).toString(36))}},{}],83:[function(t,e,n){var r=t(67)("wks"),a=t(82),i=t(29).Symbol;e.exports=function(t){return r[t]||(r[t]=i&&i[t]||(i||a)("Symbol."+t))}},{29:29,67:67,82:82}],84:[function(t,e,n){var r=t(10),a=t(83)("iterator"),i=t(45);e.exports=t(16).getIteratorMethod=function(t){return void 0!=t?t[a]||t["@@iterator"]||i[r(t)]:void 0}},{10:10,16:16,45:45,83:83}],85:[function(t,e,n){"use strict";var r,a=t(46),i=t(22),o=t(19),s=t(59),u=t(32),c=t(20),p=t(30),l=t(11),f=t(33),d=t(24),h=t(4),m=t(2),v=t(38),g=t(80),b=t(78),y=t(77),x=t(76),_=t(79),w=t(34),k=t(82)("__proto__"),E=t(8),S=t(7)(!1),O=Object.prototype,C=Array.prototype,A=C.slice,P=C.join,T=a.setDesc,j=a.getDesc,M=a.setDescs,L={};o||(r=!d(function(){return 7!=T(c("div"),"a",{get:function(){return 7}}).a}),a.setDesc=function(t,e,n){if(r)try{return T(t,e,n)}catch(a){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(h(t)[e]=n.value),t},a.getDesc=function(t,e){if(r)try{return j(t,e)}catch(n){}return p(t,e)?s(!O.propertyIsEnumerable.call(t,e),t[e]):void 0},a.setDescs=M=function(t,e){h(t);for(var n,r=a.getKeys(e),i=r.length,o=0;i>o;)a.setDesc(t,n=r[o++],e[n]);return t}),i(i.S+i.F*!o,"Object",{getOwnPropertyDescriptor:a.getDesc,defineProperty:a.setDesc,defineProperties:M});var F="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(","),N=F.concat("length","prototype"),R=F.length,D=function(){var t,e=c("iframe"),n=R,r=">";for(e.style.display="none",u.appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write(" + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + + Welcome to computer configuration utility. Please consult your system administrator if you have any questions about your device.
    + + {{#if data.battery}} + + Active + + + {{data.battery.max}} + + + {{Math.round(adata.battery.charge)}}/{{adata.battery.max}} + + {{else}} + + Not Available + + {{/if}} + + + {{data.power_usage}}W + + + + + + {{Math.round(adata.disk_used)}}GQ/{{adata.disk_size}}GQ + + + + + + {{#each data.hardware}} + + + {{desc}}
    + + + + {{enabled ? "Enabled" : "Disabled"}} + + + + {{powerusage}}W + + {{#if !critical}} + + + {{enabled ? "On" : "Off"}} + + + {{/if}} +

    + {{/each}} +
    +
    \ No newline at end of file diff --git a/tgui/src/interfaces/ntnet_chat.ract b/tgui/src/interfaces/ntnet_chat.ract new file mode 100644 index 00000000000..03be35d39de --- /dev/null +++ b/tgui/src/interfaces/ntnet_chat.ract @@ -0,0 +1,103 @@ + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + + {{#if data.adminmode}} +

    ADMINISTRATIVE MODE

    + {{/if}} + + {{#if data.title}} +
    + Current channel: +
    +
    + {{data.title}} +
    +
    + Operator access: +
    +
    + {{#if data.is_operator}} + Enabled + {{else}} + Disabled + {{/if}} +
    +
    + Controls: +
    +
    + +
    Send message +
    Change nickname +
    Toggle administration mode +
    Leave channel +
    Save log to local drive + {{#if data.is_operator}} +
    Rename channel +
    Set password +
    Delete channel + {{/if}} +
    +
    + Chat Window +
    +
    +
    + {{#each data.messages}} + {{msg}}
    + {{/each}} +
    +
    +
    + Connected Users
    + {{#each data.clients}} + {{name}}
    + {{/each}} + {{else}} + Controls: + +
    Change nickname +
    New Channel +
    Toggle administration mode +
    + Available channels: + + {{#each data.all_channels}} +
    {{chan}}
    + {{/each}} +
    + {{/if}} +
    \ No newline at end of file diff --git a/tgui/src/interfaces/ntnet_dos.ract b/tgui/src/interfaces/ntnet_dos.ract new file mode 100644 index 00000000000..afff153814d --- /dev/null +++ b/tgui/src/interfaces/ntnet_dos.ract @@ -0,0 +1,58 @@ + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + + {{#if data.error}} + ##SYSTEM ERROR: {{data.error}}RESET + {{elseif data.target}} + ##DoS traffic generator active. Tx: {{data.speed}}GQ/s
    + {{#each data.dos_strings}} + {{nums}}
    + {{/each}} + ABORT + {{else}} + ##DoS traffic generator ready. Select target device.
    + {{#if data.focus}} + Targeted device ID: {{data.focus}} + {{else}} + Targeted device ID: None + {{/if}} + EXECUTE
    + Detected devices on network:
    + {{#each data.relays}} + {{id}} + {{/each}} + {{/if}} +
    diff --git a/tgui/src/interfaces/ntnet_downloader.ract b/tgui/src/interfaces/ntnet_downloader.ract new file mode 100644 index 00000000000..da100b50e5a --- /dev/null +++ b/tgui/src/interfaces/ntnet_downloader.ract @@ -0,0 +1,119 @@ + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + + + Welcome to software download utility. Please select which software you wish to download.
    + {{#if data.error}} + + + {{data.error}} + + + + RESET + + + + {{else}} + {{#if data.downloadname}} + + Please wait... + + {{data.downloadname}} + + + {{data.downloaddesc}} + + + {{data.downloadcompletion}}GQ / {{data.downloadsize}}GQ + + + {{data.downloadspeed}} GQ/s + + + {{Math.round(adata.downloadcompletion)}}/{{adata.downloadsize}} + + + {{/if}} + {{/if}} + {{#if !data.downloadname}} + {{#if !data.error}} + + + {{Math.round(adata.disk_used)}}GQ/{{adata.disk_size}}GQ + + {{#each data.downloadable_programs}} + + {{filename}} ({{size}} GQ) + + + {{filedesc}} + + + {{fileinfo}} + + + + DOWNLOAD + + + {{/each}} + + {{#if data.hackedavailable}} + + Please note that NanoTrasen does not recommend download of software from non-official servers. + {{#each data.hacked_programs}} + + {{filename}} ({{size}} GQ) + + + {{filedesc}} + + + {{fileinfo}} + + + + DOWNLOAD + + + {{/each}} + + {{/if}} + {{/if}} + {{/if}} +


    NTOS v2.0.4b Copyright NanoTrasen 2557 - 2559 +
    \ No newline at end of file diff --git a/tgui/src/interfaces/ntnet_monitor.ract b/tgui/src/interfaces/ntnet_monitor.ract new file mode 100644 index 00000000000..67943650afe --- /dev/null +++ b/tgui/src/interfaces/ntnet_monitor.ract @@ -0,0 +1,126 @@ + + + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + + + + + {{data.ntnetrelays}} + + {{#if data.ntnetrelays}} + + {{data.ntnetstatus ? "ENABLED" : "DISABLED"}} + + + + + TOGGLE + + +

    + Caution - Disabling wireless transmitters when using wireless device may prevent you from re-enabling them again! + {{else}} +

    Wireless coverage unavailable, no relays are connected.

    + {{/if}} +
    + + + + + + + + + + +
    PROTOCOL + STATUS + CONTROL +
    Software Downloads + {{data.config_softwaredownload ? 'ENABLED' : 'DISABLED'}} + TOGGLE +
    Peer to Peer Traffic + {{data.config_peertopeer ? 'ENABLED' : 'DISABLED'}} + TOGGLE +
    Communication Systems + {{data.config_communication ? 'ENABLED' : 'DISABLED'}} + TOGGLE +
    Remote System Control + {{data.config_systemcontrol ? 'ENABLED' : 'DISABLED'}} + TOGGLE +
    +
    + + + + {{#if data.idsalarm}} + +

    NETWORK INCURSION DETECTED

    +
    + An abnormal activity has been detected in the network. Please verify system logs for more information + + {{/if}} + + {{data.idsstatus ? 'ENABLED' : 'DISABLED'}} + + + + {{data.ntnetmaxlogs}} + + + + + +
    RESET IDS +
    TOGGLE IDS +
    SET LOG LIMIT +
    PURGE LOGS +
    + + +
    +
    +
    + {{#each data.ntnetlogs}} + {{entry}}
    + {{/each}} +
    +
    +
    +
    + + \ No newline at end of file diff --git a/tgui/src/interfaces/ntnet_relay.ract b/tgui/src/interfaces/ntnet_relay.ract new file mode 100644 index 00000000000..592d235164e --- /dev/null +++ b/tgui/src/interfaces/ntnet_relay.ract @@ -0,0 +1,22 @@ + + {{#if data.dos_crashed}} +

    NETWORK BUFFERS OVERLOADED

    +

    Overload Recovery Mode

    + This system is suffering temporary outage due to overflow of traffic buffers. Until buffered traffic is processed, all further requests will be dropped. Frequent occurences of this error may indicate insufficient hardware capacity of your network. Please contact your network planning department for instructions on how to resolve this issue. +

    ADMINISTRATIVE OVERRIDE

    + CAUTION - Data loss may occur + + Purge buffered traffic + + {{else}} + + + {{data.enabled ? "ENABLED" : "DISABLED"}} + + + + + {{data.dos_overload}} / {{data.dos_capacity}} GQ + + {{/if}} +
    \ No newline at end of file diff --git a/tgui/src/interfaces/ntnet_transfer.ract b/tgui/src/interfaces/ntnet_transfer.ract new file mode 100644 index 00000000000..f0c499f8de0 --- /dev/null +++ b/tgui/src/interfaces/ntnet_transfer.ract @@ -0,0 +1,136 @@ + + + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + + {{#if data.error}} +
    +

    An error has occured during operation...

    + Additional information: {{data.error}}
    + Clear +
    + {{elseif data.downloading}} +

    Download in progress...

    +
    + Downloaded file: +
    +
    + {{data.download_name}} +
    +
    + Download progress: +
    +
    + {{data.download_progress}} / {{data.download_size}} GQ +
    +
    + Transfer speed: +
    +
    + {{data.download_netspeed}}GQ/s +
    +
    + Controls: +
    +
    + Abort download +
    + {{elseif data.uploading}} +

    Server enabled

    +
    + Connected clients: +
    +
    + {{data.upload_clients}} +
    +
    + Provided file: +
    +
    + {{data.upload_filename}} +
    +
    + Server password: +
    +
    + {{#if data.haspassword}} + ENABLED + {{else}} + DISABLED + {{/if}} +
    +
    + Commands: +
    +
    + Set password + Exit server +
    + {{elseif data.upload_filelist}} +

    File transfer server ready. Select file to upload:

    + +
    File nameFile sizeControls + {{#each data.upload_filelist}} +
    {{filename}} + {{size}}GQ + Select + {{/each}} +
    +
    + Set password + Return + {{else}} +

    Available files:

    +
    Server UIDFile NameFile SizePassword ProtectionOperations + {{#each data.servers}} +
    {{uid}} + {{filename}} + {{size}}GQ + {{#if haspassword}} + Enabled + {{/if}} + {{#if !haspassword}} + Disabled + {{/if}} + + Download + {{/each}} +
    +
    + Send file + {{/if}} + + \ No newline at end of file diff --git a/tgui/src/interfaces/power_monitor_prog.ract b/tgui/src/interfaces/power_monitor_prog.ract new file mode 100644 index 00000000000..ba40f27ec07 --- /dev/null +++ b/tgui/src/interfaces/power_monitor_prog.ract @@ -0,0 +1,112 @@ + + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + + + {{#if config.fancy}} + + {{else}} + + {{data.supply}} W + + + {{data.demand}} W + + {{/if}} + + + +
    Area
    +
    Charge
    +
    Load
    +
    Status
    +
    Equipment
    +
    Lighting
    +
    Environment
    +
    + {{#each data.areas}} + +
    {{Math.round(adata.areas[@index].charge)}} %
    +
    {{Math.round(adata.areas[@index].load)}} W
    +
    {{chargingMode(charging)}}
    +
    {{channelPower(eqp)}} [{{channelMode(eqp)}}]
    +
    {{channelPower(lgt)}} [{{channelMode(lgt)}}]
    +
    {{channelPower(env)}} [{{channelMode(env)}}]
    +
    + {{/each}} +
    \ No newline at end of file diff --git a/tgui/src/interfaces/revelation.ract b/tgui/src/interfaces/revelation.ract new file mode 100644 index 00000000000..d4bbd89b403 --- /dev/null +++ b/tgui/src/interfaces/revelation.ract @@ -0,0 +1,60 @@ + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + + +
    +
    + Payload status: +
    +
    + {{#if data.armed}} + ARMED + {{else}} + DISARMED + {{/if}} +
    +
    + Controls: +
    +
    + +
    OBFUSCATE PROGRAM NAME +
    {{data.armed ? "DISARM" : "ARM"}} + ACTIVATE +
    +
    +
    +
    \ No newline at end of file diff --git a/tgui/src/interfaces/station_alert_prog.ract b/tgui/src/interfaces/station_alert_prog.ract new file mode 100644 index 00000000000..5ba78cdb91e --- /dev/null +++ b/tgui/src/interfaces/station_alert_prog.ract @@ -0,0 +1,47 @@ + +
    +
    + + {{#if data.PC_batteryicon && data.PC_showbatteryicon}} +
    + {{/if}} + {{#if data.PC_batterypercent && data.PC_showbatteryicon}} + {{data.PC_batterypercent}} + {{/if}} + {{#if data.PC_ntneticon}} + + {{/if}} + {{#if data.PC_apclinkicon}} + + {{/if}} + {{#if data.PC_stationtime}} + {{data.PC_stationtime}} + {{/if}} + {{#each data.PC_programheaders}} + + {{/each}} +
    +
    +
    +
    + +
    Shutdown + {{#if data.PC_showexitprogram}} + EXIT PROGRAM + Minimize Program + {{/if}} +
    +
    +
    + +{{#each data.alarms:class}} + +
      + {{#each .}} +
    • {{.}}
    • + {{else}} +
    • System Nominal
    • + {{/each}} +
    +
    +{{/each}} \ No newline at end of file