diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm index 9ace5cf95b..826d25a437 100644 --- a/_maps/map_files/BoxStation/BoxStation.dmm +++ b/_maps/map_files/BoxStation/BoxStation.dmm @@ -25957,7 +25957,7 @@ /area/medical/paramedic) "bkd" = ( /obj/machinery/camera{ - c_tag = "Medbay Morgue"; + c_tag = "Paramedic Disbatch"; dir = 8; network = list("ss13","medbay") }, @@ -26360,7 +26360,7 @@ req_access_txt = "6" }, /turf/open/floor/plasteel/dark, -/area/medical/morgue) +/area/maintenance/department/medical/morgue) "blb" = ( /obj/machinery/door/airlock/command{ name = "Captain's Quarters"; @@ -33974,12 +33974,12 @@ }, /obj/machinery/bloodbankgen, /obj/machinery/camera{ - c_tag = "Medbay Recovery Room"; + c_tag = "Medbay Surgery Storage"; dir = 6; network = list("ss13","medbay") }, /turf/open/floor/plasteel/white, -/area/medical/medbay/central) +/area/medical/storage) "bCD" = ( /obj/machinery/computer/operating, /turf/open/floor/plasteel/white/side, @@ -34354,11 +34354,14 @@ /area/engineering/storage/tech) "bDA" = ( /obj/structure/disposalpipe/segment, -/obj/machinery/atmospherics/pipe/simple/supply/hidden{ - dir = 4 +/obj/structure/cable{ + icon_state = "4-8" + }, +/obj/machinery/atmospherics/pipe/manifold/supply/hidden{ + dir = 1 }, /turf/open/floor/plasteel/white, -/area/medical/medbay/central) +/area/medical/storage) "bDB" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden{ dir = 4 @@ -34366,6 +34369,9 @@ /obj/structure/cable{ icon_state = "1-4" }, +/obj/structure/cable{ + icon_state = "4-8" + }, /turf/open/floor/plasteel/white/side{ dir = 4 }, @@ -35118,7 +35124,7 @@ dir = 4 }, /turf/closed/wall, -/area/medical/medbay/central) +/area/medical/storage) "bFn" = ( /obj/structure/disposalpipe/segment{ dir = 5 @@ -35130,12 +35136,12 @@ /obj/structure/disposalpipe/segment{ dir = 10 }, -/obj/machinery/light/small{ - dir = 1 - }, /obj/effect/decal/cleanable/blood/old, +/obj/machinery/airalarm{ + pixel_y = 23 + }, /turf/open/floor/plasteel/white, -/area/medical/medbay/central) +/area/medical/storage) "bFp" = ( /obj/structure/closet/crate/freezer, /obj/item/reagent_containers/blood/random, @@ -35162,8 +35168,11 @@ dir = 8; sortType = 6 }, +/obj/machinery/light/small{ + dir = 1 + }, /turf/open/floor/plasteel/white, -/area/medical/medbay/central) +/area/medical/storage) "bFq" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden, /obj/effect/turf_decal/tile/yellow, @@ -35187,8 +35196,11 @@ /obj/structure/disposalpipe/segment, /obj/structure/closet/crate/freezer/surplus_limbs, /obj/item/reagent_containers/glass/beaker/synthflesh, +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 1 + }, /turf/open/floor/plasteel/white, -/area/medical/medbay/central) +/area/medical/storage) "bFu" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden{ dir = 4 @@ -35222,8 +35234,9 @@ "bFx" = ( /obj/structure/disposalpipe/segment, /obj/machinery/limbgrower, +/obj/machinery/atmospherics/components/unary/vent_scrubber/on, /turf/open/floor/plasteel/white, -/area/medical/medbay/central) +/area/medical/storage) "bFy" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 1 @@ -35809,7 +35822,7 @@ dir = 4 }, /turf/closed/wall, -/area/medical/medbay/central) +/area/medical/storage) "bGR" = ( /obj/structure/table, /obj/item/storage/belt/medical{ @@ -35860,11 +35873,9 @@ /area/medical/medbay/central) "bGW" = ( /obj/structure/disposalpipe/segment, -/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden{ - dir = 1 - }, +/obj/machinery/atmospherics/pipe/manifold4w/scrubbers, /turf/closed/wall, -/area/medical/medbay/central) +/area/medical/storage) "bGX" = ( /obj/machinery/light{ dir = 8 @@ -37012,7 +37023,7 @@ dir = 9 }, /turf/closed/wall, -/area/medical/medbay/central) +/area/medical/storage) "bJB" = ( /obj/machinery/atmospherics/pipe/manifold4w/scrubbers, /turf/open/floor/plasteel, @@ -52309,7 +52320,7 @@ dir = 4 }, /turf/open/floor/plating, -/area/science/robotics/mechbay) +/area/maintenance/department/medical/morgue) "cHF" = ( /obj/machinery/button/door{ id = "Skynet_launch"; @@ -54428,7 +54439,7 @@ "fvk" = ( /obj/structure/disposalpipe/segment, /turf/closed/wall, -/area/medical/medbay/central) +/area/medical/storage) "fvY" = ( /obj/machinery/computer/cryopod{ pixel_y = 26 @@ -55768,6 +55779,9 @@ req_access_txt = "45" }, /obj/machinery/door/firedoor, +/obj/structure/cable{ + icon_state = "4-8" + }, /turf/open/floor/plasteel, /area/medical/surgery) "iVJ" = ( @@ -60411,6 +60425,9 @@ /obj/effect/spawner/structure/window/reinforced, /turf/open/floor/plating, /area/engineering/atmos) +"tBV" = ( +/turf/closed/wall, +/area/medical/storage) "tCa" = ( /obj/structure/table/wood, /obj/item/instrument/guitar{ @@ -61009,8 +61026,17 @@ dir = 4 }, /obj/structure/disposalpipe/segment, +/obj/machinery/power/apc{ + areastring = "/area/medical/storage"; + name = "Medbay Surgery Storage"; + pixel_x = 1; + pixel_y = -24 + }, +/obj/structure/cable{ + icon_state = "0-4" + }, /turf/open/floor/plasteel/white, -/area/medical/medbay/central) +/area/medical/storage) "uFZ" = ( /obj/structure/closet/emcloset, /obj/effect/turf_decal/stripes/line{ @@ -61336,6 +61362,16 @@ /obj/structure/pool/Lboard, /turf/open/pool, /area/commons/fitness/pool) +"voZ" = ( +/obj/structure/disposalpipe/segment, +/obj/machinery/atmospherics/pipe/simple/supply/hidden{ + dir = 4 + }, +/obj/structure/cable{ + icon_state = "4-8" + }, +/turf/open/floor/plasteel/white, +/area/medical/storage) "vpY" = ( /obj/structure/closet/lasertag/blue, /obj/item/clothing/under/misc/pj/blue, @@ -95226,10 +95262,10 @@ aJq bHt bBz bzs -bof +tBV bFm bGI -bof +tBV cBD bKD bLO @@ -95483,7 +95519,7 @@ bwu kPj bBB eBX -bof +tBV bFp uFV fvk @@ -95740,7 +95776,7 @@ aJq bAj aJq aKG -bof +tBV bFo bDA bFt @@ -95997,9 +96033,9 @@ byX aXh bmE bCA -bof +tBV bCC -bDA +voZ bFx bGW bKI diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 8c382e56e8..19c8e68546 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -13232,7 +13232,7 @@ dir = 4 }, /turf/open/floor/plasteel, -/area/hallway/secondary/service) +/area/maintenance/port/fore) "aYA" = ( /obj/structure/cable/white{ icon_state = "4-8" @@ -33522,7 +33522,7 @@ dir = 1 }, /turf/open/floor/plasteel, -/area/security/range) +/area/maintenance/starboard) "cqi" = ( /obj/structure/cable/white, /obj/effect/spawner/structure/window/reinforced, @@ -49101,7 +49101,7 @@ icon_state = "1-2" }, /turf/open/floor/plasteel, -/area/science/misc_lab) +/area/maintenance/port) "dhU" = ( /obj/structure/sign/nanotrasen, /turf/closed/wall/r_wall, @@ -71534,6 +71534,9 @@ }, /turf/open/floor/plasteel/dark, /area/command/heads_quarters/hos) +"emZ" = ( +/turf/closed/wall, +/area/hallway/primary/port) "enl" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden, /obj/effect/turf_decal/stripes/corner, @@ -106478,7 +106481,7 @@ dir = 1 }, /turf/open/floor/plasteel, -/area/commons/dorms) +/area/maintenance/starboard) "qtk" = ( /obj/structure/table/wood, /obj/item/reagent_containers/food/drinks/soda_cans/dr_gibb, @@ -156429,7 +156432,7 @@ pzz kRu iio aMN -alf +emZ bqm bsp bug @@ -156686,7 +156689,7 @@ vgS xnz iio aMO -alf +emZ bqn bsq buh @@ -156943,7 +156946,7 @@ lNq skw iio aMN -alf +emZ bqo bsr fzc diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 9b70a54282..f39dbc578d 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -25521,6 +25521,9 @@ /obj/effect/turf_decal/tile/purple{ dir = 4 }, +/obj/item/paicard{ + pixel_x = -8 + }, /turf/open/floor/plasteel/white, /area/science/research) "cav" = ( @@ -74083,7 +74086,7 @@ }, /obj/machinery/door/firedoor, /turf/open/floor/plating, -/area/science/circuit) +/area/maintenance/starboard/aft) "sFR" = ( /obj/structure/cable{ icon_state = "2-8" @@ -76385,7 +76388,7 @@ icon_state = "1-2" }, /turf/open/floor/plating, -/area/service/library) +/area/maintenance/port) "ubJ" = ( /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden, /obj/structure/cable/yellow{ @@ -80256,7 +80259,7 @@ }, /obj/machinery/atmospherics/pipe/simple/supply/hidden, /turf/open/floor/plating, -/area/commons/vacant_room/office) +/area/maintenance/port) "wxP" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden{ dir = 10 @@ -97479,8 +97482,8 @@ dne aip dne dne -dne -dne +aRA +aRA dne baf bbK diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm index 7bfabf432d..eacbc74508 100644 --- a/_maps/map_files/PubbyStation/PubbyStation.dmm +++ b/_maps/map_files/PubbyStation/PubbyStation.dmm @@ -705,7 +705,7 @@ icon_state = "4-8" }, /turf/open/floor/plating, -/area/commons/fitness/pool) +/area/maintenance/department/crew_quarters/dorms) "abG" = ( /obj/structure/cable{ icon_state = "1-2" @@ -3392,6 +3392,10 @@ /obj/vehicle/ridden/secway, /obj/item/key/security, /obj/effect/turf_decal/bot, +/obj/machinery/airalarm{ + dir = 1; + pixel_y = -22 + }, /turf/open/floor/plasteel/showroomfloor, /area/security/office) "ais" = ( @@ -4891,6 +4895,10 @@ /obj/effect/turf_decal/tile/red{ dir = 8 }, +/obj/machinery/airalarm{ + dir = 4; + pixel_x = -23 + }, /turf/open/floor/plasteel, /area/security/brig) "alz" = ( @@ -37279,7 +37287,7 @@ dir = 4 }, /turf/open/floor/plating, -/area/science/mixing) +/area/maintenance/department/science) "bGB" = ( /obj/machinery/light/small, /obj/machinery/atmospherics/pipe/simple/supply/hidden{ @@ -45936,14 +45944,8 @@ /turf/open/floor/engine, /area/maintenance/disposal/incinerator) "bZV" = ( -/obj/effect/mapping_helpers/airlock/cyclelink_helper{ - dir = 8 - }, -/obj/machinery/door/airlock/external{ - req_access_txt = "13" - }, -/turf/open/floor/plating, -/area/maintenance/department/cargo) +/turf/closed/wall, +/area/hallway/primary/fore) "bZY" = ( /turf/closed/wall, /area/service/chapel/office) @@ -61261,8 +61263,14 @@ /turf/open/floor/plating, /area/maintenance/department/science) "vtT" = ( +/obj/effect/mapping_helpers/airlock/cyclelink_helper{ + dir = 8 + }, +/obj/machinery/door/airlock/external{ + req_access_txt = "13" + }, /turf/open/floor/plating, -/area/maintenance/solars/port) +/area/maintenance/department/cargo) "vuP" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on{ dir = 4 @@ -62179,7 +62187,7 @@ "xuv" = ( /obj/item/broken_bottle, /turf/open/floor/plating, -/area/maintenance/solars/port) +/area/maintenance/department/security/brig) "xvO" = ( /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden, /obj/machinery/atmospherics/pipe/simple/supply/hidden{ @@ -79788,11 +79796,11 @@ atp aus aiu wxb -axC +aiu xuv -azN -vtT -vtT +aoe +ajD +ajD aiu apB aiu @@ -82360,8 +82368,8 @@ apE avq apE ajM -aiu -aiu +bZV +bZV gSH xJy sJr @@ -106255,7 +106263,7 @@ aaF aaF aaF abF -aaF +aiS aiS atn awC @@ -107807,8 +107815,8 @@ aaa aaa aaa aaa -aEl -aFi +aEj +vtT aEj aaa aEj @@ -108064,9 +108072,9 @@ aaa aaa aaa aaa -aEj -bZV -aEj +cdm +cdm +cdm aaa aaa aaa @@ -108321,9 +108329,9 @@ aaa aaa aaa aaa -ahi -ahi -ahi +bBW +bBW +bBW aaa aaa aaa diff --git a/_maps/shuttles/emergency_airless.dmm b/_maps/shuttles/emergency_construction.dmm similarity index 100% rename from _maps/shuttles/emergency_airless.dmm rename to _maps/shuttles/emergency_construction.dmm diff --git a/code/__DEFINES/_flags/item_flags.dm b/code/__DEFINES/_flags/item_flags.dm index 7da71e22cb..529499127d 100644 --- a/code/__DEFINES/_flags/item_flags.dm +++ b/code/__DEFINES/_flags/item_flags.dm @@ -51,3 +51,4 @@ #define ORGAN_NO_SPOIL (1<<5) //Do not spoil under any circumstances #define ORGAN_NO_DISMEMBERMENT (1<<6) //Immune to disembowelment. #define ORGAN_EDIBLE (1<<7) //is a snack? :D +#define ORGAN_SYNTHETIC_EMP (1<<6) //Synthetic organ affected by an EMP. Deteriorates over time. diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 99a2e9d0ab..a04f02bd6a 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -136,3 +136,6 @@ // paintings #define VV_HK_REMOVE_PAINTING "remove_painting" + +//outfits +#define VV_HK_TO_OUTFIT_EDITOR "outfit_editor" diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index b8c009ff4f..9719157d4d 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -68,6 +68,7 @@ for(var/spath in subtypesof(/datum/species)) var/datum/species/S = new spath() GLOB.species_list[S.id] = spath + GLOB.species_datums[S.id] = S //Surgeries for(var/path in subtypesof(/datum/surgery)) diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 1d37f639bf..fabe70c929 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1051,7 +1051,7 @@ GLOBAL_LIST_EMPTY(friendly_animal_types) return 0 //For creating consistent icons for human looking simple animals -/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null) +/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null, no_anim = FALSE) var/static/list/humanoid_icon_cache = list() if(!icon_id || !humanoid_icon_cache[icon_id]) var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key) @@ -1065,10 +1065,9 @@ GLOBAL_LIST_EMPTY(friendly_animal_types) var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing") + COMPILE_OVERLAYS(body) for(var/D in showDirs) - body.setDir(D) - COMPILE_OVERLAYS(body) - var/icon/partial = getFlatIcon(body) + var/icon/partial = getFlatIcon(body, defdir = D, no_anim = no_anim) out_icon.Insert(partial,dir=D) humanoid_icon_cache[icon_id] = out_icon diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 513437e57e..92cf7050e0 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -320,6 +320,8 @@ GLOBAL_LIST_INIT(nonstandard_skin_tones, list("orange")) GLOBAL_LIST_EMPTY(species_list) +GLOBAL_LIST_EMPTY(species_datums) + /proc/age2agedescription(age) switch(age) if(0 to 1) diff --git a/code/__HELPERS/reagents.dm b/code/__HELPERS/reagents.dm index de225b3b53..fa655efce4 100644 --- a/code/__HELPERS/reagents.dm +++ b/code/__HELPERS/reagents.dm @@ -95,3 +95,9 @@ if("I'm feeling lucky") chosen_id = pick(subtypesof(/datum/reagent)) return chosen_id + +/proc/find_reagent_object_from_type(input) + if(GLOB.chemical_reagents_list[input]) //prefer IDs! + return GLOB.chemical_reagents_list[input] + else + return null diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index d44a4b3898..9c6f16e724 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -49,7 +49,12 @@ GLOBAL_LIST_INIT(bitfields, list( "UNIQUE_RENAME" = UNIQUE_RENAME, "USES_TGUI" = USES_TGUI, "FROZEN" = FROZEN, - "SHOVABLE_ONTO" = SHOVABLE_ONTO + "SHOVABLE_ONTO" = SHOVABLE_ONTO, + "BLOCK_Z_OUT_DOWN" = BLOCK_Z_OUT_DOWN, + "BLOCK_Z_OUT_UP" = BLOCK_Z_OUT_UP, + "BLOCK_Z_IN_DOWN" = BLOCK_Z_IN_DOWN, + "BLOCK_Z_IN_UP" = BLOCK_Z_IN_UP, + "EXAMINE_SKIP" = EXAMINE_SKIP ), "datum_flags" = list( "DF_USE_TAG" = DF_USE_TAG, diff --git a/code/controllers/subsystem/activity.dm b/code/controllers/subsystem/activity.dm index 24cd1802f0..0a8d248e58 100644 --- a/code/controllers/subsystem/activity.dm +++ b/code/controllers/subsystem/activity.dm @@ -61,14 +61,14 @@ SUBSYSTEM_DEF(activity) for(var/threat in threat_history) . = max(threat_history[threat], .) -/datum/controller/subsystem/activity/proc/on_explosion(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) +/datum/controller/subsystem/activity/proc/on_explosion(datum/source, atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) if(!("explosions" in deferred_threats)) deferred_threats["explosions"] = 0 var/area/A = get_area(epicenter) if(is_station_level(epicenter.z) && (A.area_flags & BLOBS_ALLOWED) && !istype(A, /area/asteroid)) deferred_threats["explosions"] += devastation_range**2 + heavy_impact_range**2 / 4 + light_impact_range**2 / 8 // 75 for a maxcap -/datum/controller/subsystem/activity/proc/on_death(mob/M, gibbed) +/datum/controller/subsystem/activity/proc/on_death(datum/source, mob/M, gibbed) if(!("crew_deaths" in deferred_threats)) deferred_threats["crew_deaths"] = 0 if(M?.mind && SSjob.GetJob(M.mind.assigned_role)) diff --git a/code/datums/components/radioactive.dm b/code/datums/components/radioactive.dm index 8afa4353b2..4c418bb8dc 100644 --- a/code/datums/components/radioactive.dm +++ b/code/datums/components/radioactive.dm @@ -52,7 +52,7 @@ return strength -= strength / hl3_release_date if(strength <= RAD_BACKGROUND_RADIATION) - return PROCESS_KILL + qdel(src) /datum/component/radioactive/proc/glow_loop(atom/movable/master) diff --git a/code/datums/dna.dm b/code/datums/dna.dm index b6b93bdddc..248b669ab1 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -329,12 +329,13 @@ uni_identity = generate_uni_identity() unique_enzymes = generate_unique_enzymes() -/datum/dna/proc/initialize_dna(newblood_type) +/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE) if(newblood_type) blood_type = newblood_type unique_enzymes = generate_unique_enzymes() uni_identity = generate_uni_identity() - generate_dna_blocks() + if(!skip_index) //I hate this + generate_dna_blocks() features = random_features(species?.id, holder?.gender) diff --git a/code/datums/mutations/space_adaptation.dm b/code/datums/mutations/space_adaptation.dm index a3a2f10f2f..8b2263c2f2 100644 --- a/code/datums/mutations/space_adaptation.dm +++ b/code/datums/mutations/space_adaptation.dm @@ -11,7 +11,7 @@ /datum/mutation/human/space_adaptation/New(class_ = MUT_OTHER, timer, datum/mutation/human/copymut) ..() if(!(type in visual_indicators)) - visual_indicators[type] = list(mutable_appearance('icons/effects/genetics.dmi', "fire", -MUTATIONS_LAYER)) + visual_indicators[type] = list(mutable_appearance('icons/effects/genetics.dmi', "space_adapt", -MUTATIONS_LAYER)) /datum/mutation/human/space_adaptation/get_visual_indicator() return visual_indicators[type][1] diff --git a/code/datums/outfit.dm b/code/datums/outfit.dm index da379b9851..0b46629365 100755 --- a/code/datums/outfit.dm +++ b/code/datums/outfit.dm @@ -1,71 +1,185 @@ +/** + * # Outfit datums + * + * This is a clean system of applying outfits to mobs, if you need to equip someone in a uniform + * this is the way to do it cleanly and properly. + * + * You can also specify an outfit datum on a job to have it auto equipped to the mob on join + * + * /mob/living/carbon/human/proc/equipOutfit(outfit) is the mob level proc to equip an outfit + * and you pass it the relevant datum outfit + * + * outfits can also be saved as json blobs downloadable by a client and then can be uploaded + * by that user to recreate the outfit, this is used by admins to allow for custom event outfits + * that can be restored at a later date + */ /datum/outfit + ///Name of the outfit (shows up in the equip admin verb) var/name = "Naked" - var/uniform = null - var/suit = null - var/toggle_helmet = TRUE - var/back = null - var/belt = null - var/gloves = null - var/shoes = null - var/head = null - var/mask = null - var/neck = null - var/ears = null - var/glasses = null + /// Type path of item to go in the idcard slot var/id = null - var/l_pocket = null - var/r_pocket = null + + /// Type path of item to go in uniform slot + var/uniform = null + + /// Type path of item to go in suit slot + var/suit = null + + /** + * Type path of item to go in suit storage slot + * + * (make sure it's valid for that suit) + */ var/suit_store = null - var/r_hand = null + + /// Type path of item to go in back slot + var/back = null + + /** + * list of items that should go in the backpack of the user + * + * Format of this list should be: list(path=count,otherpath=count) + */ + var/list/backpack_contents = null + + /// Type path of item to go in belt slot + var/belt = null + + /// Type path of item to go in ears slot + var/ears = null + + /// Type path of item to go in the glasses slot + var/glasses = null + + /// Type path of item to go in gloves slot + var/gloves = null + + /// Type path of item to go in head slot + var/head = null + + /// Type path of item to go in mask slot + var/mask = null + + /// Type path of item to go in neck slot + var/neck = null + + /// Type path of item to go in shoes slot + var/shoes = null + + /// Type path of item for left pocket slot + var/l_pocket = null + + /// Type path of item for right pocket slot + var/r_pocket = null + + ///Type path of item to go in the right hand var/l_hand = null - var/internals_slot = null //ID of slot containing a gas tank - var/list/backpack_contents = null // In the list(path=count,otherpath=count) format - var/box // Internals box. Will be inserted at the start of backpack_contents - var/list/implants = null + + //Type path of item to go in left hand + var/r_hand = null + + /// Any clothing accessory item var/accessory = null - var/can_be_admin_equipped = TRUE // Set to FALSE if your outfit requires runtime parameters - var/list/chameleon_extras //extra types for chameleon outfit changes, mostly guns + /// Internals box. Will be inserted at the start of backpack_contents + var/box + /** + * extra types for chameleon outfit changes, mostly guns + * + * Format of this list is (typepath, typepath, typepath) + * + * These are all added and returns in the list for get_chamelon_diguise_info proc + */ + var/list/chameleon_extras + + /** + * Any implants the mob should start implanted with + * + * Format of this list is (typepath, typepath, typepath) + */ + var/list/implants = null + + ///ID of the slot containing a gas tank + var/internals_slot = null + + /// Should the toggle helmet proc be called on the helmet during equip + var/toggle_helmet = TRUE + + /// Any undershirt. While on humans it is a string, here we use paths to stay consistent with the rest of the equips. + var/datum/sprite_accessory/undershirt = null + +/** + * Called at the start of the equip proc + * + * Override to change the value of the slots depending on client prefs, species and + * other such sources of change + * + * Extra Arguments + * * visualsOnly true if this is only for display (in the character setup screen) + * + * If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite + */ /datum/outfit/proc/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) //to be overridden for customization depending on client prefs,species etc return +/** + * Called after the equip proc has finished + * + * All items are on the mob at this point, use this proc to toggle internals + * fiddle with id bindings and accesses etc + * + * Extra Arguments + * * visualsOnly true if this is only for display (in the character setup screen) + * + * If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite + */ /datum/outfit/proc/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) //to be overridden for toggling internals, id binding, access etc return +/** + * Equips all defined types and paths to the mob passed in + * + * Extra Arguments + * * visualsOnly true if this is only for display (in the character setup screen) + * + * If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite + */ /datum/outfit/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) pre_equip(H, visualsOnly, preference_source) //Start with uniform,suit,backpack for additional slots if(uniform) - H.equip_to_slot_or_del(new uniform(H),SLOT_W_UNIFORM) + H.equip_to_slot_or_del(new uniform(H), SLOT_W_UNIFORM, TRUE) if(suit) - H.equip_to_slot_or_del(new suit(H),SLOT_WEAR_SUIT) + H.equip_to_slot_or_del(new suit(H), SLOT_WEAR_SUIT, TRUE) if(back) - H.equip_to_slot_or_del(new back(H),SLOT_BACK) + H.equip_to_slot_or_del(new back(H), SLOT_BACK, TRUE) if(belt) - H.equip_to_slot_or_del(new belt(H),SLOT_BELT) + H.equip_to_slot_or_del(new belt(H), SLOT_BELT, TRUE) if(gloves) - H.equip_to_slot_or_del(new gloves(H),SLOT_GLOVES) + H.equip_to_slot_or_del(new gloves(H), SLOT_GLOVES, TRUE) if(shoes) - H.equip_to_slot_or_del(new shoes(H),SLOT_SHOES) + H.equip_to_slot_or_del(new shoes(H), SLOT_SHOES, TRUE) if(head) - H.equip_to_slot_or_del(new head(H),SLOT_HEAD) + H.equip_to_slot_or_del(new head(H), SLOT_HEAD, TRUE) if(mask) - H.equip_to_slot_or_del(new mask(H),SLOT_WEAR_MASK) + H.equip_to_slot_or_del(new mask(H), SLOT_WEAR_MASK, TRUE) if(neck) - H.equip_to_slot_or_del(new neck(H),SLOT_NECK) + H.equip_to_slot_or_del(new neck(H), SLOT_NECK, TRUE) if(ears) - H.equip_to_slot_or_del(new ears(H),SLOT_EARS) + H.equip_to_slot_or_del(new ears(H), SLOT_EARS, TRUE) if(glasses) - H.equip_to_slot_or_del(new glasses(H),SLOT_GLASSES) + H.equip_to_slot_or_del(new glasses(H), SLOT_GLASSES, TRUE) if(id) - H.equip_to_slot_or_del(new id(H),SLOT_WEAR_ID) + H.equip_to_slot_or_del(new id(H), SLOT_WEAR_ID, TRUE) if(suit_store) - H.equip_to_slot_or_del(new suit_store(H),SLOT_S_STORE) + H.equip_to_slot_or_del(new suit_store(H), SLOT_S_STORE, TRUE) + if(undershirt) + H.undershirt = initial(undershirt.name) if(accessory) var/obj/item/clothing/under/U = H.w_uniform @@ -81,9 +195,9 @@ if(!visualsOnly) // Items in pockets or backpack don't show up on mob's icon. if(l_pocket) - H.equip_to_slot_or_del(new l_pocket(H),SLOT_L_STORE) + H.equip_to_slot_or_del(new l_pocket(H), SLOT_L_STORE, TRUE) if(r_pocket) - H.equip_to_slot_or_del(new r_pocket(H),SLOT_R_STORE) + H.equip_to_slot_or_del(new r_pocket(H), SLOT_R_STORE, TRUE) if(box) if(!backpack_contents) @@ -97,7 +211,7 @@ if(!isnum(number))//Default to 1 number = 1 for(var/i in 1 to number) - H.equip_to_slot_or_del(new path(H),SLOT_IN_BACKPACK) + H.equip_to_slot_or_del(new path(H), SLOT_IN_BACKPACK, TRUE) if(!H.head && toggle_helmet && istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit)) var/obj/item/clothing/suit/space/hardsuit/HS = H.wear_suit @@ -112,55 +226,178 @@ H.update_action_buttons_icon() if(implants) for(var/implant_type in implants) - var/obj/item/implant/I = new implant_type + var/obj/item/implant/I = new implant_type(H) I.implant(H, null, TRUE) H.update_body() return TRUE +/** + * Apply a fingerprint from the passed in human to all items in the outfit + * + * Used for forensics setup when the mob is first equipped at roundstart + * essentially calls add_fingerprint to every defined item on the human + * + */ /datum/outfit/proc/apply_fingerprints(mob/living/carbon/human/H) if(!istype(H)) return if(H.back) - H.back.add_fingerprint(H,1) //The 1 sets a flag to ignore gloves + H.back.add_fingerprint(H, ignoregloves = TRUE) for(var/obj/item/I in H.back.contents) - I.add_fingerprint(H,1) + I.add_fingerprint(H, ignoregloves = TRUE) if(H.wear_id) - H.wear_id.add_fingerprint(H,1) + H.wear_id.add_fingerprint(H, ignoregloves = TRUE) if(H.w_uniform) - H.w_uniform.add_fingerprint(H,1) + H.w_uniform.add_fingerprint(H, ignoregloves = TRUE) if(H.wear_suit) - H.wear_suit.add_fingerprint(H,1) + H.wear_suit.add_fingerprint(H, ignoregloves = TRUE) if(H.wear_mask) - H.wear_mask.add_fingerprint(H,1) + H.wear_mask.add_fingerprint(H, ignoregloves = TRUE) if(H.wear_neck) - H.wear_neck.add_fingerprint(H,1) + H.wear_neck.add_fingerprint(H, ignoregloves = TRUE) if(H.head) - H.head.add_fingerprint(H,1) + H.head.add_fingerprint(H, ignoregloves = TRUE) if(H.shoes) - H.shoes.add_fingerprint(H,1) + H.shoes.add_fingerprint(H, ignoregloves = TRUE) if(H.gloves) - H.gloves.add_fingerprint(H,1) + H.gloves.add_fingerprint(H, ignoregloves = TRUE) if(H.ears) - H.ears.add_fingerprint(H,1) + H.ears.add_fingerprint(H, ignoregloves = TRUE) if(H.glasses) - H.glasses.add_fingerprint(H,1) + H.glasses.add_fingerprint(H, ignoregloves = TRUE) if(H.belt) - H.belt.add_fingerprint(H,1) + H.belt.add_fingerprint(H, ignoregloves = TRUE) for(var/obj/item/I in H.belt.contents) - I.add_fingerprint(H,1) + I.add_fingerprint(H, ignoregloves = TRUE) if(H.s_store) - H.s_store.add_fingerprint(H,1) + H.s_store.add_fingerprint(H, ignoregloves = TRUE) if(H.l_store) - H.l_store.add_fingerprint(H,1) + H.l_store.add_fingerprint(H, ignoregloves = TRUE) if(H.r_store) - H.r_store.add_fingerprint(H,1) + H.r_store.add_fingerprint(H, ignoregloves = TRUE) for(var/obj/item/I in H.held_items) - I.add_fingerprint(H,1) - return 1 + I.add_fingerprint(H, ignoregloves = TRUE) + return TRUE +/// Return a list of all the types that are required to disguise as this outfit type /datum/outfit/proc/get_chameleon_disguise_info() var/list/types = list(uniform, suit, back, belt, gloves, shoes, head, mask, neck, ears, glasses, id, l_pocket, r_pocket, suit_store, r_hand, l_hand) types += chameleon_extras listclearnulls(types) return types + +/// Return a json list of this outfit +/datum/outfit/proc/get_json_data() + . = list() + .["outfit_type"] = type + .["name"] = name + .["uniform"] = uniform + .["suit"] = suit + .["toggle_helmet"] = toggle_helmet + .["back"] = back + .["belt"] = belt + .["gloves"] = gloves + .["shoes"] = shoes + .["head"] = head + .["mask"] = mask + .["neck"] = neck + .["ears"] = ears + .["glasses"] = glasses + .["id"] = id + .["l_pocket"] = l_pocket + .["r_pocket"] = r_pocket + .["suit_store"] = suit_store + .["r_hand"] = r_hand + .["l_hand"] = l_hand + .["internals_slot"] = internals_slot + .["backpack_contents"] = backpack_contents + .["box"] = box + .["implants"] = implants + .["accessory"] = accessory + +/// Copy most vars from another outfit to this one +/datum/outfit/proc/copy_from(datum/outfit/target) + name = target.name + uniform = target.uniform + suit = target.suit + toggle_helmet = target.toggle_helmet + back = target.back + belt = target.belt + gloves = target.gloves + shoes = target.shoes + head = target.head + mask = target.mask + neck = target.neck + ears = target.ears + glasses = target.glasses + id = target.id + l_pocket = target.l_pocket + r_pocket = target.r_pocket + suit_store = target.suit_store + r_hand = target.r_hand + l_hand = target.l_hand + internals_slot = target.internals_slot + backpack_contents = target.backpack_contents + box = target.box + implants = target.implants + accessory = target.accessory + +/// Prompt the passed in mob client to download this outfit as a json blob +/datum/outfit/proc/save_to_file(mob/admin) + var/stored_data = get_json_data() + var/json = json_encode(stored_data) + //Kinda annoying but as far as i can tell you need to make actual file. + var/f = file("data/TempOutfitUpload") + fdel(f) + WRITE_FILE(f,json) + admin << ftp(f,"[name].json") + +/// Create an outfit datum from a list of json data +/datum/outfit/proc/load_from(list/outfit_data) + //This could probably use more strict validation + name = outfit_data["name"] + uniform = text2path(outfit_data["uniform"]) + suit = text2path(outfit_data["suit"]) + toggle_helmet = outfit_data["toggle_helmet"] + back = text2path(outfit_data["back"]) + belt = text2path(outfit_data["belt"]) + gloves = text2path(outfit_data["gloves"]) + shoes = text2path(outfit_data["shoes"]) + head = text2path(outfit_data["head"]) + mask = text2path(outfit_data["mask"]) + neck = text2path(outfit_data["neck"]) + ears = text2path(outfit_data["ears"]) + glasses = text2path(outfit_data["glasses"]) + id = text2path(outfit_data["id"]) + l_pocket = text2path(outfit_data["l_pocket"]) + r_pocket = text2path(outfit_data["r_pocket"]) + suit_store = text2path(outfit_data["suit_store"]) + r_hand = text2path(outfit_data["r_hand"]) + l_hand = text2path(outfit_data["l_hand"]) + internals_slot = outfit_data["internals_slot"] + var/list/backpack = outfit_data["backpack_contents"] + backpack_contents = list() + for(var/item in backpack) + var/itype = text2path(item) + if(itype) + backpack_contents[itype] = backpack[item] + box = text2path(outfit_data["box"]) + var/list/impl = outfit_data["implants"] + implants = list() + for(var/I in impl) + var/imptype = text2path(I) + if(imptype) + implants += imptype + accessory = text2path(outfit_data["accessory"]) + return TRUE + +/datum/outfit/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION("", "---") + VV_DROPDOWN_OPTION(VV_HK_TO_OUTFIT_EDITOR, "Outfit Editor") + +/datum/outfit/vv_do_topic(list/href_list) + . = ..() + if(href_list[VV_HK_TO_OUTFIT_EDITOR]) + usr.client.open_outfit_editor(src) diff --git a/code/game/machinery/limbgrower.dm b/code/game/machinery/limbgrower.dm index aa1d05884e..dc87322b57 100644 --- a/code/game/machinery/limbgrower.dm +++ b/code/game/machinery/limbgrower.dm @@ -1,9 +1,5 @@ -#define LIMBGROWER_MAIN_MENU 1 -#define LIMBGROWER_CATEGORY_MENU 2 -#define LIMBGROWER_CHEMICAL_MENU 3 -//use these for the menu system - - +/// The limbgrower. Makes organd and limbs with synthflesh and chems. +/// See [limbgrower_designs.dm] for everything we can make. /obj/machinery/limbgrower name = "limb grower" desc = "It grows new limbs using Synthflesh." @@ -15,161 +11,235 @@ active_power_usage = 100 circuit = /obj/item/circuitboard/machine/limbgrower - var/operating = FALSE - var/disabled = FALSE + /// The category of limbs we're browing in our UI. + var/selected_category = "human" + /// If we're currently printing something. var/busy = FALSE - var/prod_coeff = 1 + /// How efficient our machine is. Better parts = less chemicals used and less power used. Range of 1 to 0.25. + var/production_coefficient = 1 + /// How long it takes for us to print a limb. Affected by production_coefficient. + var/production_speed = 3 SECONDS + /// The design we're printing currently. var/datum/design/being_built + /// Our internal techweb for limbgrower designs. var/datum/techweb/stored_research - var/selected_category - var/screen = 1 + /// All the categories of organs we can print. var/list/categories = list( - "human" = /datum/species/human, - "lizard" = /datum/species/lizard, - "mammal" = /datum/species/mammal, - "insect" = /datum/species/insect, - "fly" = /datum/species/fly, - "plasmaman" = /datum/species/plasmaman, - "xeno" = /datum/species/xeno, - "other" = /datum/species, - ) - var/list/stored_species = list() + "human", + "lizard", + "mammal", + "insect", + "fly", + "plasmaman", + "xeno", + "other", + ) var/obj/item/disk/data/dna_disk /obj/machinery/limbgrower/Initialize() create_reagents(100, OPENCONTAINER) stored_research = new /datum/techweb/specialized/autounlocking/limbgrower - for(var/i in categories) - var/species = categories[i] - stored_species[i] = new species() . = ..() + AddComponent(/datum/component/plumbing/simple_demand) + AddComponent(/datum/component/simple_rotation, ROTATION_WRENCH | ROTATION_CLOCKWISE, null, CALLBACK(src, .proc/can_be_rotated)) -/obj/machinery/limbgrower/ui_interact(mob/user) +/obj/machinery/limbgrower/ui_interact(mob/user, datum/tgui/ui) . = ..() - if(!is_operational()) - return + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Limbgrower", src) + ui.open() - var/dat = main_win(user) +/obj/machinery/limbgrower/ui_data(mob/user) + var/list/data = list() - switch(screen) - if(LIMBGROWER_MAIN_MENU) - dat = main_win(user) - if(LIMBGROWER_CATEGORY_MENU) - dat = category_win(user,selected_category) - if(LIMBGROWER_CHEMICAL_MENU) - dat = chemical_win(user) + for(var/datum/reagent/reagent_id in reagents.reagent_list) + var/list/reagent_data = list( + reagent_name = reagent_id.name, + reagent_amount = reagent_id.volume, + reagent_type = reagent_id.type + ) + data["reagents"] += list(reagent_data) - var/datum/browser/popup = new(user, "Limb Grower", name, 400, 500) - popup.set_content(dat) - popup.open() + data["total_reagents"] = reagents.total_volume + data["max_reagents"] = reagents.maximum_volume + data["busy"] = busy + var/list/disk_data = list() + disk_data["disk"] = dna_disk //Do i, the machine, have a disk? + disk_data["name"] = dna_disk?.fields["name"] //Name for the human saved if there is one + data["disk"] = disk_data + + return data + +/obj/machinery/limbgrower/ui_static_data(mob/user) + var/list/data = list() + data["categories"] = list() + + var/species_categories = categories.Copy() + for(var/species in species_categories) + species_categories[species] = list() + for(var/design_id in stored_research.researched_designs) + var/datum/design/limb_design = SSresearch.techweb_design_by_id(design_id) + for(var/found_category in species_categories) + if(found_category in limb_design.category) + species_categories[found_category] += limb_design + + for(var/category in species_categories) + var/list/category_data = list( + name = category, + designs = list(), + ) + for(var/datum/design/found_design in species_categories[category]) + var/list/all_reagents = list() + for(var/reagent_typepath in found_design.reagents_list) + var/datum/reagent/reagent_id = find_reagent_object_from_type(reagent_typepath) + var/list/reagent_data = list( + name = reagent_id.name, + amount = (found_design.reagents_list[reagent_typepath] * production_coefficient), + ) + all_reagents += list(reagent_data) + + category_data["designs"] += list(list( + parent_category = category, + name = found_design.name, + id = found_design.id, + needed_reagents = all_reagents, + )) + + data["categories"] += list(category_data) + + return data /obj/machinery/limbgrower/on_deconstruction() - for(var/obj/item/reagent_containers/glass/G in component_parts) - reagents.trans_to(G, G.reagents.maximum_volume) + for(var/obj/item/reagent_containers/glass/our_beaker in component_parts) + reagents.trans_to(our_beaker, our_beaker.reagents.maximum_volume) ..() -/obj/machinery/limbgrower/attackby(obj/item/O, mob/user, params) - if(busy) +/obj/machinery/limbgrower/attackby(obj/item/user_item, mob/living/user, params) + if (busy) to_chat(user, "\The [src] is busy. Please wait for completion of previous operation.") return - if(default_deconstruction_screwdriver(user, "limbgrower_panelopen", "limbgrower_idleoff", O)) - updateUsrDialog() + if(default_deconstruction_screwdriver(user, "limbgrower_panelopen", "limbgrower_idleoff", user_item)) + ui_close(user) return - if(panel_open && default_deconstruction_crowbar(O)) - return - - if(user.a_intent == INTENT_HARM) //so we can hit the machine + if(user_item.tool_behaviour == TOOL_WRENCH && panel_open) return ..() - if(istype(O, /obj/item/disk)) + if(panel_open && default_deconstruction_crowbar(user_item)) + return + + if(istype(user_item, /obj/item/disk)) if(dna_disk) to_chat(user, "\The [src] already has a dna disk, take it out first!") return else - O.forceMove(src) - dna_disk = O - to_chat(user, "You insert \the [O] into \the [src].") + user_item.forceMove(src) + dna_disk = user_item + to_chat(user, "You insert \the [user_item] into \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) return -/obj/machinery/limbgrower/Topic(href, href_list) - if(..()) + if(user.a_intent != INTENT_HELP) + return ..() + +/obj/machinery/limbgrower/proc/can_be_rotated() + if(panel_open) + return TRUE + return FALSE + +/obj/machinery/limbgrower/ui_act(action, list/params) + . = ..() + if(.) return - if (!busy) - if(href_list["menu"]) - screen = text2num(href_list["menu"]) - if(href_list["category"]) - selected_category = href_list["category"] + if (busy) + to_chat(usr, "\The [src] is busy. Please wait for completion of previous operation.") + return - if(href_list["disposeI"]) //Get rid of a reagent incase you add the wrong one by mistake - reagents.del_reagent(text2path(href_list["disposeI"])) + switch(action) - if(href_list["make"]) + if("empty_reagent") + reagents.del_reagent(text2path(params["reagent_type"])) + . = TRUE - ///////////////// - //href protection - being_built = stored_research.isDesignResearchedID(href_list["make"]) //check if it's a valid design + if("eject_disk") + eject_disk(usr) + + if("make_limb") + being_built = stored_research.isDesignResearchedID(params["design_id"]) if(!being_built) - return + CRASH("[src] was passed an invalid design id!") + /// All the reagents we're using to make our organ. + var/list/consumed_reagents_list = being_built.reagents_list.Copy() + /// The amount of power we're going to use, based on how much reagent we use. + var/power = 0 - var/synth_cost = being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff - var/power = max(2000, synth_cost/5) + for(var/reagent_id in consumed_reagents_list) + consumed_reagents_list[reagent_id] *= production_coefficient + if(!reagents.has_reagent(reagent_id, consumed_reagents_list[reagent_id])) + audible_message("\The [src] buzzes.") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + return - if(reagents.has_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)) - busy = TRUE - use_power(power) - flick("limbgrower_fill",src) - icon_state = "limbgrower_idleon" - addtimer(CALLBACK(src, .proc/build_item),32*prod_coeff) + power = max(2000, (power + consumed_reagents_list[reagent_id])) - if(href_list["dna_disk"]) - var/mob/living/carbon/user = usr - if(istype(user)) - if(!dna_disk) - var/obj/item/disk/diskette = user.get_active_held_item() - if(istype(diskette)) - diskette.forceMove(src) - dna_disk = diskette - to_chat(user, "You insert \the [diskette] into \the [src].") - else - dna_disk.forceMove(src.loc) - user.put_in_active_hand(dna_disk) - to_chat(user, "You remove \the [dna_disk] from \the [src].") - dna_disk = null - else - to_chat(user, "You are unable to grasp \the [dna_disk] disk from \the [src].") - else - to_chat(usr, "\The [src] is busy. Please wait for completion of previous operation.") + busy = TRUE + use_power(power) + flick("limbgrower_fill",src) + icon_state = "limbgrower_idleon" + selected_category = params["active_tab"] + addtimer(CALLBACK(src, .proc/build_item, consumed_reagents_list), production_speed * production_coefficient) + . = TRUE - updateUsrDialog() return -/obj/machinery/limbgrower/proc/build_item() - if(reagents.has_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)) //sanity check, if this happens we are in big trouble - reagents.remove_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff) - var/buildpath = being_built.build_path - if(ispath(buildpath, /obj/item/bodypart)) //This feels like spaghetti code, but i need to initiliaze a limb somehow - build_limb(buildpath) - else if(ispath(buildpath, /obj/item/organ/genital)) //genitals are uhh... customizable - build_genital(buildpath) - else - //Just build whatever it is - new buildpath(loc) - else - src.visible_message(" Something went very wrong and there isnt enough synthflesh anymore!") - busy = FALSE - flick("limbgrower_unfill",src) - icon_state = "limbgrower_idleoff" - updateUsrDialog() +/* + * The process of beginning to build a limb or organ. + * Goes through and sanity checks that we actually have enough reagent to build our item. + * Then, remove those reagents from our reagents datum. + * + * After the reagents are handled, we can proceede with making the limb or organ. (Limbs are handled in a separate proc) + * + * modified_consumed_reagents_list - the list of reagents we will consume on build, modified by the production coefficient. + */ +/obj/machinery/limbgrower/proc/build_item(list/modified_consumed_reagents_list) + for(var/reagent_id in modified_consumed_reagents_list) + if(!reagents.has_reagent(reagent_id, modified_consumed_reagents_list[reagent_id])) + audible_message("\The [src] buzzes.") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + break -/obj/machinery/limbgrower/proc/build_limb(buildpath) + reagents.remove_reagent(reagent_id, modified_consumed_reagents_list[reagent_id]) + + var/built_typepath = being_built.build_path + // If we have a bodypart, we need to initialize the limb on its own. Otherwise we can build it here. + if(ispath(built_typepath, /obj/item/bodypart)) + build_limb(built_typepath) + else if(ispath(built_typepath, /obj/item/organ/genital)) //genitals are uhh... customizable + build_genital(built_typepath) + else + new built_typepath(loc) + + busy = FALSE + flick("limbgrower_unfill", src) + icon_state = "limbgrower_idleoff" + +/* + * The process of putting together a limb. + * This is called from after we remove the reagents, so this proc is just initializing the limb type. + * + * This proc handles skin / mutant color, greyscaling, names and descriptions, and various other limb creation steps. + * + * built_typepath - the path of the bodypart we're building. + */ +/obj/machinery/limbgrower/proc/build_limb(built_typepath) //i need to create a body part manually using a set icon (otherwise it doesnt appear) var/obj/item/bodypart/limb - var/datum/species/selected = stored_species[selected_category] - limb = new buildpath(loc) + var/datum/species/selected = GLOB.species_datums[selected_category] + limb = new built_typepath(loc) limb.base_bp_icon = selected.icon_limbs || DEFAULT_BODYPART_ICON_ORGANIC limb.species_id = selected.limbs_id limb.color_src = (MUTCOLORS in selected.species_traits ? MUTCOLORS : (selected.use_skintones ? SKINTONE : FALSE)) @@ -189,135 +259,103 @@ BP.name = "\improper synthetic [lowertext(selected.name)] [limb.name]" BP.desc = "A synthetic [selected_category] limb that will morph on its first use in surgery. This one is for the [parse_zone(limb.body_zone)]." -/obj/machinery/limbgrower/proc/build_genital(buildpath) +/* + * Builds genitals, modifies to be the same + * as the person's cloning data on the data disk + */ +/obj/machinery/limbgrower/proc/build_genital(built_typepath) //i needed to create a way to customize gene tools using dna var/list/features = dna_disk?.fields["features"] if(length(features)) - switch(buildpath) + switch(built_typepath) if(/obj/item/organ/genital/penis) var/obj/item/organ/genital/penis/penis = new(loc) if(features["has_cock"]) penis.shape = features["cock_shape"] penis.length = features["cock_shape"] penis.diameter_ratio = features["cock_diameter_ratio"] - penis.color = sanitize_hexcolor(features["cock_color"], 6) - penis.update_icon() + penis.color = sanitize_hexcolor(features["cock_color"], 6, TRUE) + penis.update() if(/obj/item/organ/genital/testicles) var/obj/item/organ/genital/testicles/balls = new(loc) if(features["has_balls"]) - balls.color = sanitize_hexcolor(features["balls_color"], 6) + balls.color = sanitize_hexcolor(features["balls_color"], 6, TRUE) balls.shape = features["balls_shape"] balls.size = features["balls_size"] balls.fluid_rate = features["balls_cum_rate"] balls.fluid_mult = features["balls_cum_mult"] balls.fluid_efficiency = features["balls_efficiency"] + balls.update() if(/obj/item/organ/genital/vagina) var/obj/item/organ/genital/vagina/vegana = new(loc) - if(features["has_vagina"]) - vegana.color = sanitize_hexcolor(features["vag_color"], 6) + if(features["has_vag"]) + vegana.color = sanitize_hexcolor(features["vag_color"], 6, TRUE) vegana.shape = features["vag_shape"] + vegana.update() if(/obj/item/organ/genital/breasts) var/obj/item/organ/genital/breasts/boobs = new(loc) if(features["has_breasts"]) - boobs.color = sanitize_hexcolor(features["breasts_color"], 6) + boobs.color = sanitize_hexcolor(features["breasts_color"], 6, TRUE) boobs.size = features["breasts_size"] boobs.shape = features["breasts_shape"] if(!features["breasts_producing"]) boobs.genital_flags &= ~(GENITAL_FUID_PRODUCTION|CAN_CLIMAX_WITH|CAN_MASTURBATE_WITH) + boobs.update() else - new buildpath(loc) + new built_typepath(loc) else - new buildpath(loc) + new built_typepath(loc) /obj/machinery/limbgrower/RefreshParts() reagents.maximum_volume = 0 - for(var/obj/item/reagent_containers/glass/G in component_parts) - reagents.maximum_volume += G.volume - G.reagents.trans_to(src, G.reagents.total_volume) - var/T=1.2 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T -= M.rating*0.2 - prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4 + for(var/obj/item/reagent_containers/glass/our_beaker in component_parts) + reagents.maximum_volume += our_beaker.volume + our_beaker.reagents.trans_to(src, our_beaker.reagents.total_volume) + production_coefficient = 1.2 + for(var/obj/item/stock_parts/manipulator/our_manipulator in component_parts) + production_coefficient -= our_manipulator.rating * 0.2 + production_coefficient = clamp(production_coefficient, 0, 1) // coefficient goes from 1 -> 0.8 -> 0.6 -> 0.4 /obj/machinery/limbgrower/examine(mob/user) . = ..() + if(!panel_open) + . += "It looks like as if the panel were open you could rotate it with a wrench." + else + . += "The panel is open." if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Storing up to [reagents.maximum_volume]u of synthflesh.
Synthflesh consumption at [prod_coeff*100]%." + . += "The status display reads: Storing up to [reagents.maximum_volume]u of reagents.
Reagent consumption rate at [production_coefficient * 100]%.
" -/obj/machinery/limbgrower/proc/main_win(mob/user) - var/dat = "

[src] Menu:


" - dat += "[dna_disk ? "Remove" : "Insert"] cloning data disk" - dat += "
" - dat += "Chemical Storage" - dat += materials_printout() - dat += "" - - for(var/C in categories) - dat += "" - dat += "" - //one category per line - - dat += "
[C]
" - return dat - -/obj/machinery/limbgrower/proc/category_win(mob/user,selected_category) - var/dat = "Return to main menu" - dat += "

Browsing [selected_category]:


" - dat += materials_printout() - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category)) - continue - if(disabled || !can_build(D)) - dat += "[D.name]" - else - dat += "[D.name]" - dat += "[get_design_cost(D)]
" - - dat += "
" - return dat - - -/obj/machinery/limbgrower/proc/chemical_win(mob/user) - var/dat = "Return to main menu" - dat += "

Browsing Chemical Storage:


" - dat += materials_printout() - - for(var/datum/reagent/R in reagents.reagent_list) - dat += "[R.name]: [R.volume]" - dat += "Purge
" - - dat += "
" - return dat - -/obj/machinery/limbgrower/proc/materials_printout() - var/dat = "Total amount:> [reagents.total_volume] / [reagents.maximum_volume] cm3
" - return dat - -/obj/machinery/limbgrower/proc/can_build(datum/design/D) - return (reagents.has_reagent(/datum/reagent/medicine/synthflesh, D.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)) //Return whether the machine has enough synthflesh to produce the design - -/obj/machinery/limbgrower/proc/get_design_cost(datum/design/D) - var/dat - if(D.reagents_list[/datum/reagent/medicine/synthflesh]) - dat += "[D.reagents_list[/datum/reagent/medicine/synthflesh] * prod_coeff] Synthetic flesh " - return dat +/* + * Checks our reagent list to see if a design can be built. + * + * limb_design - the design we're checking for buildability. + * + * returns TRUE if we have enough reagent to build it. Returns FALSE if we do not. + */ +/obj/machinery/limbgrower/proc/can_build(datum/design/limb_design) + for(var/datum/reagent/reagent_id in limb_design.reagents_list) + if(!reagents.has_reagent(reagent_id, limb_design.reagents_list[reagent_id] * production_coefficient)) + return FALSE + return TRUE +/// Emagging a limbgrower allows you to build synthetic armblades. /obj/machinery/limbgrower/emag_act(mob/user) . = ..() if(obj_flags & EMAGGED) return - for(var/id in SSresearch.techweb_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(id) - if((D.build_type & LIMBGROWER) && ("emagged" in D.category)) - stored_research.add_design(D) + for(var/design_id in SSresearch.techweb_designs) + var/datum/design/found_design = SSresearch.techweb_design_by_id(design_id) + if((found_design.build_type & LIMBGROWER) && ("emagged" in found_design.category)) + stored_research.add_design(found_design) to_chat(user, "A warning flashes onto the screen, stating that safety overrides have been deactivated!") obj_flags |= EMAGGED - return TRUE + update_static_data(user) /obj/machinery/limbgrower/AltClick(mob/living/user) . = ..() + eject_disk(user) + +/obj/machinery/limbgrower/proc/eject_disk(mob/user) if(istype(user) && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) if(busy) to_chat(user, "\The [src] is busy. Please wait for completion of previous operation.") @@ -326,6 +364,7 @@ dna_disk.forceMove(src.loc) user.put_in_active_hand(dna_disk) to_chat(user, "You remove \the [dna_disk] from \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) dna_disk = null else to_chat(user, "\The [src] has doesn't have a disk on it!") diff --git a/code/game/objects/items/airlock_painter.dm b/code/game/objects/items/airlock_painter.dm index b28de437ed..8edd1c2a76 100644 --- a/code/game/objects/items/airlock_painter.dm +++ b/code/game/objects/items/airlock_painter.dm @@ -157,7 +157,7 @@ to_chat(user, "You need to get closer!") return if(use_paint(user) && isturf(F)) - F.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', stored_decal_total, turn(stored_dir, -dir2angle(F.dir)), CLEAN_STRONG, color, null, null, alpha) + F.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, CLEAN_STRONG, color, null, null, alpha) /obj/item/airlock_painter/decal/attack_self(mob/user) if((ink) && (ink.charges >= 1)) @@ -180,6 +180,11 @@ stored_decal_total = "[stored_decal][yellow_fix][stored_color]" return +/obj/item/airlock_painter/decal/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/decals) + ) + /obj/item/airlock_painter/decal/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) @@ -189,6 +194,7 @@ /obj/item/airlock_painter/decal/ui_data(mob/user) var/list/data = list() data["decal_direction"] = stored_dir + data["decal_dir_text"] = dir2text(stored_dir) data["decal_color"] = stored_color data["decal_style"] = stored_decal data["decal_list"] = list() diff --git a/code/game/objects/items/chromosome.dm b/code/game/objects/items/chromosome.dm index 3acf3cfe5c..f5b693879b 100644 --- a/code/game/objects/items/chromosome.dm +++ b/code/game/objects/items/chromosome.dm @@ -75,18 +75,3 @@ desc = "A chromosome that reduces action based mutation cooldowns by by 50%." icon_state = "energy" energy_coeff = 0.5 - -/obj/item/chromosome/reinforcer - name = "reinforcement chromosome" - desc = "A chromosome that renders mutations immune to mutadone." - icon_state = "reinforcer" - weight = 3 - -/obj/item/chromosome/reinforcer/can_apply(datum/mutation/human/HM) - if(!HM || !(HM.can_chromosome == CHROMOSOME_NONE)) - return FALSE - return !HM.mutadone_proof - -/obj/item/chromosome/reinforcer/apply(datum/mutation/human/HM) - HM.mutadone_proof = TRUE - ..() diff --git a/code/game/objects/items/miscellaneous.dm b/code/game/objects/items/miscellaneous.dm index 224c4ffb9b..80466832a6 100644 --- a/code/game/objects/items/miscellaneous.dm +++ b/code/game/objects/items/miscellaneous.dm @@ -135,8 +135,8 @@ /obj/item/organ/cyberimp/arm/toolset, /obj/item/organ/cyberimp/arm/surgery, /obj/item/organ/cyberimp/chest/thrusters, - /obj/item/organ/lungs/cybernetic, - /obj/item/organ/liver/cybernetic) //cyberimplants range from a nice bonus to fucking broken bullshit so no subtypesof + /obj/item/organ/lungs/cybernetic/tier3, + /obj/item/organ/liver/cybernetic/tier3) //cyberimplants range from a nice bonus to fucking broken bullshit so no subtypesof for(var/V in templist) var/atom/A = V augment_list[initial(A.name)] = A diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index f22ceb6a2d..96eb4e4a0f 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -93,7 +93,7 @@ GLOBAL_PROTECT(admin_verbs_ban) GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_sound, /client/proc/manual_play_web_sound, /client/proc/set_round_end_sound)) GLOBAL_PROTECT(admin_verbs_sounds) GLOBAL_LIST_INIT(admin_verbs_fun, list( - /client/proc/cmd_admin_dress, + /client/proc/cmd_select_equipment, /client/proc/cmd_admin_gib_self, /client/proc/drop_bomb, /client/proc/set_dynex_scale, @@ -232,7 +232,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list( /client/proc/play_local_sound, /client/proc/play_sound, /client/proc/set_round_end_sound, - /client/proc/cmd_admin_dress, + /client/proc/cmd_select_equipment, /client/proc/cmd_admin_gib_self, /client/proc/drop_bomb, /client/proc/drop_dynex_bomb, diff --git a/code/modules/admin/outfit_editor.dm b/code/modules/admin/outfit_editor.dm new file mode 100644 index 0000000000..9a99d8b20e --- /dev/null +++ b/code/modules/admin/outfit_editor.dm @@ -0,0 +1,196 @@ + +/client/proc/open_outfit_editor(datum/outfit/target) + var/datum/outfit_editor/ui = new(usr, target) + ui.ui_interact(usr) + +#define OUTFIT_EDITOR_NAME "Outfit-O-Tron 9000" +/datum/outfit_editor + var/client/owner + + var/dummy_key + + var/datum/outfit/drip + +/datum/outfit_editor/New(user, datum/outfit/target) + owner = CLIENT_FROM_VAR(user) + + if(ispath(target)) + drip = new /datum/outfit + drip.copy_from(new target) + else if(istype(target)) + drip = target + else + drip = new /datum/outfit + drip.name = "New Outfit" + +/datum/outfit_editor/ui_state(mob/user) + return GLOB.admin_state + +/datum/outfit_editor/ui_status(mob/user, datum/ui_state/state) + if(QDELETED(drip)) + return UI_CLOSE + return ..() + +/datum/outfit_editor/ui_close(mob/user) + clear_human_dummy(dummy_key) + qdel(src) + +/datum/outfit_editor/proc/init_dummy() + dummy_key = "outfit_editor_[owner]" + generate_dummy_lookalike(dummy_key, owner.mob) + unset_busy_human_dummy(dummy_key) + +/datum/outfit_editor/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "OutfitEditor", OUTFIT_EDITOR_NAME) + ui.open() + ui.set_autoupdate(FALSE) + +/datum/outfit_editor/proc/entry(data) + if(ispath(data, /obj/item)) + var/obj/item/item = data + return list( + "path" = item, + "name" = initial(item.name), + "desc" = initial(item.desc), + // at this point initializing the item is probably faster tbh + "sprite" = icon2base64(icon(initial(item.icon), initial(item.icon_state))), + ) + + return data + +/datum/outfit_editor/proc/serialize_outfit() + var/list/outfit_slots = drip.get_json_data() + . = list() + for(var/key in outfit_slots) + var/val = outfit_slots[key] + . += list("[key]" = entry(val)) + +/datum/outfit_editor/ui_data(mob/user) + var/list/data = list() + + data["outfit"] = serialize_outfit() + data["saveable"] = !GLOB.custom_outfits.Find(drip) + + if(!dummy_key) + init_dummy() + var/icon/dummysprite = get_flat_human_icon(null, + dummy_key = dummy_key, + showDirs = list(SOUTH), + outfit_override = drip) + data["dummy64"] = icon2base64(dummysprite) + + return data + + +/datum/outfit_editor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + if(..()) + return + . = TRUE + + var/slot = params["slot"] + switch(action) + if("click") + choose_item(slot) + if("ctrlClick") + choose_any_item(slot) + if("clear") + if(drip.vars.Find(slot)) + drip.vars[slot] = null + + if("rename") + var/newname = stripped_input(owner, "What do you want to name this outfit?", OUTFIT_EDITOR_NAME) + if(newname) + drip.name = newname + if("save") + GLOB.custom_outfits |= drip + SStgui.update_user_uis(owner.mob) + if("delete") + GLOB.custom_outfits -= drip + SStgui.update_user_uis(owner.mob) + if("vv") + owner.debug_variables(drip) + + +/datum/outfit_editor/proc/set_item(slot, obj/item/choice) + if(!choice) + return + if(!ispath(choice)) + alert(owner, "Invalid item", OUTFIT_EDITOR_NAME, "oh no") + return + if(initial(choice.icon_state) == null) //hacky check copied from experimentor code + var/msg = "Warning: This item's icon_state is null, indicating it is very probably not actually a usable item." + if(alert(owner, msg, OUTFIT_EDITOR_NAME, "Use it anyway", "Cancel") != "Use it anyway") + return + + if(drip.vars.Find(slot)) + drip.vars[slot] = choice + +/datum/outfit_editor/proc/choose_any_item(slot) + var/obj/item/choice = pick_closest_path(FALSE) + + if(!choice) + return + + set_item(slot, choice) + +//this proc will try to give a good selection of items that the user can choose from +//it does *not* give a selection of all items that can fit in a slot because lag; +//most notably the hand and pocket slots because they accept pretty much anything +//also stuff that fits in the belt and back slots are scattered pretty much all over the place +/datum/outfit_editor/proc/choose_item(slot) + var/list/options = list() + + switch(slot) + if("head") + options = typesof(/obj/item/clothing/head) + if("glasses") + options = typesof(/obj/item/clothing/glasses) + if("ears") + options = typesof(/obj/item/radio/headset) + + if("neck") + options = typesof(/obj/item/clothing/neck) + if("mask") + options = typesof(/obj/item/clothing/mask) + + if("uniform") + options = typesof(/obj/item/clothing/under) + if("suit") + options = typesof(/obj/item/clothing/suit) + if("gloves") + options = typesof(/obj/item/clothing/gloves) + + if("suit_store") + var/obj/item/clothing/suit/suit = drip.suit + if(suit) + suit = new suit //initial() doesn't like lists + options = suit.allowed + if(!options.len) //nothing will happen, but don't let the user think it's broken + to_chat(owner, "No options available for the current suit.") + + if("belt") + options = typesof(/obj/item/storage/belt) + if("id") + options = typesof(/obj/item/card/id) + + if("l_hand") + choose_any_item(slot) + if("back") + options = typesof(/obj/item/storage/backpack) + if("r_hand") + choose_any_item(slot) + + if("l_pocket") + choose_any_item(slot) + if("shoes") + options = typesof(/obj/item/clothing/shoes) + if("r_pocket") + choose_any_item(slot) + + if(length(options)) + set_item(slot, tgui_input_list(owner, "Choose an item", OUTFIT_EDITOR_NAME, options)) + + +#undef OUTFIT_EDITOR_NAME diff --git a/code/modules/admin/outfit_manager.dm b/code/modules/admin/outfit_manager.dm new file mode 100644 index 0000000000..9d20b64547 --- /dev/null +++ b/code/modules/admin/outfit_manager.dm @@ -0,0 +1,73 @@ +/client/proc/outfit_manager() + set category = "Debug" + set name = "Outfit Manager" + + if(!check_rights(R_DEBUG)) + return + var/datum/outfit_manager/ui = new(usr) + ui.ui_interact(usr) + + +/datum/outfit_manager + var/client/owner + +/datum/outfit_manager/New(user) + owner = CLIENT_FROM_VAR(user) + +/datum/outfit_manager/ui_state(mob/user) + return GLOB.admin_state + +/datum/outfit_manager/ui_close(mob/user) + qdel(src) + +/datum/outfit_manager/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "OutfitManager") + ui.open() + +/datum/outfit_manager/proc/entry(datum/outfit/outfit) + var/vv = FALSE + var/datum/outfit/varedit/varoutfit = outfit + if(istype(varoutfit)) + vv = length(varoutfit.vv_values) + return list( + "name" = "[outfit.name] [vv ? "(VV)" : ""]", + "ref" = REF(outfit), + ) + +/datum/outfit_manager/ui_data(mob/user) + var/list/data = list() + + var/list/outfits = list() + for(var/datum/outfit/custom_outfit in GLOB.custom_outfits) + outfits += list(entry(custom_outfit)) + data["outfits"] = outfits + + return data + +/datum/outfit_manager/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + if(..()) + return + . = TRUE + + switch(action) + if("new") + owner.open_outfit_editor(new /datum/outfit) + if("load") + owner.holder.load_outfit(owner.mob) + if("copy") + var/datum/outfit/outfit = tgui_input_list(owner, "Pick an outfit to copy from", "Outfit Manager", subtypesof(/datum/outfit)) + if(ispath(outfit)) + owner.open_outfit_editor(new outfit) + + var/datum/outfit/target_outfit = locate(params["outfit"]) + if(!istype(target_outfit)) + return + switch(action) //wow we're switching through action again this is horrible optimization smh + if("edit") + owner.open_outfit_editor(target_outfit) + if("save") + owner.holder.save_outfit(owner.mob, target_outfit) + if("delete") + owner.holder.delete_outfit(owner.mob, target_outfit) diff --git a/code/modules/admin/outfits.dm b/code/modules/admin/outfits.dm new file mode 100644 index 0000000000..1b615e3d62 --- /dev/null +++ b/code/modules/admin/outfits.dm @@ -0,0 +1,32 @@ +GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits + +/datum/admins/proc/save_outfit(mob/admin, datum/outfit/O) + O.save_to_file(admin) + SStgui.update_user_uis(admin) + +/datum/admins/proc/delete_outfit(mob/admin, datum/outfit/O) + GLOB.custom_outfits -= O + qdel(O) + to_chat(admin,"Outfit deleted.") + SStgui.update_user_uis(admin) + +/datum/admins/proc/load_outfit(mob/admin) + var/outfit_file = input("Pick outfit json file:", "File") as null|file + if(!outfit_file) + return + var/filedata = file2text(outfit_file) + var/json = json_decode(filedata) + if(!json) + to_chat(admin,"JSON decode error.") + return + var/otype = text2path(json["outfit_type"]) + if(!ispath(otype,/datum/outfit)) + to_chat(admin,"Malformed/Outdated file.") + return + var/datum/outfit/O = new otype + if(!O.load_from(json)) + to_chat(admin,"Malformed/Outdated file.") + return + GLOB.custom_outfits += O + SStgui.update_user_uis(admin) + diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 2c1ddc4e0a..a123d73e62 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -484,74 +484,52 @@ set name = "Test Areas (ALL)" cmd_admin_areatest(FALSE) -/client/proc/cmd_admin_dress(mob/M in GLOB.mob_list) - set category = "Admin.Events" - set name = "Select equipment" - if(!(ishuman(M) || isobserver(M))) - alert("Invalid mob") - return - - var/dresscode = robust_dress_shop() - - if(!dresscode) - return - - var/delete_pocket - var/mob/living/carbon/human/H - if(isobserver(M)) - H = M.change_mob_type(/mob/living/carbon/human, null, null, TRUE) - else - H = M - if(alert("Drop Items in Pockets? No will delete them.", "Robust quick dress shop", "Yes", "No") == "No") - delete_pocket = TRUE - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Select Equipment") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - for (var/obj/item/I in H.get_equipped_items(delete_pocket)) - qdel(I) - if(dresscode != "Naked") - H.equipOutfit(dresscode) - - H.regenerate_icons() - - log_admin("[key_name(usr)] changed the equipment of [key_name(H)] to [dresscode].") - message_admins("[key_name_admin(usr)] changed the equipment of [ADMIN_LOOKUPFLW(H)] to [dresscode].") - /client/proc/robust_dress_shop() - var/list/outfits = list("Cancel","Naked","Custom","As Job...") - var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job) + + var/list/baseoutfits = list("Naked","Custom","As Job...", "As Plasmaman...") + var/list/outfits = list() + var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job) - typesof(/datum/outfit/plasmaman) + for(var/path in paths) var/datum/outfit/O = path //not much to initalize here but whatever - if(initial(O.can_be_admin_equipped)) - outfits[initial(O.name)] = path + outfits[initial(O.name)] = path - var/dresscode = input("Select outfit", "Robust quick dress shop") as null|anything in outfits + var/dresscode = input("Select outfit", "Robust quick dress shop") as null|anything in baseoutfits + sortList(outfits) if (isnull(dresscode)) return if (outfits[dresscode]) dresscode = outfits[dresscode] - if(dresscode == "Cancel") - return - if (dresscode == "As Job...") var/list/job_paths = subtypesof(/datum/outfit/job) var/list/job_outfits = list() for(var/path in job_paths) var/datum/outfit/O = path - if(initial(O.can_be_admin_equipped)) - job_outfits[initial(O.name)] = path + job_outfits[initial(O.name)] = path - dresscode = input("Select job equipment", "Robust quick dress shop") as null|anything in job_outfits + dresscode = input("Select job equipment", "Robust quick dress shop") as null|anything in sortList(job_outfits) dresscode = job_outfits[dresscode] if(isnull(dresscode)) return + if (dresscode == "As Plasmaman...") + var/list/plasmaman_paths = typesof(/datum/outfit/plasmaman) + var/list/plasmaman_outfits = list() + for(var/path in plasmaman_paths) + var/datum/outfit/O = path + plasmaman_outfits[initial(O.name)] = path + + dresscode = input("Select plasmeme equipment", "Robust quick dress shop") as null|anything in sortList(plasmaman_outfits) + dresscode = plasmaman_outfits[dresscode] + if(isnull(dresscode)) + return + if (dresscode == "Custom") var/list/custom_names = list() for(var/datum/outfit/D in GLOB.custom_outfits) custom_names[D.name] = D - var/selected_name = input("Select outfit", "Robust quick dress shop") as null|anything in custom_names + var/selected_name = input("Select outfit", "Robust quick dress shop") as null|anything in sortList(custom_names) dresscode = custom_names[selected_name] if(isnull(dresscode)) return diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 199004abed..7e7bac9ff0 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -879,8 +879,6 @@ Traitors and the like can also be revived with the previous role mostly intact. message_admins("[ADMIN_LOOKUPFLW(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [ADMIN_VERBOSEJMP(N)].") SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Nuke", "[N.timing]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits - /client/proc/create_outfits() set category = "Debug" set name = "Create Custom Outfit" diff --git a/code/modules/admin/verbs/selectequipment.dm b/code/modules/admin/verbs/selectequipment.dm new file mode 100644 index 0000000000..eb75df9ac1 --- /dev/null +++ b/code/modules/admin/verbs/selectequipment.dm @@ -0,0 +1,227 @@ +/client/proc/cmd_select_equipment(mob/target in GLOB.mob_list) + set category = "Admin.Events" + set name = "Select equipment" + + + var/datum/select_equipment/ui = new(usr, target) + ui.ui_interact(usr) + +/* + * This is the datum housing the select equipment UI. + * + * You may notice some oddities about the way outfits are passed to the UI and vice versa here. + * That's because it handles both outfit typepaths (for normal outfits) *and* outfit objects (for custom outfits). + * + * Custom outfits need to be objects as they're created in runtime. + * "Then just handle the normal outfits as objects too and simplify the handling" - you may say. + * There are about 300 outfit types at the time of writing this. Initializing all of these to objects would be a huge waste. + * + */ + +/datum/select_equipment + var/client/user + var/mob/target_mob + + var/dummy_key + + //static list to share all the outfit typepaths between all instances of this datum. + var/static/list/cached_outfits + + //a typepath if the selected outfit is a normal outfit; + //an object if the selected outfit is a custom outfit + var/datum/outfit/selected_outfit = /datum/outfit + //serializable string for the UI to keep track of which outfit is selected + var/selected_identifier = "/datum/outfit" + +/datum/select_equipment/New(_user, mob/target) + user = CLIENT_FROM_VAR(_user) + + if(!ishuman(target) && !isobserver(target)) + alert("Invalid mob") + return + target_mob = target + +/datum/select_equipment/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SelectEquipment", "Select Equipment") + ui.open() + ui.set_autoupdate(FALSE) + +/datum/select_equipment/ui_state(mob/user) + return GLOB.admin_state + +/datum/select_equipment/ui_status(mob/user, datum/ui_state/state) + if(QDELETED(target_mob)) + return UI_CLOSE + return ..() + +/datum/select_equipment/ui_close(mob/user) + clear_human_dummy(dummy_key) + qdel(src) + +/datum/select_equipment/proc/init_dummy() + dummy_key = "selectequipmentUI_[target_mob]" + generate_dummy_lookalike(dummy_key, target_mob) + unset_busy_human_dummy(dummy_key) + return + +/** + * Packs up data about an outfit as an assoc list to send to the UI as an outfit entry. + * + * Args: + * * category (string) - The tab it will be under + * + * * identifier (typepath or ref) - This will sent this back to ui_act to preview or spawn in an outfit. + * * Must be unique between all entries. + * + * * name (string) - Will be the text on the button + * + * * priority (bool)(optional) - If True, the UI will sort the entry to the top, right below favorites. + * + * * custom_entry (bool)(optional) - Send the identifier with a "ref" keyword instead of "path", + * * for the UI to tell apart custom outfits from normal ones. + * + * Returns (list) An outfit entry + */ + +/datum/select_equipment/proc/outfit_entry(category, identifier, name, priority=FALSE, custom_entry=FALSE) + if(custom_entry) + return list("category" = category, "ref" = identifier, "name" = name, "priority" = priority) + return list("category" = category, "path" = identifier, "name" = name, "priority" = priority) + +/datum/select_equipment/proc/make_outfit_entries(category="General", list/outfit_list) + var/list/entries = list() + for(var/path as anything in outfit_list) + var/datum/outfit/outfit = path + entries += list(outfit_entry(category, path, initial(outfit.name))) + return entries + +//GLOB.custom_outfits lists outfit *objects* so we'll need to do some custom handling for it +/datum/select_equipment/proc/make_custom_outfit_entries(list/outfit_list) + var/list/entries = list() + for(var/datum/outfit/outfit as anything in outfit_list) + entries += list(outfit_entry("Custom", REF(outfit), outfit.name, custom_entry=TRUE)) //it's either this or special handling on the UI side + return entries + +/datum/select_equipment/ui_data(mob/user) + var/list/data = list() + if(!dummy_key) + init_dummy() + + var/icon/dummysprite = get_flat_human_icon(null, + dummy_key = dummy_key, + outfit_override = selected_outfit, + no_anim = TRUE) + data["icon64"] = icon2base64(dummysprite) + data["name"] = target_mob + + var/datum/preferences/prefs = user?.client?.prefs + data["favorites"] = list() + if(prefs) + data["favorites"] = prefs.favorite_outfits + + var/list/custom + custom += make_custom_outfit_entries(GLOB.custom_outfits) + data["custom_outfits"] = custom + data["current_outfit"] = selected_identifier + return data + + +/datum/select_equipment/ui_static_data(mob/user) + var/list/data = list() + if(!cached_outfits) + cached_outfits = list() + cached_outfits += list(outfit_entry("General", /datum/outfit, "Naked", priority=TRUE)) + cached_outfits += make_outfit_entries("General", subtypesof(/datum/outfit) - typesof(/datum/outfit/job) - typesof(/datum/outfit/plasmaman)) + cached_outfits += make_outfit_entries("Jobs", typesof(/datum/outfit/job)) + cached_outfits += make_outfit_entries("Plasmamen Outfits", typesof(/datum/outfit/plasmaman)) + + data["outfits"] = cached_outfits + return data + + +/datum/select_equipment/proc/resolve_outfit(text) + + var/path = text2path(text) + if(ispath(path, /datum/outfit)) + return path + + else //don't bail yet - could be a custom outfit + var/datum/outfit/custom_outfit = locate(text) + if(istype(custom_outfit)) + return custom_outfit + + +/datum/select_equipment/ui_act(action, params) + if(..()) + return + . = TRUE + switch(action) + if("preview") + var/datum/outfit/new_outfit = resolve_outfit(params["path"]) + + if(ispath(new_outfit)) //got a typepath - that means we're dealing with a normal outfit + selected_identifier = new_outfit //these are keyed by type + //by the way, no, they can't be keyed by name because many of them have duplicate names + + else if(istype(new_outfit)) //got an initialized object - means it's a custom outfit + selected_identifier = REF(new_outfit) //and the outfit will be keyed by its ref (cause its type will always be /datum/outfit) + + else //we got nothing and should bail + return + + selected_outfit = new_outfit + + if("applyoutfit") + var/datum/outfit/new_outfit = resolve_outfit(params["path"]) + if(new_outfit && ispath(new_outfit)) //initialize it + new_outfit = new new_outfit + if(!istype(new_outfit)) + return + user.admin_apply_outfit(target_mob, new_outfit) + + if("customoutfit") + user.outfit_manager() + + if("togglefavorite") + var/datum/outfit/outfit_path = resolve_outfit(params["path"]) + if(!ispath(outfit_path)) //we do *not* want custom outfits (i.e objects) here, they're not even persistent + return + + if(user.prefs.favorite_outfits.Find(outfit_path)) //already there, remove it + user.prefs.favorite_outfits -= outfit_path + else //not there, add it + user.prefs.favorite_outfits += outfit_path + user.prefs.save_preferences() + +/client/proc/admin_apply_outfit(mob/target, dresscode) + if(!ishuman(target) && !isobserver(target)) + alert("Invalid mob") + return + + if(!dresscode) + return + + var/delete_pocket + var/mob/living/carbon/human/human_target + if(isobserver(target)) + human_target = target.change_mob_type(/mob/living/carbon/human, delete_old_mob = TRUE) + else + human_target = target + if(human_target.l_store || human_target.r_store || human_target.s_store) //saves a lot of time for admins and coders alike + if(alert("Drop Items in Pockets? No will delete them.", "Robust quick dress shop", "Yes", "No") == "No") + delete_pocket = TRUE + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Select Equipment") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + for(var/obj/item/item in human_target.get_equipped_items(delete_pocket)) + qdel(item) + if(dresscode != "Naked") + human_target.equipOutfit(dresscode) + + human_target.regenerate_icons() + + log_admin("[key_name(usr)] changed the equipment of [key_name(human_target)] to [dresscode].") + message_admins("[key_name_admin(usr)] changed the equipment of [ADMIN_LOOKUPFLW(human_target)] to [dresscode].") + + return dresscode diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index e32fcee639..8eedd640a7 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -345,6 +345,14 @@ InsertAll("", each, GLOB.alldirs) ..() +/datum/asset/spritesheet/decals + name = "decals" + +/datum/asset/spritesheet/decals/register() + for(var/each in list('icons/turf/decals.dmi')) + InsertAll("", each, GLOB.alldirs) + ..() + /datum/asset/spritesheet/supplypods name = "supplypods" diff --git a/code/modules/cargo/exports/organs_robotics.dm b/code/modules/cargo/exports/organs_robotics.dm index b65cf28949..08340e6a56 100644 --- a/code/modules/cargo/exports/organs_robotics.dm +++ b/code/modules/cargo/exports/organs_robotics.dm @@ -75,7 +75,7 @@ cost = 250 unit_name = "heart" export_types = list(/obj/item/organ/heart) - exclude_types = list(/obj/item/organ/heart/cursed, /obj/item/organ/heart/cybernetic) + exclude_types = list(/obj/item/organ/heart/cursed, /obj/item/organ/heart/cybernetic/tier2, /obj/item/organ/heart/cybernetic/tier3) /datum/export/organs/tongue cost = 75 @@ -92,29 +92,30 @@ cost = 50 //can be replaced unit_name = "stomach" export_types = list(/obj/item/organ/stomach) + exclude_types = list(/obj/item/organ/stomach/cybernetic/tier2, /obj/item/organ/stomach/cybernetic/tier3) /datum/export/organs/lungs cost = 150 unit_name = "lungs" - export_types = list(/obj/item/organ/lungs) - exclude_types = list(/obj/item/organ/lungs/cybernetic, /obj/item/organ/lungs/cybernetic/upgraded) + export_types = list(/obj/item/organ/lungs,) + exclude_types = list(/obj/item/organ/lungs/cybernetic/tier2, /obj/item/organ/lungs/cybernetic/tier3) /datum/export/organs/liver cost = 175 unit_name = "liver" export_types = list(/obj/item/organ/liver) - exclude_types = list(/obj/item/organ/liver/cybernetic, /obj/item/organ/liver/cybernetic/upgraded) + exclude_types = list(/obj/item/organ/liver/cybernetic/tier2, /obj/item/organ/liver/cybernetic/tier3) /datum/export/organs/cybernetic cost = 225 unit_name = "cybernetic organ" - export_types = list(/obj/item/organ/liver/cybernetic, /obj/item/organ/lungs/cybernetic, /obj/item/organ/eyes/robotic, /obj/item/organ/heart/cybernetic) - exclude_types = list(/obj/item/organ/lungs/cybernetic/upgraded, /obj/item/organ/liver/cybernetic/upgraded) + export_types = list(/obj/item/organ/liver/cybernetic/tier2, /obj/item/organ/lungs/cybernetic/tier2, /obj/item/organ/eyes/robotic/shield, /obj/item/organ/eyes/robotic/glow, /obj/item/organ/stomach/cybernetic/tier2, /obj/item/organ/heart/cybernetic/tier2) + exclude_types = list(/obj/item/organ/liver/cybernetic/tier3, /obj/item/organ/lungs/cybernetic/tier3, /obj/item/organ/eyes/robotic/xray, /obj/item/organ/eyes/robotic/thermals, /obj/item/organ/stomach/cybernetic/tier3, /obj/item/organ/heart/cybernetic/tier3) /datum/export/organs/upgraded cost = 275 unit_name = "upgraded cybernetic organ" - export_types = list(/obj/item/organ/lungs/cybernetic/upgraded, /obj/item/organ/liver/cybernetic/upgraded) + export_types = list(/obj/item/organ/liver/cybernetic/tier3, /obj/item/organ/lungs/cybernetic/tier3, /obj/item/organ/eyes/robotic/xray, /obj/item/organ/eyes/robotic/thermals, /obj/item/organ/stomach/cybernetic/tier3, /obj/item/organ/heart/cybernetic/tier3) /datum/export/organs/tail // yeah have fun pulling this off someone without catching a bwoink cost = 500 diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm index cf9cc5e0d1..8fc1275e4a 100644 --- a/code/modules/cargo/packs/security.dm +++ b/code/modules/cargo/packs/security.dm @@ -80,15 +80,15 @@ /datum/supply_pack/security/russianclothing name = "Russian Surplus Clothing" - desc = "An old russian crate full of surplus armor that they used to use! Has two sets of bulletproff armor, a few union suits and some warm hats!" + desc = "An old russian crate full of surplus armor that they used to use! Has two sets of bulletproof armor, a few union suits and some warm hats!" contraband = TRUE cost = 5750 // Its basicly sec suits, good boots/gloves - contains = list(/obj/item/clothing/suit/armor/navyblue/russian, - /obj/item/clothing/suit/armor/navyblue/russian, + contains = list(/obj/item/clothing/under/syndicate/rus_army, + /obj/item/clothing/under/syndicate/rus_army, /obj/item/clothing/shoes/combat, /obj/item/clothing/shoes/combat, - /obj/item/clothing/head/ushanka, - /obj/item/clothing/head/ushanka, + /obj/item/clothing/head/helmet/rus_helmet, + /obj/item/clothing/head/helmet/rus_helmet, /obj/item/clothing/suit/armor/bulletproof, /obj/item/clothing/suit/armor/bulletproof, /obj/item/clothing/head/helmet/alt, @@ -98,23 +98,21 @@ /obj/item/clothing/mask/gas, /obj/item/clothing/mask/gas) crate_name = "surplus russian clothing" - crate_type = /obj/structure/closet/crate/internals /datum/supply_pack/security/russian_partisan name = "Russian Partisan Gear" desc = "An old russian partisan equipment crate, comes with a full russian outfit, a loaded surplus rifle and a second magazine." contraband = TRUE - access = FALSE cost = 6500 contains = list(/obj/item/clothing/suit/armor/navyblue/russian, /obj/item/clothing/shoes/combat, - /obj/item/clothing/head/ushanka, + /obj/item/clothing/head/helmet/rus_helmet, /obj/item/clothing/suit/armor/bulletproof, /obj/item/clothing/head/helmet/alt, /obj/item/clothing/gloves/tackler/combat/insulated, + /obj/item/clothing/under/syndicate/rus_army, /obj/item/clothing/mask/gas) crate_name = "surplus russian gear" - crate_type = /obj/structure/closet/crate/internals /datum/supply_pack/security/russian_partisan/fill(obj/structure/closet/crate/C) ..() @@ -241,7 +239,7 @@ access = FALSE access_any = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS) contains = list(/obj/item/ammo_box/c38/dumdum) - crate_name = ".38 match crate" + crate_name = ".38 dumdum crate" /datum/supply_pack/security/match name = ".38 Match Grade Speedloader" diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 65809383c4..f5c1e5f254 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -227,6 +227,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/persistent_scars = TRUE ///If we want to broadcast deadchat connect/disconnect messages var/broadcast_login_logout = TRUE + ///What outfit typepaths we've favorited in the SelectEquipment menu + var/list/favorite_outfits = list() /// We have 5 slots for persistent scars, if enabled we pick a random one to load (empty by default) and scars at the end of the shift if we survived as our original person var/list/scars_list = list("1" = "", "2" = "", "3" = "", "4" = "", "5" = "") /// Which of the 5 persistent scar slots we randomly roll to load for this round, if enabled. Actually rolled in [/datum/preferences/proc/load_character(slot)] diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 0367fc9471..7c93bec600 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -406,6 +406,15 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["auto_ooc"] >> auto_ooc S["no_tetris_storage"] >> no_tetris_storage + //favorite outfits + S["favorite_outfits"] >> favorite_outfits + + var/list/parsed_favs = list() + for(var/typetext in favorite_outfits) + var/datum/outfit/path = text2path(typetext) + if(ispath(path)) //whatever typepath fails this check probably doesn't exist anymore + parsed_favs += path + favorite_outfits = uniqueList(parsed_favs) //try to fix any outdated data if necessary if(needs_update >= 0) @@ -455,6 +464,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car no_tetris_storage = sanitize_integer(no_tetris_storage, 0, 1, initial(no_tetris_storage)) key_bindings = sanitize_islist(key_bindings, list()) modless_key_bindings = sanitize_islist(modless_key_bindings, list()) + favorite_outfits = SANITIZE_LIST(favorite_outfits) verify_keybindings_valid() // one of these days this will runtime and you'll be glad that i put it in a different proc so no one gets their saves wiped @@ -556,6 +566,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["pda_skin"], pda_skin) WRITE_FILE(S["key_bindings"], key_bindings) WRITE_FILE(S["modless_key_bindings"], modless_key_bindings) + WRITE_FILE(S["favorite_outfits"], favorite_outfits) //citadel code WRITE_FILE(S["screenshake"], screenshake) diff --git a/code/modules/client/verbs/autobunker.dm b/code/modules/client/verbs/autobunker.dm index 620854b9ed..367f1944cc 100644 --- a/code/modules/client/verbs/autobunker.dm +++ b/code/modules/client/verbs/autobunker.dm @@ -3,7 +3,7 @@ set desc = "Authorizes your account in the panic bunker of any servers connected to this function." set category = "OOC" - if(!(prefs.db_flags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE)) + if(prefs.db_flags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE) to_chat(src, "You are not age verified.") return diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index 6e570e595a..008772663d 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -83,8 +83,7 @@ standard_outfit_options = list() for(var/path in subtypesof(/datum/outfit/job)) var/datum/outfit/O = path - if(initial(O.can_be_admin_equipped)) - standard_outfit_options[initial(O.name)] = path + standard_outfit_options[initial(O.name)] = path sortTim(standard_outfit_options, /proc/cmp_text_asc) outfit_options = standard_outfit_options diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index c79dee926c..01d27531e1 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -358,6 +358,12 @@ ..() user.cure_blind("blindfold_[REF(src)]") +/obj/item/clothing/glasses/fakeblindfold + name = "thin blindfold" + desc = "Covers the eyes, but not thick enough to obscure vision. Mostly for aesthetic." + icon_state = "blindfoldwhite" + item_state = "blindfoldwhite" + /obj/item/clothing/glasses/sunglasses/blindfold/white name = "blind personnel blindfold" desc = "Indicates that the wearer suffers from blindness." diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 6732f1c86c..254ed60c03 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -145,7 +145,7 @@ /obj/item/clothing/suit/armor/riot name = "riot suit" desc = "A suit of semi-flexible polycarbonate body armor with heavy padding to protect against melee attacks. Helps the wearer resist shoving in close quarters." - icon_state = "riot" + icon_state = "swat" item_state = "swat_suit" body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index ed1ba3852f..3074abd070 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -24,7 +24,12 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) /mob/living/carbon/human/dummy/proc/wipe_state() delete_equipment() icon_render_key = null - cut_overlays() + cut_overlays(TRUE) + +/mob/living/carbon/human/dummy/setup_human_dna() + create_dna(src) + randomize_human(src) + dna.initialize_dna(skip_index = TRUE) //Skip stuff that requires full round init. //Inefficient pooling/caching way. GLOBAL_LIST_EMPTY(human_dummy_list) @@ -42,13 +47,48 @@ GLOBAL_LIST_EMPTY(dummy_mob_list) D = new GLOB.human_dummy_list[slotkey] = D GLOB.dummy_mob_list += D + else + D.regenerate_icons() //they were cut in wipe_state() D.in_use = TRUE return D -/proc/unset_busy_human_dummy(slotnumber) - if(!slotnumber) +/proc/generate_dummy_lookalike(slotkey, mob/target) + if(!istype(target)) + return generate_or_wait_for_human_dummy(slotkey) + + var/mob/living/carbon/human/dummy/copycat = generate_or_wait_for_human_dummy(slotkey) + + if(iscarbon(target)) + var/mob/living/carbon/carbon_target = target + carbon_target.dna.transfer_identity(copycat, transfer_SE = TRUE) + + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + human_target.copy_clothing_prefs(copycat) + + copycat.updateappearance(icon_update=TRUE, mutcolor_update=TRUE, mutations_overlay_update=TRUE) + else + //even if target isn't a carbon, if they have a client we can make the + //dummy look like what their human would look like based on their prefs + target?.client?.prefs?.copy_to(copycat, icon_updates=TRUE, roundstart_checks=FALSE) + + return copycat + +/proc/unset_busy_human_dummy(slotkey) + if(!slotkey) return - var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotnumber] + var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotkey] if(istype(D)) D.wipe_state() D.in_use = FALSE + +/proc/clear_human_dummy(slotkey) + if(!slotkey) + return + + var/mob/living/carbon/human/dummy/dummy = GLOB.human_dummy_list[slotkey] + + GLOB.human_dummy_list -= slotkey + if(istype(dummy)) + GLOB.dummy_mob_list -= dummy + qdel(dummy) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 8660e115a6..c2014cbe41 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -14,10 +14,7 @@ //initialize limbs first create_bodyparts() - //initialize dna. for spawned humans; overwritten by other code - create_dna(src) - randomize_human(src) - dna.initialize_dna() + setup_human_dna() if(dna.species) set_species(dna.species.type) @@ -36,6 +33,11 @@ RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood) GLOB.human_list += src +/mob/living/carbon/human/proc/setup_human_dna() + //initialize dna. for spawned humans; overwritten by other code + create_dna(src) + randomize_human(src) + dna.initialize_dna() /mob/living/carbon/human/ComponentInitialize() . = ..() diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 3e65bb6e66..7e3b2ab015 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -176,3 +176,9 @@ /mob/living/carbon/human/get_biological_state() return dna.species.get_biological_state() + +///copies over clothing preferences like underwear to another human +/mob/living/carbon/human/proc/copy_clothing_prefs(mob/living/carbon/human/destination) + destination.underwear = underwear + destination.undershirt = undershirt + destination.socks = socks diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index 72a72d3683..b1441ce55f 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -23,7 +23,7 @@ Difficulty: Hard */ /mob/living/simple_animal/hostile/megafauna/bubblegum - name = "bubblegum" + name = "Bubblegum" desc = "In what passes for a hierarchy among slaughter demons, this one is king." health = 2500 maxHealth = 2500 @@ -443,7 +443,7 @@ Difficulty: Hard charge(chargeat, delay, chargepast) /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination - name = "bubblegum's hallucination" + name = "Bubblegum's hallucination" desc = "Is that really just a hallucination?" health = 1 maxHealth = 1 diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index d146bb855f..bd45c482a2 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -64,7 +64,6 @@ friendly_verb_simple = "groom" mob_size = MOB_SIZE_SMALL movement_type = FLYING - gold_core_spawnable = FRIENDLY_SPAWN var/parrot_damage_upper = 10 var/parrot_state = PARROT_WANDER //Hunt for a perch when created diff --git a/code/modules/projectiles/ammunition/special/syringe.dm b/code/modules/projectiles/ammunition/special/syringe.dm index 10e4402856..caf5da3562 100644 --- a/code/modules/projectiles/ammunition/special/syringe.dm +++ b/code/modules/projectiles/ammunition/special/syringe.dm @@ -25,7 +25,7 @@ /obj/item/ammo_casing/chemgun name = "dart synthesiser" desc = "A high-power spring, linked to an energy-based dart synthesiser." - projectile_type = /obj/item/projectile/bullet/dart + projectile_type = /obj/item/projectile/bullet/dart/piercing firing_effect_type = null /obj/item/ammo_casing/chemgun/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") @@ -35,7 +35,7 @@ var/obj/item/gun/chem/CG = loc if(CG.syringes_left <= 0) return - CG.reagents.trans_to(BB, 15) + CG.reagents.trans_to(BB, 10) BB.name = "chemical dart" CG.syringes_left-- ..() diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index e8d53ddeb8..a3985c8f42 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -407,11 +407,11 @@ fire_sound = 'sound/weapons/rifleshot.ogg' weapon_weight = WEAPON_HEAVY mag_type = /obj/item/ammo_box/magazine/m10mm/rifle - fire_delay = 30 + fire_delay = 10 burst_size = 1 can_unsuppress = TRUE can_suppress = TRUE - w_class = WEIGHT_CLASS_HUGE + w_class = WEIGHT_CLASS_BULKY slot_flags = ITEM_SLOT_BACK automatic_burst_overlay = FALSE actions_types = list() diff --git a/code/modules/projectiles/guns/misc/chem_gun.dm b/code/modules/projectiles/guns/misc/chem_gun.dm index dcabc13989..779ab64bc2 100644 --- a/code/modules/projectiles/guns/misc/chem_gun.dm +++ b/code/modules/projectiles/guns/misc/chem_gun.dm @@ -13,9 +13,9 @@ custom_materials = list(/datum/material/iron=2000) clumsy_check = FALSE fire_sound = 'sound/items/syringeproj.ogg' - var/time_per_syringe = 250 - var/syringes_left = 4 - var/max_syringes = 4 + var/time_per_syringe = 300 + var/syringes_left = 5 + var/max_syringes = 5 var/last_synth = 0 /obj/item/gun/chem/Initialize() diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index e45434cec4..bb5989e5c6 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -644,66 +644,109 @@ //Cybernetic organs /datum/design/cybernetic_liver - name = "Cybernetic Liver" - desc = "A cybernetic liver" + name = "Basic Cybernetic Liver" + desc = "A basic cybernetic liver." id = "cybernetic_liver" build_type = PROTOLATHE | MECHFAB + construction_time = 40 materials = list(/datum/material/iron = 500, /datum/material/glass = 500) build_path = /obj/item/organ/liver/cybernetic - category = list("Misc","Medical Designs") - departmental_flags = DEPARTMENTAL_FLAG_MEDICAL - -/datum/design/cybernetic_heart - name = "Cybernetic Heart" - desc = "A cybernetic heart" - id = "cybernetic_heart" - build_type = PROTOLATHE | MECHFAB - materials = list(/datum/material/iron = 500, /datum/material/glass = 500) - build_path = /obj/item/organ/heart/cybernetic - category = list("Misc","Medical Designs") - departmental_flags = DEPARTMENTAL_FLAG_MEDICAL - -/datum/design/cybernetic_heart_u - name = "Upgraded Cybernetic Heart" - desc = "An upgraded cybernetic heart" - id = "cybernetic_heart_u" - build_type = PROTOLATHE | MECHFAB - construction_time = 50 - materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/silver = 500) - build_path = /obj/item/organ/heart/cybernetic/upgraded category = list("Misc", "Medical Designs") departmental_flags = DEPARTMENTAL_FLAG_MEDICAL -/datum/design/cybernetic_liver_u - name = "Upgraded Cybernetic Liver" - desc = "An upgraded cybernetic liver" - id = "cybernetic_liver_u" - build_type = PROTOLATHE | MECHFAB +/datum/design/cybernetic_liver/tier2 + name = "Cybernetic Liver" + desc = "A cybernetic liver." + id = "cybernetic_liver_tier2" materials = list(/datum/material/iron = 500, /datum/material/glass = 500) - build_path = /obj/item/organ/liver/cybernetic/upgraded - category = list("Misc","Medical Designs") + build_path = /obj/item/organ/liver/cybernetic/tier2 + +/datum/design/cybernetic_liver/tier3 + name = "Upgraded Cybernetic Liver" + desc = "An upgraded cybernetic liver." + id = "cybernetic_liver_tier3" + construction_time = 50 + materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/silver = 600, /datum/material/gold = 600, /datum/material/plasma = 1000, /datum/material/diamond = 2000) + build_path = /obj/item/organ/liver/cybernetic/tier3 + +/datum/design/cybernetic_heart + name = "Basic Cybernetic Heart" + desc = "A basic cybernetic heart." + id = "cybernetic_heart" + build_type = PROTOLATHE | MECHFAB + construction_time = 40 + materials = list(/datum/material/iron = 500, /datum/material/glass = 500) + build_path = /obj/item/organ/heart/cybernetic + category = list("Misc", "Medical Designs") departmental_flags = DEPARTMENTAL_FLAG_MEDICAL +/datum/design/cybernetic_heart/tier2 + name = "Cybernetic Heart" + desc = "A cybernetic heart." + id = "cybernetic_heart_tier2" + materials = list(/datum/material/iron = 500, /datum/material/glass = 500) + build_path = /obj/item/organ/heart/cybernetic/tier2 + +/datum/design/cybernetic_heart/tier3 + name = "Upgraded Cybernetic Heart" + desc = "An upgraded cybernetic heart." + id = "cybernetic_heart_tier3" + construction_time = 50 + materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/silver = 600, /datum/material/gold = 600, /datum/material/plasma = 1000, /datum/material/diamond = 2000) + build_path = /obj/item/organ/heart/cybernetic/tier3 + /datum/design/cybernetic_lungs - name = "Cybernetic Lungs" - desc = "A pair of cybernetic lungs." + name = "Basic Cybernetic Lungs" + desc = "A basic pair of cybernetic lungs." id = "cybernetic_lungs" build_type = PROTOLATHE | MECHFAB + construction_time = 40 materials = list(/datum/material/iron = 500, /datum/material/glass = 500) build_path = /obj/item/organ/lungs/cybernetic - category = list("Misc","Medical Designs") + category = list("Misc", "Medical Designs") departmental_flags = DEPARTMENTAL_FLAG_MEDICAL -/datum/design/cybernetic_lungs_u +/datum/design/cybernetic_lungs/tier2 + name = "Cybernetic Lungs" + desc = "A pair of cybernetic lungs." + id = "cybernetic_lungs_tier2" + materials = list(/datum/material/iron = 500, /datum/material/glass = 500) + build_path = /obj/item/organ/lungs/cybernetic/tier2 + +/datum/design/cybernetic_lungs/tier3 name = "Upgraded Cybernetic Lungs" desc = "A pair of upgraded cybernetic lungs." - id = "cybernetic_lungs_u" + id = "cybernetic_lungs_tier3" + construction_time = 50 + materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/silver = 600, /datum/material/gold = 600, /datum/material/plasma = 1000, /datum/material/diamond = 2000) + build_path = /obj/item/organ/lungs/cybernetic/tier3 + +/datum/design/cybernetic_stomach + name = "Basic Cybernetic Stomach" + desc = "A basic cybernetic stomach." + id = "cybernetic_stomach" build_type = PROTOLATHE | MECHFAB - materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/silver = 500) - build_path = /obj/item/organ/lungs/cybernetic/upgraded - category = list("Misc","Medical Designs") + construction_time = 40 + materials = list(/datum/material/iron = 500, /datum/material/glass = 500) + build_path = /obj/item/organ/stomach/cybernetic + category = list("Misc", "Medical Designs") departmental_flags = DEPARTMENTAL_FLAG_MEDICAL +/datum/design/cybernetic_stomach/tier2 + name = "Cybernetic Stomach" + desc = "A cybernetic stomach." + id = "cybernetic_stomach_tier2" + materials = list(/datum/material/iron = 500, /datum/material/glass = 500) + build_path = /obj/item/organ/stomach/cybernetic/tier2 + +/datum/design/cybernetic_stomach/tier3 + name = "Upgraded Cybernetic Stomach" + desc = "An upgraded cybernetic stomach." + id = "cybernetic_stomach_tier3" + construction_time = 50 + materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/silver = 600, /datum/material/gold = 600, /datum/material/plasma = 1000, /datum/material/diamond = 2000) + build_path = /obj/item/organ/stomach/cybernetic/tier3 + /datum/design/cybernetic_tongue name = "Cybernetic tongue" desc = "A fancy cybernetic tongue." @@ -711,7 +754,7 @@ build_type = PROTOLATHE | MECHFAB materials = list(/datum/material/iron = 500, /datum/material/glass = 500) build_path = /obj/item/organ/tongue/cybernetic - category = list("Misc","Medical Designs") + category = list("Misc", "Medical Designs") departmental_flags = DEPARTMENTAL_FLAG_MEDICAL /datum/design/cybernetic_ears diff --git a/code/modules/research/techweb/nodes/medical_nodes.dm b/code/modules/research/techweb/nodes/medical_nodes.dm index 0eca3ea877..1d7d579dad 100644 --- a/code/modules/research/techweb/nodes/medical_nodes.dm +++ b/code/modules/research/techweb/nodes/medical_nodes.dm @@ -67,20 +67,27 @@ design_ids = list("implanter", "implantcase", "implant_chem", "implant_tracking", "locator", "c38_trac") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) +/datum/techweb_node/basic_cyber_organs + id = "basic_cyber_organs" + starting_node = TRUE + display_name = "Basic Cybernetic Organs" + description = "We have the techinology to force him to live a disgusting halflife." + design_ids = list("cybernetic_liver", "cybernetic_heart", "cybernetic_lungs", "cybernetic_stomach") + /datum/techweb_node/cyber_organs id = "cyber_organs" display_name = "Cybernetic Organs" description = "We have the technology to rebuild him." - prereq_ids = list("adv_biotech") - design_ids = list("cybernetic_ears", "cybernetic_heart", "cybernetic_liver", "cybernetic_lungs", "cybernetic_tongue") + prereq_ids = list("biotech") + design_ids = list("cybernetic_ears", "cybernetic_heart_tier2", "cybernetic_liver_tier2", "cybernetic_lungs_tier2", "cybernetic_stomach_tier2") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000) /datum/techweb_node/cyber_organs_upgraded id = "cyber_organs_upgraded" display_name = "Upgraded Cybernetic Organs" description = "We have the technology to upgrade him." - prereq_ids = list("cyber_organs") - design_ids = list("cybernetic_ears_u", "cybernetic_heart_u", "cybernetic_liver_u", "cybernetic_lungs_u", "ipc_stomach") + prereq_ids = list("adv_biotech", "cyber_organs") + design_ids = list("cybernetic_ears_u", "cybernetic_heart_tier3", "cybernetic_liver_tier3", "cybernetic_lungs_tier3", "cybernetic_stomach_tier3") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1500) /datum/techweb_node/cyber_implants diff --git a/code/modules/research/techweb/nodes/syndicate_nodes.dm b/code/modules/research/techweb/nodes/syndicate_nodes.dm index e659e49ac7..5a3c0f541f 100644 --- a/code/modules/research/techweb/nodes/syndicate_nodes.dm +++ b/code/modules/research/techweb/nodes/syndicate_nodes.dm @@ -21,7 +21,7 @@ id = "advanced_illegal_ballistics" display_name = "Advanced Non-Standard Ballistics" description = "Ballistic ammunition for non-standard firearms. Usually the ones you don't have nor want to be involved with." - design_ids = list("10mm","10mmap","10mminc","10mmhp","sl357","sl357ap","pistolm9mm","m45","bolt_clip") + design_ids = list("10mm","10mmap","10mminc","10mmhp","sl357","sl357ap","pistolm9mm","m45","bolt_clip","m10apbox","m10firebox","m10hpbox") prereq_ids = list("ballistic_weapons","syndicate_basic","explosive_weapons") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 25000) //This gives sec lethal mags/clips for guns from traitors, space, or anything in between. diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 4dba68ada3..6263284584 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -197,45 +197,67 @@ colour = "red" /obj/item/organ/heart/cybernetic - name = "cybernetic heart" - desc = "An electronic device designed to mimic the functions of an organic human heart. Offers no benefit over an organic heart other than being easy to make." + name = "basic cybernetic heart" + desc = "A basic electronic device designed to mimic the functions of an organic human heart." icon_state = "heart-c" organ_flags = ORGAN_SYNTHETIC + maxHealth = STANDARD_ORGAN_THRESHOLD*0.75 //This also hits defib timer, so a bit higher than its less important counterparts + + var/dose_available = FALSE + var/rid = /datum/reagent/medicine/epinephrine + var/ramount = 10 + var/emp_vulnerability = 1 //The value the severity of emps are divided by to determine the likelihood of permanent damage. + +/obj/item/organ/heart/cybernetic/tier2 + name = "cybernetic heart" + desc = "An electronic device designed to mimic the functions of an organic human heart. Also holds an emergency dose of epinephrine, used automatically after facing severe trauma." + icon_state = "heart-c-u" + maxHealth = 1.5 * STANDARD_ORGAN_THRESHOLD + dose_available = TRUE + emp_vulnerability = 2 + +/obj/item/organ/heart/cybernetic/tier3 + name = "upgraded cybernetic heart" + desc = "An electronic device designed to mimic the functions of an organic human heart. Also holds an emergency dose of epinephrine, used automatically after facing severe trauma. This upgraded model can regenerate its dose after use." + icon_state = "heart-c-u2" + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD + dose_available = TRUE + rid = /datum/reagent/medicine/atropine + ramount = 5 + emp_vulnerability = 3 /obj/item/organ/heart/cybernetic/emp_act(severity) . = ..() + + // If the owner doesn't need a heart, we don't need to do anything with it. + if(!owner.needs_heart()) + return + if(. & EMP_PROTECT_SELF) return - Stop() - addtimer(CALLBACK(src, .proc/Restart), 0.2*severity SECONDS) - damage += severity + if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people. + owner.Dizzy(10) + owner.losebreath += 10 + COOLDOWN_START(src, severe_cooldown, 20 SECONDS) + if(prob(severity/emp_vulnerability)) //Chance of permanent effects + organ_flags |= ORGAN_SYNTHETIC_EMP //Starts organ faliure - gonna need replacing soon. + Stop() + owner.visible_message("[owner] clutches at [owner.p_their()] chest as if [owner.p_their()] heart is stopping!", \ + "You feel a terrible pain in your chest, as if your heart has stopped!") + addtimer(CALLBACK(src, .proc/Restart), 10 SECONDS) -/obj/item/organ/heart/cybernetic/upgraded - name = "upgraded cybernetic heart" - desc = "An electronic device designed to mimic the functions of an organic human heart. Also holds an emergency dose of epinephrine, used automatically after facing severe trauma. This upgraded model can regenerate its dose after use." - icon_state = "heart-c-u" - maxHealth = 2 * STANDARD_ORGAN_THRESHOLD - - //I put it on upgraded for now. - var/dose_available = TRUE - var/rid = /datum/reagent/medicine/epinephrine - var/ramount = 10 - -/obj/item/organ/heart/cybernetic/upgraded/on_life() +/obj/item/organ/heart/cybernetic/on_life(delta_time, times_fired) . = ..() - if(!.) - return if(dose_available && owner.health <= owner.crit_threshold && !owner.reagents.has_reagent(rid)) - owner.reagents.add_reagent(rid, ramount) used_dose() - if(ramount < 10) //eats your nutrition to regen epinephrine - var/regen_amount = owner.nutrition/2000 - owner.adjust_nutrition(-regen_amount) - ramount += regen_amount -/obj/item/organ/heart/cybernetic/upgraded/proc/used_dose() +/obj/item/organ/heart/cybernetic/proc/used_dose() + owner.reagents.add_reagent(rid, ramount) + dose_available = FALSE + +/obj/item/organ/heart/cybernetic/tier3/used_dose() + . = ..() addtimer(VARSET_CALLBACK(src, dose_available, TRUE), 5 MINUTES) - ramount = 0 /obj/item/organ/heart/ipc name = "IPC heart" diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm index 749f5a8c38..2037547d36 100755 --- a/code/modules/surgery/organs/liver.dm +++ b/code/modules/surgery/organs/liver.dm @@ -98,23 +98,41 @@ icon_state = "liver-c" /obj/item/organ/liver/cybernetic - name = "cybernetic liver" + name = "basic cybernetic liver" icon_state = "liver-c" - desc = "An electronic device designed to mimic the functions of a human liver. It has no benefits over an organic liver, but is easy to produce." + desc = "A very basic device designed to mimic the functions of a human liver. Handles toxins slightly worse than an organic liver." organ_flags = ORGAN_SYNTHETIC - maxHealth = 1.1 * STANDARD_ORGAN_THRESHOLD + toxTolerance = 0.3 * LIVER_DEFAULT_TOX_TOLERANCE //little less than 1u of toxin purging + toxLethality = 1.1 * LIVER_DEFAULT_TOX_LETHALITY + maxHealth = STANDARD_ORGAN_THRESHOLD*0.5 -/obj/item/organ/liver/cybernetic/upgraded - name = "upgraded cybernetic liver" + var/emp_vulnerability = 1 //The value the severity of emps are divided by to determine the likelihood of permanent damage. + +/obj/item/organ/liver/cybernetic/tier2 + name = "cybernetic liver" icon_state = "liver-c-u" - desc = "An upgraded version of the cybernetic liver, designed to improve upon organic livers. It is resistant to alcohol poisoning and is very robust at filtering toxins." + desc = "An electronic device designed to mimic the functions of a human liver. Handles toxins slightly better than an organic liver." + maxHealth = 1.5 * STANDARD_ORGAN_THRESHOLD + toxTolerance = 2 * LIVER_DEFAULT_TOX_TOLERANCE //6 units of toxin purging + toxLethality = 0.8 * LIVER_DEFAULT_TOX_LETHALITY //20% less damage than a normal liver + emp_vulnerability = 2 + +/obj/item/organ/liver/cybernetic/tier3 + name = "upgraded cybernetic liver" + icon_state = "liver-c-u2" + desc = "An upgraded version of the cybernetic liver, designed to improve further upon organic livers. It is resistant to alcohol poisoning and is very robust at filtering toxins." alcohol_tolerance = 0.001 maxHealth = 2 * STANDARD_ORGAN_THRESHOLD - toxTolerance = 15 //can shrug off up to 15u of toxins - toxLethality = 0.008 //20% less damage than a normal liver + toxTolerance = 5 * LIVER_DEFAULT_TOX_TOLERANCE //15 units of toxin purging + toxLethality = 0.4 * LIVER_DEFAULT_TOX_LETHALITY //60% less damage than a normal liver + emp_vulnerability = 3 /obj/item/organ/liver/cybernetic/emp_act(severity) . = ..() if(. & EMP_PROTECT_SELF) return - damage += severity + if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people. + owner.adjustToxLoss(10) + COOLDOWN_START(src, severe_cooldown, 10 SECONDS) + if(prob(severity/emp_vulnerability)) //Chance of permanent effects + organ_flags |= ORGAN_SYNTHETIC_EMP //Starts organ faliure - gonna need replacing soon. diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 083c71fda2..c94fb16add 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -547,33 +547,52 @@ maxHealth = INFINITY//I don't understand how plamamen work, so I'm not going to try t give them special lungs atm /obj/item/organ/lungs/cybernetic - name = "cybernetic lungs" - desc = "A cybernetic version of the lungs found in traditional humanoid entities. It functions the same as an organic lung and is merely meant as a replacement." + name = "basic cybernetic lungs" + desc = "A basic cybernetic version of the lungs found in traditional humanoid entities." icon_state = "lungs-c" organ_flags = ORGAN_SYNTHETIC - maxHealth = 400 - safe_oxygen_min = 13 + maxHealth = STANDARD_ORGAN_THRESHOLD * 0.5 -/obj/item/organ/lungs/cybernetic/emp_act() - . = ..() - if(. & EMP_PROTECT_SELF) - return - owner.losebreath = 20 - owner.adjustOrganLoss(ORGAN_SLOT_LUNGS, 25) + var/emp_vulnerability = 1 //The value the severity of emps are divided by to determine the likelihood of permanent damage. -/obj/item/organ/lungs/cybernetic/upgraded - name = "upgraded cybernetic lungs" - desc = "A more advanced version of the stock cybernetic lungs. They are capable of filtering out lower levels of toxins and carbon dioxide." +/obj/item/organ/lungs/cybernetic/tier2 + name = "cybernetic lungs" + desc = "A cybernetic version of the lungs found in traditional humanoid entities. Allows for greater intakes of oxygen than organic lungs, requiring slightly less pressure." icon_state = "lungs-c-u" - safe_toxins_max = 20 - safe_co2_max = 20 - safe_oxygen_max = 250 + maxHealth = 1.5 * STANDARD_ORGAN_THRESHOLD + safe_oxygen_min = 13 + safe_oxygen_max = 100 + emp_vulnerability = 2 + +/obj/item/organ/lungs/cybernetic/tier3 + name = "upgraded cybernetic lungs" + desc = "A more advanced version of the stock cybernetic lungs. Features the ability to filter out various airbourne toxins and carbon dioxide even at heavy levels." + icon_state = "lungs-c-u2" + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD + safe_oxygen_min = 4 //You could literally be breathing the thinnest amount of oxygen and be fine + safe_oxygen_max = 250 //Or be in an enriched oxygen room for that matter + safe_toxins_max = 30 + safe_co2_max = 30 + SA_para_min = 30 + SA_sleep_min = 50 + BZ_trip_balls_min = 30 + emp_vulnerability = 3 cold_level_1_threshold = 200 cold_level_2_threshold = 140 cold_level_3_threshold = 100 maxHealth = 550 +/obj/item/organ/lungs/cybernetic/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people. + owner.losebreath += 20 + COOLDOWN_START(src, severe_cooldown, 30 SECONDS) + if(prob(severity/emp_vulnerability)) //Chance of permanent effects + organ_flags |= ORGAN_SYNTHETIC_EMP //Starts organ faliure - gonna need replacing soon. + /obj/item/organ/lungs/ashwalker name = "ash lungs" desc = "blackened lungs identical from specimens recovered from lavaland, unsuited to higher air pressures." diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm index cb4de69fbd..6cdeadcbb3 100644 --- a/code/modules/surgery/organs/organ_internal.dm +++ b/code/modules/surgery/organs/organ_internal.dm @@ -16,6 +16,7 @@ var/decay_factor = 0 //same as above but when without a living owner, set to 0 for generic organs var/high_threshold = STANDARD_ORGAN_THRESHOLD * 0.45 //when severe organ damage occurs var/low_threshold = STANDARD_ORGAN_THRESHOLD * 0.1 //when minor organ damage occurs + var/severe_cooldown //cooldown for severe effects, used for synthetic organ emp effects. ///Organ variables for determining what we alert the owner with when they pass/clear the damage thresholds var/prev_damage = 0 @@ -153,6 +154,9 @@ /obj/item/organ/proc/on_life() //repair organ damage if the organ is not failing or synthetic if(organ_flags & ORGAN_FAILING || !owner) return FALSE + if(organ_flags & ORGAN_SYNTHETIC_EMP) //Synthetic organ has been emped, is now failing. + applyOrganDamage(maxHealth * decay_factor) + return if(!is_cold() && damage) ///Damage decrements by a percent of its maxhealth var/healing_amount = -(maxHealth * healing_factor) diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm index ba7b950602..defb062f1a 100644 --- a/code/modules/surgery/organs/stomach.dm +++ b/code/modules/surgery/organs/stomach.dm @@ -93,6 +93,40 @@ icon_state = "stomach-p" desc = "A strange crystal that is responsible for metabolizing the unseen energy force that feeds plasmamen." +/obj/item/organ/stomach/cybernetic + name = "basic cybernetic stomach" + icon_state = "stomach-c" + desc = "A basic device designed to mimic the functions of a human stomach" + organ_flags = ORGAN_SYNTHETIC + maxHealth = STANDARD_ORGAN_THRESHOLD * 0.5 + var/emp_vulnerability = 1 //The value the severity of emps are divided by to determine the likelihood of permanent damage. + +/obj/item/organ/stomach/cybernetic/tier2 + name = "cybernetic stomach" + icon_state = "stomach-c-u" + desc = "An electronic device designed to mimic the functions of a human stomach. Handles disgusting food a bit better." + maxHealth = 1.5 * STANDARD_ORGAN_THRESHOLD + disgust_metabolism = 2 + emp_vulnerability = 2 + +/obj/item/organ/stomach/cybernetic/tier3 + name = "upgraded cybernetic stomach" + icon_state = "stomach-c-u2" + desc = "An upgraded version of the cybernetic stomach, designed to improve further upon organic stomachs. Handles disgusting food very well." + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD + disgust_metabolism = 3 + emp_vulnerability = 3 + +/obj/item/organ/stomach/cybernetic/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people. + owner.vomit(stun = FALSE) + COOLDOWN_START(src, severe_cooldown, 10 SECONDS) + if(prob(severity/emp_vulnerability)) //Chance of permanent effects + organ_flags |= ORGAN_SYNTHETIC_EMP //Starts organ faliure - gonna need replacing soon. + /obj/item/organ/stomach/ipc name = "ipc cell" icon_state = "stomach-ipc" diff --git a/code/modules/uplink/uplink_items/uplink_roles.dm b/code/modules/uplink/uplink_items/uplink_roles.dm index 4edbe2f2c7..774c2d9794 100644 --- a/code/modules/uplink/uplink_items/uplink_roles.dm +++ b/code/modules/uplink/uplink_items/uplink_roles.dm @@ -208,9 +208,10 @@ /datum/uplink_item/role_restricted/chemical_gun name = "Reagent Dartgun" - desc = "A heavily modified syringe gun which is capable of synthesizing its own chemical darts using input reagents. Can hold 100u of reagents." + desc = "A heavily modified syringe gun which is capable of synthesizing its own chemical darts using input reagents. \ + Synthesizes one piercing 10 unit dart every 30 seconds up to a maximum of five. Can hold 100u of reagents." item = /obj/item/gun/chem - cost = 12 + cost = 10 restricted_roles = list("Chemist", "Chief Medical Officer") /datum/uplink_item/role_restricted/reverse_bear_trap @@ -257,4 +258,4 @@ item = /obj/item/storage/toolbox/emergency/turret cost = 11 restricted_roles = list("Station Engineer") - + diff --git a/html/changelog.html b/html/changelog.html index 8e88c39201..389a308886 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -50,6 +50,77 @@ -->
+

01 May 2021

+

qweq12yt updated:

+
    +
  • Restores the sprite for the Riot Suit.
  • +
+ +

30 April 2021

+

DrPainis updated:

+
    +
  • Bubblegum's hallucinations are capitalized.
  • +
+

Melbert, SandPoot updated:

+
    +
  • TGUI Limbgrower
  • +
  • Refactored the limbgrower to modernize the code and allow for more types of designs.
  • +
  • The limbgrower now supports plumbing ducts.
  • +
  • Fixes genitals not actually getting data from disks.
  • +
  • Adds two special helpers.
  • +
+

SandPoot updated:

+
    +
  • The decal painter now has visible previews for your tile painting funs.
  • +
  • Fixes decal painter painting in the opposite direction.
  • +
+

TheObserver-sys updated:

+
    +
  • Restores the access lock on crates that should have them, given the goods inside.
  • +
  • Makes the 10MM Surplus Rifle a less awful thing to use.
  • +
  • replaces unarmored things with their armored versions.
  • +
  • Illegal Tech Ammo actually is fucking reasonable, now.
  • +
  • Expensive Illegal Tech Ammo Boxes are now constructible, with actually justifiable prices.
  • +
+

WanderingFox95 updated:

+
    +
  • There's finally a reason for the reagent dart gun to exist and be used!
  • +
+

akada updated:

+
    +
  • Changes the space adaptation sprite to something less intrusive and more subtle.
  • +
+

necromanceranne updated:

+
    +
  • Basic cybernetic organs: they're worse than organic! Basic stomachs, hearts, lungs and livers! For when you hate someone enough to not bother harvesting organs from a monkeyhuman!
  • +
  • Cybernetic organs have been adjusted into three tiers: 1 (basic), 2 (standard, better than organic) and 3 (absolutely better than organic but expensive to print)
  • +
  • Cybernetic organs that are emp'd instead suffer different effects based on the severity of the emp. The bigger the emp, the worse the effect is.
  • +
  • Rather than outright bricking, severely emp'd cyberorgans degrade over time very quickly, requiring replacement in the near future.
  • +
  • Fake blindfolds in the loadout. They don't obscure vision, for better or worse.
  • +
+ +

29 April 2021

+

Putnam3145 updated:

+
    +
  • Fixed a couple runtimes in activity (threat) tracking
  • +
+

keronshb updated:

+
    +
  • Removes the Reinforcement Chromosome from Genetics.
  • +
+ +

26 April 2021

+

Trigg, stylemistake and SandPoot updated:

+
    +
  • Admins just got a new TGUI Select Equipment menu tweak: Prevents the window from creating sprites for any animated version there might be. (this guarantees consistant sprite size/amount)
  • +
+ +

25 April 2021

+

DrPainis updated:

+
    +
  • Bubblegum is now capitalized.
  • +
+

22 April 2021

Whoneedspacee updated:

    @@ -722,165 +793,6 @@
    • cursed rice hat right in front of the jungle gateway's entrance is now removed from this dimensional plane
    - -

    28 February 2021

    -

    Putnam3145 updated:

    -
      -
    • Polychromic windbreaker's alt-click message is now coherent
    • -
    • Toggleable suits now have an on_toggle proc to be overridden.
    • -
    -

    R3dtail updated:

    -
      -
    • doubled max belly name length and quadrupled belly description length
    • -
    -

    SandPoot updated:

    -
      -
    • Body rejuvenation surgery will loop until the patient is completely healed.
    • -
    -

    dzahlus updated:

    -
      -
    • fixes toxinlovers dying from heretic stuff that should heal them instead
    • -
    - -

    27 February 2021

    -

    Hatterhat updated:

    -
      -
    • Lingfists (trait_mauler) now deal no stam damage and lost their 15(!!!) armor penetration.
    • -
    -

    Putnam3145 updated:

    -
      -
    • Tablets now protect their contents from rads.
    • -
    -

    TheObserver-sys updated:

    -
      -
    • Chems that should have been usable are now usable, try some cryoxadone on a plant today!!!
    • -
    -

    kappa-sama updated:

    -
      -
    • cards and card binders are now small-class items
    • -
    -

    keronshb updated:

    -
      -
    • 16 > 10 unlock cost for stronger abilities
    • -
    • Made nearly all other abilities for free.
    • -
    -

    kiwedespars updated:

    -
      -
    • reverted the pr that absolutely gutted pugilism and made it worse than base unarmed, also gives it a second long stagger
    • -
    • removed the ability to parry while horizontal, because that's dumb and makes it easy to just time the parries right.
    • -
    -

    silicons updated:

    -
      -
    • chaplain arrythmic knives can no longer be abused for infinite speed.
    • -
    - -

    26 February 2021

    -

    DeltaFire15 updated:

    -
      -
    • All machine-frame based tool-use actions now have state-checking callbacks.
    • -
    - -

    25 February 2021

    -

    DeltaFire15 updated:

    -
      -
    • Traitor / Ling objective amount should now be correct again.
    • -
    - -

    24 February 2021

    -

    SandPoot updated:

    -
      -
    • Regular crowbars no longer open powered airlocks.
    • -
    -

    silicons updated:

    -
      -
    • xeno cube makes hostile xenos now, and drops a sentinel instead of a drone.
    • -
    - -

    23 February 2021

    -

    keronshb updated:

    -
      -
    • Hyperblade to uplink with poplock
    • -
    • Removes combination of two Dragon Tooth Swords while keeping it for regular eutactics.
    • -
    -

    timothyteakettle updated:

    -
      -
    • banning panel prioritises mobs with clients now when trying to find them if they're in the game
    • -
    - -

    22 February 2021

    -

    Putnam3145 updated:

    -
      -
    • (Hexa)crocin
    • -
    • (Hexa)camphor
    • -
    • Nymphomaniac quirk
    • -
    • All climaxes and arousals are now logged, as well as genital exposure.
    • -
    -

    SandPoot updated:

    -
      -
    • Cyborg tablets and it's special app for self-management.
    • -
    • In the case of a doomsday device being created outside of an AI it will delete itself.
    • -
    • Some sprites for it have been added and the borg's hud light toggles been changed to only on-off (made by yours truly)
    • -
    • A lot of borg code was changed
    • -
    • Tools no longer use istype checks and actually check for their behavior.
    • -
    -

    Vynzill updated:

    -
      -
    • cursed rice hat that's hard to find and obtain, along with a couple other hats
    • -
    • a replacement toy gun for donksoft lmg
    • -
    • gorillas to the jungle gateway, friendly, even when attacked.
    • -
    • couple mapping errors I noticed, most importantly a missing window in the chapel.
    • -
    • shotgun and donksoft lmg removed, captain coat nerfed armor values.
    • -
    • leaper healthpool from 450 to 550 hopefully making it more of a struggle, and gives it a name.
    • -
    • leaper pit is more wider. The hidden room south is now more obvious to find
    • -
    -

    dzahlus updated:

    -
      -
    • Added pain emote to getting wounded
    • -
    • added a new pain emote sounds
    • -
    - -

    21 February 2021

    -

    Hatterhat updated:

    -
      -
    • Anomaly announcements and brand intelligence now always announce instead of having some ham-fisted chance of being a command report.
    • -
    -

    IronEleven updated:

    -
      -
    • Raises Space Vine Population Requirement from 10 to 20
    • -
    -

    MrJWhit updated:

    -
      -
    • Removes an unnecessary % on the seed extractor.
    • -
    -

    timothyteakettle updated:

    -
      -
    • the query for checking mentors now gets properly deleted
    • -
    • vampires no longer burn in the chapel if they signed up as the chaplain
    • -
    - -

    20 February 2021

    -

    Adelphon updated:

    -
      -
    • polychromic pants
    • -
    • urban coat made polychromic
    • -
    -

    Chiirno updated:

    -
      -
    • Synthflesh now unhusks with 100u instead of requiring 101u.
    • -
    -

    SmArtKar updated:

    -
      -
    • Added some QoL changes to TCG
    • -
    • Fixed TCG cards not saving
    • -
    -

    TyrianTyrell updated:

    -
      -
    • fixed the signed language so that you can actually use it, and that it's unusable when it's meant to be.
    • -
    -

    timothyteakettle updated:

    -
      -
    • stops people using Message All on PDAs when their cartridge doesn't allow it
    • -
GoonStation 13 Development Team diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml index 21084be59e..1212e88f23 100644 --- a/html/changelogs/.all_changelog.yml +++ b/html/changelogs/.all_changelog.yml @@ -29164,3 +29164,58 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. - balance: triple fire breath for the lava swoop only happens below half health now - bugfix: The arena attack not making safespots when you fight it in a mech +2021-04-25: + DrPainis: + - spellcheck: Bubblegum is now capitalized. +2021-04-26: + Trigg, stylemistake and SandPoot: + - admin: 'Admins just got a new TGUI Select Equipment menu tweak: Prevents the window + from creating sprites for any animated version there might be. (this guarantees + consistant sprite size/amount)' +2021-04-29: + Putnam3145: + - bugfix: Fixed a couple runtimes in activity (threat) tracking + keronshb: + - balance: Removes the Reinforcement Chromosome from Genetics. +2021-04-30: + DrPainis: + - spellcheck: Bubblegum's hallucinations are capitalized. + Melbert, SandPoot: + - refactor: TGUI Limbgrower + - refactor: Refactored the limbgrower to modernize the code and allow for more types + of designs. + - rscadd: The limbgrower now supports plumbing ducts. + - bugfix: Fixes genitals not actually getting data from disks. + - code_imp: Adds two special helpers. + SandPoot: + - rscadd: The decal painter now has visible previews for your tile painting funs. + - bugfix: Fixes decal painter painting in the opposite direction. + TheObserver-sys: + - bugfix: Restores the access lock on crates that should have them, given the goods + inside. + - bugfix: Makes the 10MM Surplus Rifle a less awful thing to use. + - bugfix: replaces unarmored things with their armored versions. + - balance: Illegal Tech Ammo actually is fucking reasonable, now. + - balance: Expensive Illegal Tech Ammo Boxes are now constructible, with actually + justifiable prices. + WanderingFox95: + - balance: There's finally a reason for the reagent dart gun to exist and be used! + akada: + - imageadd: Changes the space adaptation sprite to something less intrusive and + more subtle. + necromanceranne: + - rscadd: 'Basic cybernetic organs: they''re worse than organic! Basic stomachs, + hearts, lungs and livers! For when you hate someone enough to not bother harvesting + organs from a monkeyhuman!' + - rscadd: 'Cybernetic organs have been adjusted into three tiers: 1 (basic), 2 (standard, + better than organic) and 3 (absolutely better than organic but expensive to + print)' + - rscadd: Cybernetic organs that are emp'd instead suffer different effects based + on the severity of the emp. The bigger the emp, the worse the effect is. + - rscadd: Rather than outright bricking, severely emp'd cyberorgans degrade over + time very quickly, requiring replacement in the near future. + - rscadd: Fake blindfolds in the loadout. They don't obscure vision, for better + or worse. +2021-05-01: + qweq12yt: + - bugfix: Restores the sprite for the Riot Suit. diff --git a/html/changelogs/AutoChangeLog-pr-14670.yml b/html/changelogs/AutoChangeLog-pr-14670.yml new file mode 100644 index 0000000000..4264b1e083 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-14670.yml @@ -0,0 +1,10 @@ +author: "TripleShades" +delete-after: True +changes: + - rscadd: "Added two air alarms to Pubby Security, one in the evidence locker room and one in the main equipment back room" + - rscadd: "pAI Card back to outside Research in Meta Station" + - bugfix: "Pubby Disposals now shunts to space" + - bugfix: "Maintinence Areas being not applied to certain airlocks as well as stealing minor walls" + - bugfix: "Box Surgery Storage camera is now renamed to be on the network" + - bugfix: "Box Paramedic Station camera is now renamed to be on the network, and no longer steals the Morgue's cam +tweak: Box Surgery Storage is now it's own proper room" diff --git a/icons/effects/genetics.dmi b/icons/effects/genetics.dmi index 373a9de623..16ceb1f18c 100644 Binary files a/icons/effects/genetics.dmi and b/icons/effects/genetics.dmi differ diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi index 0460934eee..52172bbe29 100755 Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ diff --git a/icons/turf/decals.dmi b/icons/turf/decals.dmi index 2b2b62a99c..4ed8c8db9a 100644 Binary files a/icons/turf/decals.dmi and b/icons/turf/decals.dmi differ diff --git a/modular_citadel/code/modules/client/loadout/glasses.dm b/modular_citadel/code/modules/client/loadout/glasses.dm index b0eecbbf28..f3b07657f4 100644 --- a/modular_citadel/code/modules/client/loadout/glasses.dm +++ b/modular_citadel/code/modules/client/loadout/glasses.dm @@ -6,6 +6,10 @@ name = "Blindfold" path = /obj/item/clothing/glasses/sunglasses/blindfold +/datum/gear/glasses/fakeblindfold + name = "Fake Blindfold" + path = /obj/item/clothing/glasses/fakeblindfold + /datum/gear/glasses/cold name = "Cold goggles" path = /obj/item/clothing/glasses/cold diff --git a/modular_citadel/code/modules/projectiles/boxes_magazines/external/pistol.dm b/modular_citadel/code/modules/projectiles/boxes_magazines/external/pistol.dm index c39c66578b..858fe8fd5f 100644 --- a/modular_citadel/code/modules/projectiles/boxes_magazines/external/pistol.dm +++ b/modular_citadel/code/modules/projectiles/boxes_magazines/external/pistol.dm @@ -3,10 +3,9 @@ desc = "A gun magazine. Loaded with rounds which ignite the target.." id = "10mminc" build_type = PROTOLATHE - materials = list(/datum/material/plasma = 50000, /datum/material/iron = 18000) - reagents_list = list(/datum/reagent/toxin/plasma = 120, /datum/reagent/napalm = 240) + materials = list(/datum/material/plasma = 5000, /datum/material/iron = 7500) build_path = /obj/item/ammo_box/magazine/m10mm/fire - category = list("Weapons") + category = list("Ammo") departmental_flags = DEPARTMENTAL_FLAG_SECURITY /datum/design/m10mm @@ -14,7 +13,7 @@ desc = "A gun magazine." id = "10mm" build_type = PROTOLATHE - materials = list(/datum/material/iron = 55000) + materials = list(/datum/material/iron = 6000) build_path = /obj/item/ammo_box/magazine/m10mm category = list("Ammo") departmental_flags = DEPARTMENTAL_FLAG_SECURITY @@ -24,8 +23,7 @@ desc = "A gun magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing." id = "10mmhp" build_type = PROTOLATHE - materials = list(/datum/material/iron = 40000, /datum/material/glass = 50000) - reagents_list = list(/datum/reagent/sonic_powder = 280) + materials = list(/datum/material/iron = 7500, /datum/material/glass = 5000) build_path = /obj/item/ammo_box/magazine/m10mm/hp category = list("Ammo") departmental_flags = DEPARTMENTAL_FLAG_SECURITY @@ -35,7 +33,7 @@ desc = "A gun magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." id = "10mmap" build_type = PROTOLATHE - materials = list(/datum/material/iron = 40000, /datum/material/titanium = 60000) + materials = list(/datum/material/iron = 7500, /datum/material/titanium = 6500) build_path = /obj/item/ammo_box/magazine/m10mm/ap category = list("Ammo") departmental_flags = DEPARTMENTAL_FLAG_SECURITY @@ -54,7 +52,7 @@ name = "handgun magazine (.45)" id = "m45" build_type = PROTOLATHE - materials = list(/datum/material/iron = 80000) + materials = list(/datum/material/iron = 8000) build_path = /obj/item/ammo_box/magazine/m45 category = list("Ammo") departmental_flags = DEPARTMENTAL_FLAG_SECURITY @@ -64,7 +62,7 @@ desc = "A gun magazine." id = "pistolm9mm" build_type = PROTOLATHE - materials = list(/datum/material/iron = 80000) + materials = list(/datum/material/iron = 7500) build_path = /obj/item/ammo_box/magazine/pistolm9mm category = list("Ammo") departmental_flags = DEPARTMENTAL_FLAG_SECURITY @@ -84,7 +82,37 @@ desc = "A revolver speedloader. Cuts through like a hot knife through butter." id = "sl357ap" build_type = PROTOLATHE - materials = list(/datum/material/iron = 30000, /datum/material/titanium = 45000) + materials = list(/datum/material/iron = 30000, /datum/material/titanium = 5000) build_path = /obj/item/ammo_box/a357/ap category = list("Ammo") departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/m10apbox + name = "ammo box (10mm Armour Piercing)" + desc = "A box of ammo containing 20 rounds designed to penetrate armor, at the cost of raw damage." + id = "m10apbox" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 30000, /datum/material/titanium = 6000) + build_path = /obj/item/ammo_box/c10mm/ap + category = list("Ammo") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/m10firebox + name = "ammo box (10mm Incendiary)" + desc = "A box of ammo containing 20 rounds designed to set people ablaze, at the cost of raw damage." + id = "m10firebox" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 30000, /datum/material/plasma = 6000) + build_path = /obj/item/ammo_box/c10mm/fire + category = list("Ammo") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY + +/datum/design/m10hpbox + name = "ammo box (10mm Hollowpoint)" + desc = "A box of ammo containing 20 rounds designed to tear through unarmored opponents, while being completely ineffective against armor." + id = "m10hpbox" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 30000, /datum/material/glass = 6000) + build_path = /obj/item/ammo_box/c10mm/hp + category = list("Ammo") + departmental_flags = DEPARTMENTAL_FLAG_SECURITY diff --git a/tgstation.dme b/tgstation.dme index d13020e2e5..017bd393e7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1416,6 +1416,9 @@ #include "code\modules\admin\ipintel.dm" #include "code\modules\admin\IsBanned.dm" #include "code\modules\admin\NewBan.dm" +#include "code\modules\admin\outfit_editor.dm" +#include "code\modules\admin\outfit_manager.dm" +#include "code\modules\admin\outfits.dm" #include "code\modules\admin\permissionedit.dm" #include "code\modules\admin\player_panel.dm" #include "code\modules\admin\sound_emitter.dm" @@ -1456,6 +1459,7 @@ #include "code\modules\admin\verbs\randomverbs.dm" #include "code\modules\admin\verbs\reestablish_db_connection.dm" #include "code\modules\admin\verbs\secrets.dm" +#include "code\modules\admin\verbs\selectequipment.dm" #include "code\modules\admin\verbs\shuttlepanel.dm" #include "code\modules\admin\verbs\spawnobjasmob.dm" #include "code\modules\admin\verbs\tripAI.dm" diff --git a/tgui/packages/common/collections.js b/tgui/packages/common/collections.js index 7b4540e730..5c0b3d0893 100644 --- a/tgui/packages/common/collections.js +++ b/tgui/packages/common/collections.js @@ -171,6 +171,8 @@ export const sortBy = (...iterateeFns) => array => { return mappedArray; }; +export const sort = sortBy(); + /** * A fast implementation of reduce. */ @@ -235,6 +237,8 @@ export const uniqBy = iterateeFn => array => { return result; }; +export const uniq = uniqBy(); + /** * Creates an array of grouped elements, the first of which contains * the first elements of the given arrays, the second of which contains diff --git a/tgui/packages/tgui/interfaces/DecalPainter.js b/tgui/packages/tgui/interfaces/DecalPainter.js index 90ab589208..c28ba8b4f1 100644 --- a/tgui/packages/tgui/interfaces/DecalPainter.js +++ b/tgui/packages/tgui/interfaces/DecalPainter.js @@ -1,6 +1,7 @@ import { useBackend } from '../backend'; -import { Button, Section } from '../components'; +import { Box, Button, Section } from '../components'; import { Window } from '../layouts'; +import { classes } from 'common/react'; export const DecalPainter = (props, context) => { const { act, data } = useBackend(context); @@ -16,11 +17,25 @@ export const DecalPainter = (props, context) => { {decal_list.map(decal => ( ))}
@@ -28,7 +43,12 @@ export const DecalPainter = (props, context) => { return ( ); })}
@@ -45,7 +74,12 @@ export const DecalPainter = (props, context) => { return ( ); })} diff --git a/tgui/packages/tgui/interfaces/Limbgrower.js b/tgui/packages/tgui/interfaces/Limbgrower.js new file mode 100644 index 0000000000..b91c7e4b45 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Limbgrower.js @@ -0,0 +1,109 @@ +import { useBackend, useSharedState } from '../backend'; +import { Box, Button, Dimmer, Icon, LabeledList, Section, Tabs } from '../components'; +import { Window } from '../layouts'; + +export const Limbgrower = (props, context) => { + const { act, data } = useBackend(context); + const { + reagents = [], + total_reagents, + max_reagents, + categories = [], + busy, + disk = [], + } = data; + const [tab, setTab] = useSharedState( + context, 'category', categories[0]?.name); + const designList = categories + .find(category => category.name === tab) + ?.designs; + + return ( + + {!!busy && ( + + + {' Building...'} + + )} + +
act('eject_disk')} + disabled={!disk['disk']} + /> + }> + {disk['name'] ? ( +
+ Containing data for {disk['name']},
+ Attempting to create genitalia will use the disk's data. +
+ ) : disk['disk'] ? "No data." : "No disk."} +
+
+ + {total_reagents} / {max_reagents} reagent capacity used. + + + {reagents.map(reagent => ( + act('empty_reagent', { + reagent_type: reagent.reagent_type, + })} /> + )}> + {reagent.reagent_amount}u + + ))} + +
+
+ + {categories.map(category => ( + setTab(category.name)}> + {category.name} + + ))} + + + {designList.map(design => ( + act('make_limb', { + design_id: design.id, + active_tab: design.parent_category, + })} /> + )}> + {design.needed_reagents.map(reagent => ( + + {reagent.name}: {reagent.amount}u + + ))} + + ))} + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ListInput.js b/tgui/packages/tgui/interfaces/ListInput.js new file mode 100644 index 0000000000..721ac4f5ac --- /dev/null +++ b/tgui/packages/tgui/interfaces/ListInput.js @@ -0,0 +1,206 @@ +/** + * @file + * @copyright 2020 watermelon914 (https://github.com/watermelon914) + * @license MIT + */ + +import { clamp01 } from 'common/math'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Section, Input, Stack } from '../components'; +import { Window } from '../layouts'; + +const ARROW_KEY_UP = 38; +const ARROW_KEY_DOWN = 40; + +let lastScrollTime = 0; + +export const ListInput = (props, context) => { + const { act, data } = useBackend(context); + const { + title, + message, + buttons, + timeout, + } = data; + + // Search + const [showSearchBar, setShowSearchBar] = useLocalState( + context, 'search_bar', false); + const [displayedArray, setDisplayedArray] = useLocalState( + context, 'displayed_array', buttons); + + // KeyPress + const [searchArray, setSearchArray] = useLocalState( + context, 'search_array', []); + const [searchIndex, setSearchIndex] = useLocalState( + context, 'search_index', 0); + const [lastCharCode, setLastCharCode] = useLocalState( + context, 'last_char_code', null); + + // Selected Button + const [selectedButton, setSelectedButton] = useLocalState( + context, 'selected_button', buttons[0]); + + const handleKeyDown = e => { + e.preventDefault(); + if (lastScrollTime > performance.now()) { + return; + } + lastScrollTime = performance.now() + 125; + + if (e.keyCode === ARROW_KEY_UP || e.keyCode === ARROW_KEY_DOWN) { + let direction = 1; + if (e.keyCode === ARROW_KEY_UP) direction = -1; + + let index = 0; + for (index; index < buttons.length; index++) { + if (buttons[index] === selectedButton) break; + } + index += direction; + if (index < 0) index = buttons.length - 1; + else if (index >= buttons.length) index = 0; + setSelectedButton(buttons[index]); + setLastCharCode(null); + document.getElementById(buttons[index]).focus(); + return; + } + + const charCode = String.fromCharCode(e.keyCode).toLowerCase(); + if (!charCode) return; + + let foundValue; + if (charCode === lastCharCode && searchArray.length > 0) { + const nextIndex = searchIndex + 1; + + if (nextIndex < searchArray.length) { + foundValue = searchArray[nextIndex]; + setSearchIndex(nextIndex); + } + else { + foundValue = searchArray[0]; + setSearchIndex(0); + } + } + else { + const resultArray = displayedArray.filter(value => + value.substring(0, 1).toLowerCase() === charCode + ); + + if (resultArray.length > 0) { + setSearchArray(resultArray); + setSearchIndex(0); + foundValue = resultArray[0]; + } + } + + if (foundValue) { + setLastCharCode(charCode); + setSelectedButton(foundValue); + document.getElementById(foundValue).focus(); + } + }; + + return ( + + {timeout !== undefined && } + + + +
{ + setShowSearchBar(!showSearchBar); + setDisplayedArray(buttons); + }} + /> + )}> + {displayedArray.map(button => ( + + ))} +
+
+ {showSearchBar && ( + + setDisplayedArray( + buttons.filter(val => ( + val.toLowerCase().search(value.toLowerCase()) !== -1 + )) + )} + /> + + )} + + + + + + {currItem?.sprite && ( + <> + + act("clear", { slot })} /> + + )} + + + {currItem?.name || "Empty"} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/OutfitManager.js b/tgui/packages/tgui/interfaces/OutfitManager.js new file mode 100644 index 0000000000..2ffe8cd520 --- /dev/null +++ b/tgui/packages/tgui/interfaces/OutfitManager.js @@ -0,0 +1,81 @@ +import { useBackend } from '../backend'; +import { Button, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +export const OutfitManager = (props, context) => { + const { act, data } = useBackend(context); + const { outfits } = data; + return ( + + +
+
+
+
+ ); +}; + diff --git a/tgui/packages/tgui/interfaces/SelectEquipment.js b/tgui/packages/tgui/interfaces/SelectEquipment.js new file mode 100644 index 0000000000..e7abf709bc --- /dev/null +++ b/tgui/packages/tgui/interfaces/SelectEquipment.js @@ -0,0 +1,214 @@ +import { filter, map, sortBy, uniq } from 'common/collections'; +import { flow } from 'common/fp'; +import { createSearch } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Icon, Input, Section, Stack, Tabs } from '../components'; +import { Window } from '../layouts'; + +// here's an important mental define: +// custom outfits give a ref keyword instead of path +const getOutfitKey = outfit => outfit.path || outfit.ref; + +const useOutfitTabs = (context, categories) => { + return useLocalState(context, 'selected-tab', categories[0]); +}; + +export const SelectEquipment = (props, context) => { + const { act, data } = useBackend(context); + const { + name, + icon64, + current_outfit, + favorites, + } = data; + + const isFavorited = entry => favorites?.includes(entry.path); + + const outfits = map(entry => ({ + ...entry, + favorite: isFavorited(entry), + }))([ + ...data.outfits, + ...data.custom_outfits, + ]); + + // even if no custom outfits were sent, we still want to make sure there's + // at least a 'Custom' tab so the button to create a new one pops up + const categories = uniq([ + ...outfits.map(entry => entry.category), + 'Custom', + ]); + const [tab] = useOutfitTabs(context, categories); + + const [searchText, setSearchText] = useLocalState( + context, 'searchText', ''); + const searchFilter = createSearch(searchText, entry => ( + entry.name + entry.path + )); + + const visibleOutfits = flow([ + filter(entry => entry.category === tab), + filter(searchFilter), + sortBy( + entry => !entry.favorite, + entry => !entry.priority, + entry => entry.name + ), + ])(outfits); + + const getOutfitEntry = current_outfit => outfits.find(outfit => ( + getOutfitKey(outfit) === current_outfit + )); + + const currentOutfitEntry = getOutfitEntry(current_outfit); + + return ( + + + + + + + setSearchText(value)} /> + + + + + + + + + + + + +
+ +
+
+ +
+ +
+
+
+
+
+
+
+ ); +}; + +const DisplayTabs = (props, context) => { + const { categories } = props; + const [tab, setTab] = useOutfitTabs(context, categories); + return ( + + {categories.map(category => ( + setTab(category)}> + {category} + + ))} + + ); +}; + +const OutfitDisplay = (props, context) => { + const { act, data } = useBackend(context); + const { current_outfit } = data; + const { entries, currentTab } = props; + return ( +
+ {entries.map(entry => ( + + )} +
+ ); +}; + +const CurrentlySelectedDisplay = (props, context) => { + const { act, data } = useBackend(context); + const { current_outfit } = data; + const { entry } = props; + return ( + + {entry?.path && ( + + act('togglefavorite', { + path: entry.path, + })} /> + + )} + + + Currently selected: + + + {entry?.name} + + + + + + + ); +};