diff --git a/_maps/RandomRuins/SpaceRuins/crashedship.dmm b/_maps/RandomRuins/SpaceRuins/crashedship.dmm index 63e6770e42..beb93b319e 100644 --- a/_maps/RandomRuins/SpaceRuins/crashedship.dmm +++ b/_maps/RandomRuins/SpaceRuins/crashedship.dmm @@ -2372,7 +2372,7 @@ }, /area/awaymission/BMPship/Midship) "hh" = ( -/obj/item/caution, +/obj/item/clothing/suit/caution, /turf/open/floor/plating/airless, /area/awaymission/BMPship/Midship) "hi" = ( diff --git a/_maps/RandomZLevels/away_mission/Academy.dmm b/_maps/RandomZLevels/away_mission/Academy.dmm index 37b0098fd2..9b61f50600 100644 --- a/_maps/RandomZLevels/away_mission/Academy.dmm +++ b/_maps/RandomZLevels/away_mission/Academy.dmm @@ -2710,7 +2710,7 @@ /obj/structure/cable{ icon_state = "4-8" }, -/obj/item/caution, +/obj/item/clothing/suit/caution, /obj/effect/turf_decal/tile/green, /obj/effect/turf_decal/tile/green{ dir = 4 diff --git a/_maps/RandomZLevels/away_mission/SnowCabin.dmm b/_maps/RandomZLevels/away_mission/SnowCabin.dmm index 844a134d27..fbb83899fd 100644 --- a/_maps/RandomZLevels/away_mission/SnowCabin.dmm +++ b/_maps/RandomZLevels/away_mission/SnowCabin.dmm @@ -312,7 +312,7 @@ /obj/structure/janitorialcart, /obj/item/mop, /obj/item/reagent_containers/glass/bucket, -/obj/item/caution, +/obj/item/clothing/suit/caution, /obj/effect/decal/cleanable/dirt, /turf/open/floor/plating, /area/awaymission/cabin) diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm index 3e87af5427..cb257efeba 100644 --- a/_maps/map_files/BoxStation/BoxStation.dmm +++ b/_maps/map_files/BoxStation/BoxStation.dmm @@ -45265,7 +45265,7 @@ /area/maintenance/aft) "cfq" = ( /obj/structure/mopbucket, -/obj/item/caution, +/obj/item/clothing/suit/caution, /turf/open/floor/plating, /area/maintenance/aft) "cfr" = ( diff --git a/_maps/map_files/CogStation/CogStation.dmm b/_maps/map_files/CogStation/CogStation.dmm index 7350c713ad..482ddc3f22 100644 --- a/_maps/map_files/CogStation/CogStation.dmm +++ b/_maps/map_files/CogStation/CogStation.dmm @@ -3643,7 +3643,7 @@ lootcount = 2; name = "2maintenance loot spawner" }, -/obj/item/caution, +/obj/item/clothing/suit/caution, /turf/open/floor/plating, /area/construction) "ajb" = ( @@ -4579,7 +4579,7 @@ /turf/open/floor/plasteel, /area/security/processing) "alk" = ( -/obj/item/caution, +/obj/item/clothing/suit/caution, /turf/open/floor/plating, /area/construction/secondary) "all" = ( @@ -23340,19 +23340,19 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, -/obj/item/caution{ +/obj/item/clothing/suit/caution{ pixel_x = 6; pixel_y = 3 }, -/obj/item/caution{ +/obj/item/clothing/suit/caution{ pixel_x = 6; pixel_y = 3 }, -/obj/item/caution{ +/obj/item/clothing/suit/caution{ pixel_x = 6; pixel_y = 3 }, -/obj/item/caution{ +/obj/item/clothing/suit/caution{ pixel_x = 6; pixel_y = 3 }, @@ -26810,7 +26810,7 @@ /area/quartermaster/sorting) "bgP" = ( /obj/item/analyzer, -/obj/item/caution, +/obj/item/clothing/suit/caution, /turf/open/floor/plating, /area/maintenance/disposal) "bgQ" = ( @@ -33797,7 +33797,7 @@ /turf/open/floor/plasteel/dark, /area/chapel/office) "bvU" = ( -/obj/item/caution, +/obj/item/clothing/suit/caution, /obj/effect/decal/cleanable/dirt, /obj/structure/sign/warning/biohazard{ pixel_x = -32 @@ -38795,7 +38795,7 @@ /turf/open/floor/plasteel/freezer, /area/crew_quarters/toilet/restrooms) "bGv" = ( -/obj/item/caution, +/obj/item/clothing/suit/caution, /turf/open/floor/plasteel, /area/science/server/compcore) "bGw" = ( @@ -40999,7 +40999,7 @@ dir = 1; pixel_y = -26 }, -/obj/item/caution, +/obj/item/clothing/suit/caution, /obj/item/shovel, /obj/item/stack/tile/plasteel{ pixel_x = 10; @@ -51446,8 +51446,8 @@ /area/quartermaster/warehouse) "cgt" = ( /obj/structure/rack, -/obj/item/caution, -/obj/item/caution, +/obj/item/clothing/suit/caution, +/obj/item/clothing/suit/caution, /obj/effect/decal/cleanable/dirt, /turf/open/floor/plasteel, /area/quartermaster/warehouse) @@ -61826,7 +61826,7 @@ /obj/effect/turf_decal/tile/green{ dir = 8 }, -/obj/item/caution, +/obj/item/clothing/suit/caution, /obj/machinery/atmospherics/pipe/simple/cyan/hidden{ dir = 4 }, @@ -62925,7 +62925,7 @@ /obj/effect/turf_decal/tile/green{ dir = 4 }, -/obj/item/caution, +/obj/item/clothing/suit/caution, /obj/machinery/atmospherics/pipe/manifold4w/orange/hidden, /obj/structure/cable{ icon_state = "4-8" diff --git a/_maps/map_files/LambdaStation/dorms.dmm b/_maps/map_files/LambdaStation/dorms.dmm index a38d2b9e1d..b7adb4a518 100644 --- a/_maps/map_files/LambdaStation/dorms.dmm +++ b/_maps/map_files/LambdaStation/dorms.dmm @@ -10679,7 +10679,7 @@ light_color = "#e8eaff" }, /obj/machinery/vending/wardrobe/jani_wardrobe{ - products = list(/obj/item/clothing/under/rank/civilian/janitor = 2, /obj/item/cartridge/janitor = 2, /obj/item/clothing/gloves/color/black = 2, /obj/item/clothing/head/soft/purple = 2, /obj/item/paint/paint_remover = 2, /obj/item/melee/flyswatter = 2, /obj/item/flashlight = 2, /obj/item/caution = 10, /obj/item/holosign_creator = 2, /obj/item/lightreplacer = 2, /obj/item/soap/nanotrasen = 2, /obj/item/storage/bag/trash = 2, /obj/item/clothing/shoes/galoshes = 2, /obj/item/watertank/janitor = 2, /obj/item/storage/belt/janitor = 2) + products = list(/obj/item/clothing/under/rank/civilian/janitor = 2, /obj/item/cartridge/janitor = 2, /obj/item/clothing/gloves/color/black = 2, /obj/item/clothing/head/soft/purple = 2, /obj/item/paint/paint_remover = 2, /obj/item/melee/flyswatter = 2, /obj/item/flashlight = 2, /obj/item/clothing/suit/caution = 10, /obj/item/holosign_creator = 2, /obj/item/lightreplacer = 2, /obj/item/soap/nanotrasen = 2, /obj/item/storage/bag/trash = 2, /obj/item/clothing/shoes/galoshes = 2, /obj/item/watertank/janitor = 2, /obj/item/storage/belt/janitor = 2) }, /turf/open/floor/plating, /area/janitor) @@ -13637,7 +13637,7 @@ /turf/open/floor/plasteel, /area/hallway/primary/central) "IX" = ( -/obj/item/caution, +/obj/item/clothing/suit/caution, /obj/effect/decal/cleanable/dirt, /obj/effect/decal/cleanable/dirt, /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer3{ diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index ee47b2edab..c2953916e0 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -12681,7 +12681,7 @@ /turf/open/floor/wood, /area/crew_quarters/dorms) "axL" = ( -/obj/item/caution, +/obj/item/clothing/suit/caution, /obj/effect/turf_decal/stripes/line{ dir = 1 }, @@ -39921,9 +39921,6 @@ /turf/open/floor/plasteel/dark, /area/bridge) "bzU" = ( -/obj/structure/chair/comfy/teal{ - dir = 4 - }, /obj/structure/chair/comfy/black{ dir = 4 }, @@ -65296,9 +65293,6 @@ /turf/open/floor/plasteel, /area/medical/genetics) "czd" = ( -/obj/machinery/light_switch{ - pixel_x = 23 - }, /obj/machinery/atmospherics/pipe/simple/supply/hidden{ dir = 4 }, diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm index 0356dd1d07..d367ed82b1 100644 --- a/_maps/map_files/PubbyStation/PubbyStation.dmm +++ b/_maps/map_files/PubbyStation/PubbyStation.dmm @@ -24603,7 +24603,7 @@ /turf/open/floor/plating, /area/maintenance/department/cargo) "beS" = ( -/obj/item/caution, +/obj/item/clothing/suit/caution, /turf/open/floor/plating, /area/maintenance/department/cargo) "beU" = ( diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index e4a2e6521a..a860a8cd3f 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -238,8 +238,11 @@ CHECK_TICK SSdbcore.SetRoundEnd() //Collects persistence features - if(mode.allow_persistence_save) - SSpersistence.CollectData() + if(mode.station_was_nuked) + SSpersistence.station_was_destroyed = TRUE + if(!mode.allow_persistence_save) + SSpersistence.station_persistence_save_disabled = TRUE + SSpersistence.CollectData() //stop collecting feedback during grifftime SSblackbox.Seal() diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm index 0444e42e91..7fad1690e6 100644 --- a/code/_globalvars/lists/flavor_misc.dm +++ b/code/_globalvars/lists/flavor_misc.dm @@ -275,7 +275,7 @@ GLOBAL_LIST_INIT(wisdoms, world.file2list("strings/wisdoms.txt")) //LANGUAGE CHARACTER CUSTOMIZATION GLOBAL_LIST_INIT(speech_verbs, list("default","says","gibbers", "states", "chitters", "chimpers", "declares", "bellows", "buzzes" ,"beeps", "chirps", "clicks", "hisses" ,"poofs" , "puffs", "rattles", "mewls" ,"barks", "blorbles", "squeaks", "squawks", "flutters", "warbles")) -GLOBAL_LIST_INIT(roundstart_tongues, list("default","human tongue" = /obj/item/organ/tongue, "lizard tongue" = /obj/item/organ/tongue/lizard, "skeleton tongue" = /obj/item/organ/tongue/bone, "fly tongue" = /obj/item/organ/tongue/fly, "ipc tongue" = /obj/item/organ/tongue/robot/ipc)) +GLOBAL_LIST_INIT(roundstart_tongues, list("default","human tongue" = /obj/item/organ/tongue, "lizard tongue" = /obj/item/organ/tongue/lizard, "skeleton tongue" = /obj/item/organ/tongue/bone, "fly tongue" = /obj/item/organ/tongue/fly, "ipc tongue" = /obj/item/organ/tongue/robot/ipc, "xeno tongue" = /obj/item/organ/tongue/alien)) //SPECIES BODYPART LISTS //locked parts are those that your picked species requires to have diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 67e4a97894..dc8e9e1859 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -566,3 +566,20 @@ config_entry_value = 0.333 min_val = 0 integer = FALSE + +/// Amount of dirtyness tiles need to spawn dirt. +/datum/config_entry/number/turf_dirt_threshold + config_entry_value = 100 + min_val = 1 + integer = TRUE + +/// Alpha dirt starts at +/datum/config_entry/number/dirt_alpha_starting + config_entry_value = 127 + max_val = 255 + min_val = 0 + integer = TRUE + +/// Dirtyness multiplier for making turfs dirty +/datum/config_entry/number/turf_dirty_multiplier + config_entry_value = 1 diff --git a/code/controllers/configuration/entries/persistence.dm b/code/controllers/configuration/entries/persistence.dm new file mode 100644 index 0000000000..2444034b86 --- /dev/null +++ b/code/controllers/configuration/entries/persistence.dm @@ -0,0 +1,20 @@ +/// Whether or not to use the persistence system for cleanable objects +/datum/config_entry/flag/persistent_debris + config_entry_value = FALSE + +/// Whether or not to nuke all roundstart debris that isn't due to persistence if the above is true +/datum/config_entry/flag/persistent_debris_only + config_entry_value = TRUE + +/// Max amount of objects to store, total +/datum/config_entry/number/persistent_debris_global_max + config_entry_value = 10000 + integer = TRUE + +/// Max amount of objects to store per type +/datum/config_entry/number/persistent_debris_type_max + config_entry_value = 2000 + integer = TRUE + +/// Wipe dirty stuff on nuke +/datum/config_entry/flag/persistent_debris_wipe_on_nuke diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 51d56545b6..da3bb24a2f 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -115,18 +115,22 @@ SUBSYSTEM_DEF(garbage) lastlevel = level - for (var/refID in queue) - if (!refID) + //We do this rather then for(var/refID in queue) because that sort of for loop copies the whole list. + //Normally this isn't expensive, but the gc queue can grow to 40k items, and that gets costly/causes overrun. + for (var/i in 1 to length(queue)) + var/list/L = queue[i] + if (length(L) < 2) count++ if (MC_TICK_CHECK) return continue - var/GCd_at_time = queue[refID] + var/GCd_at_time = L[1] if(GCd_at_time > cut_off_time) break // Everything else is newer, skip them count++ + var/refID = L[2] var/datum/D D = locate(refID) @@ -202,10 +206,7 @@ SUBSYSTEM_DEF(garbage) D.gc_destroyed = gctime var/list/queue = queues[level] - if (queue[refid]) - queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list. - - queue[refid] = gctime + queue[++queue.len] = list(gctime, refid) // not += for byond reasons #ifdef LEGACY_REFERENCE_TRACKING /datum/controller/subsystem/garbage/proc/add_type_to_findref(type) diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 2ea3b29c91..90908bbde0 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -41,6 +41,8 @@ SUBSYSTEM_DEF(mapping) var/datum/space_level/transit var/datum/space_level/empty_space var/num_of_res_levels = 1 + /// Lookup for zlevel to station z. text = num. + var/list/z_to_station_z_index var/stat_map_name = "Loading..." @@ -259,6 +261,16 @@ SUBSYSTEM_DEF(mapping) INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!") return parsed_maps +/datum/controller/subsystem/mapping/proc/setup_station_z_index() + z_to_station_z_index = list() + var/sz = 1 + var/cz = station_start + if(islist(config.map_file)) + for(var/map in config.map_file) + z_to_station_z_index["[cz++]"] = sz++ + else + z_to_station_z_index["[station_start]"] = 1 + /datum/controller/subsystem/mapping/proc/loadWorld() //if any of these fail, something has gone horribly, HORRIBLY, wrong var/list/FailedZs = list() @@ -271,6 +283,8 @@ SUBSYSTEM_DEF(mapping) INIT_ANNOUNCE("Loading [config.map_name]...") LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION, FALSE, config.orientation) + setup_station_z_index() + if(SSdbcore.Connect()) var/datum/DBQuery/query_round_map_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET map_name = '[config.map_name]' WHERE id = [GLOB.round_id]") query_round_map_name.Execute() diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm similarity index 51% rename from code/controllers/subsystem/persistence.dm rename to code/controllers/subsystem/persistence/_persistence.dm index e39242aac3..741d1dd72a 100644 --- a/code/controllers/subsystem/persistence.dm +++ b/code/controllers/subsystem/persistence/_persistence.dm @@ -5,18 +5,14 @@ SUBSYSTEM_DEF(persistence) name = "Persistence" init_order = INIT_ORDER_PERSISTENCE flags = SS_NO_FIRE - var/list/satchel_blacklist = list() //this is a typecache - var/list/new_secret_satchels = list() //these are objects - var/list/old_secret_satchels = list() + + /// Marks if the station got horribly destroyed + var/station_was_destroyed = FALSE + /// Marks if persistence save should be disabled + var/station_persistence_save_disabled = FALSE var/list/obj/structure/chisel_message/chisel_messages = list() var/list/saved_messages = list() - var/list/saved_modes = list(1,2,3) - var/list/saved_dynamic_rules = list(list(),list(),list()) - var/list/saved_storytellers = list("foo","bar","baz") - var/list/average_dynamic_threat = 50 - var/list/saved_maps - var/list/saved_trophies = list() var/list/spawned_objects = list() var/list/antag_rep = list() var/list/antag_rep_change = list() @@ -28,62 +24,81 @@ SUBSYSTEM_DEF(persistence) var/list/paintings = list() /datum/controller/subsystem/persistence/Initialize() - LoadSatchels() - LoadPoly() - LoadChiselMessages() - LoadTrophies() - LoadRecentModes() - LoadRecentStorytellers() - LoadRecentRulesets() - LoadRecentMaps() - LoadPhotoPersistence() + LoadServerPersistence() + LoadGamePersistence() + var/map_persistence_path = get_map_persistence_path() + if(map_persistence_path) + LoadMapPersistence() + return ..() + +/** + * Gets the persistence path of the current map. + */ +/datum/controller/subsystem/persistence/proc/get_map_persistence_path() + ASSERT(SSmapping.config) + if(!SSmapping.config.persistence_key || (SSmapping.config.persistence_key == "NO_PERSIST")) + return null + return "data/persistence/[ckey(SSmapping.config.persistence_key)]" + +/datum/controller/subsystem/persistence/proc/CollectData() + SaveServerPersistence() + if(station_persistence_save_disabled) + return + SaveGamePersistence() + var/map_persistence_path = get_map_persistence_path() + if(map_persistence_path) + SaveMapPersistence() + +/** + * Loads persistent data relevant to the server: Configurations, past gamemodes, votes, antag rep, etc + */ +/datum/controller/subsystem/persistence/proc/LoadServerPersistence() for(var/client/C in GLOB.clients) LoadSavedVote(C.ckey) if(CONFIG_GET(flag/use_antag_rep)) LoadAntagReputation() LoadRandomizedRecipes() - LoadPanicBunker() - return ..() -/datum/controller/subsystem/persistence/proc/LoadSatchels() - var/placed_satchel = 0 - var/path +/** + * Saves persistent data relevant to the server: Configurations, past gamemodes, votes, antag rep, etc + */ +/datum/controller/subsystem/persistence/proc/SaveServerPersistence() + if(CONFIG_GET(flag/use_antag_rep)) + CollectAntagReputation() + SaveRandomizedRecipes() - var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json") - var/list/json = list() - if(fexists(json_file)) - json = json_decode(file2text(json_file)) +/** + * Loads persistent data relevant to the game in general: Photos, etc + * + * Legacy map persistence systems also use this. + */ +/datum/controller/subsystem/persistence/proc/LoadGamePersistence() + LoadChiselMessages() + LoadPhotoPersistence() + LoadPaintings() - old_secret_satchels = json["data"] - var/obj/item/storage/backpack/satchel/flat/F - if(old_secret_satchels && old_secret_satchels.len >= 10) //guards against low drop pools assuring that one player cannot reliably find his own gear. - var/pos = rand(1, old_secret_satchels.len) - F = new() - old_secret_satchels.Cut(pos, pos+1 % old_secret_satchels.len) - F.x = old_secret_satchels[pos]["x"] - F.y = old_secret_satchels[pos]["y"] - F.z = SSmapping.station_start - path = text2path(old_secret_satchels[pos]["saved_obj"]) +/** + * Saves persistent data relevant to the game in general: Photos, etc + * + * Legacy map persistence systems also use this. + */ +/datum/controller/subsystem/persistence/proc/SaveGamePersistence() + CollectChiselMessages() + SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. + SavePaintings() + SaveScars() - if(F) - if(isfloorturf(F.loc) && !isplatingturf(F.loc)) - F.hide(1) - if(ispath(path)) - var/spawned_item = new path(F) - spawned_objects[spawned_item] = TRUE - placed_satchel++ - var/free_satchels = 0 - for(var/turf/T in shuffle(block(locate(TRANSITIONEDGE,TRANSITIONEDGE,SSmapping.station_start), locate(world.maxx-TRANSITIONEDGE,world.maxy-TRANSITIONEDGE,SSmapping.station_start)))) //Nontrivially expensive but it's roundstart only - if(isfloorturf(T) && !isplatingturf(T)) - new /obj/item/storage/backpack/satchel/flat/secret(T) - free_satchels++ - if((free_satchels + placed_satchel) == 10) //ten tiles, more than enough to kill anything that moves - break +/** + * Loads persistent data relevant to the current map: Objects, etc. + */ +/datum/controller/subsystem/persistence/proc/LoadMapPersistence() + return -/datum/controller/subsystem/persistence/proc/LoadPoly() - for(var/mob/living/simple_animal/parrot/Poly/P in GLOB.alive_mob_list) - twitterize(P.speech_buffer, "polytalk") - break //Who's been duping the bird?! +/** + * Saves persistent data relevant to the current map: Objects, etc. + */ +/datum/controller/subsystem/persistence/proc/SaveMapPersistence() + return /datum/controller/subsystem/persistence/proc/LoadChiselMessages() var/list/saved_messages = list() @@ -130,63 +145,6 @@ SUBSYSTEM_DEF(persistence) log_world("Loaded [saved_messages.len] engraved messages on map [SSmapping.config.map_name]") -/datum/controller/subsystem/persistence/proc/LoadTrophies() - if(fexists("data/npc_saves/TrophyItems.sav")) //legacy compatability to convert old format to new - var/savefile/S = new /savefile("data/npc_saves/TrophyItems.sav") - var/saved_json - S >> saved_json - if(!saved_json) - return - saved_trophies = json_decode(saved_json) - fdel("data/npc_saves/TrophyItems.sav") - else - var/json_file = file("data/npc_saves/TrophyItems.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - saved_trophies = json["data"] - SetUpTrophies(saved_trophies.Copy()) - -/datum/controller/subsystem/persistence/proc/LoadRecentModes() - var/json_file = file("data/RecentModes.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - saved_modes = json["data"] - -/datum/controller/subsystem/persistence/proc/LoadRecentRulesets() - var/json_file = file("data/RecentRulesets.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - saved_dynamic_rules = json["data"] - -/datum/controller/subsystem/persistence/proc/LoadRecentStorytellers() - var/json_file = file("data/RecentStorytellers.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - saved_storytellers = json["data"] - if(saved_storytellers.len > 3) - average_dynamic_threat = saved_storytellers[4] - saved_storytellers.len = 3 - -/datum/controller/subsystem/persistence/proc/LoadRecentMaps() - var/json_file = file("data/RecentMaps.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - if(!json) - return - saved_maps = json["maps"] /datum/controller/subsystem/persistence/proc/LoadAntagReputation() var/json = file2text(FILE_ANTAG_REP) @@ -207,59 +165,6 @@ SUBSYSTEM_DEF(persistence) return saved_votes[ckey] = json["data"] -/datum/controller/subsystem/persistence/proc/SetUpTrophies(list/trophy_items) - for(var/A in GLOB.trophy_cases) - var/obj/structure/displaycase/trophy/T = A - if (T.showpiece) - continue - T.added_roundstart = TRUE - - var/trophy_data = pick_n_take(trophy_items) - - if(!islist(trophy_data)) - continue - - var/list/chosen_trophy = trophy_data - - if(!chosen_trophy || isemptylist(chosen_trophy)) //Malformed - continue - - var/path = text2path(chosen_trophy["path"]) //If the item no longer exist, this returns null - if(!path) - continue - - T.showpiece = new /obj/item/showpiece_dummy(T, path) - T.trophy_message = chosen_trophy["message"] - T.placer_key = chosen_trophy["placer_key"] - T.update_icon() - -/datum/controller/subsystem/persistence/proc/CollectData() - CollectChiselMessages() - CollectSecretSatchels() - CollectTrophies() - CollectRoundtype() - if(istype(SSticker.mode, /datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - CollectStoryteller(mode) - CollectRulesets(mode) - RecordMaps() - SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. - if(CONFIG_GET(flag/use_antag_rep)) - CollectAntagReputation() - SaveRandomizedRecipes() - SavePanicBunker() - SavePaintings() - SaveScars() - -/datum/controller/subsystem/persistence/proc/LoadPanicBunker() - var/bunker_path = file("data/bunker_passthrough.json") - if(fexists(bunker_path)) - var/list/json = json_decode(file2text(bunker_path)) - GLOB.bunker_passthrough = json["data"] - for(var/ckey in GLOB.bunker_passthrough) - if(daysSince(GLOB.bunker_passthrough[ckey]) >= CONFIG_GET(number/max_bunker_days)) - GLOB.bunker_passthrough -= ckey - /datum/controller/subsystem/persistence/proc/GetPhotoAlbums() var/album_path = file("data/photo_albums.json") if(fexists(album_path)) @@ -329,35 +234,6 @@ SUBSYSTEM_DEF(persistence) WRITE_FILE(frame_path, frame_json) -/datum/controller/subsystem/persistence/proc/CollectSecretSatchels() - satchel_blacklist = typecacheof(list(/obj/item/stack/tile/plasteel, /obj/item/crowbar)) - var/list/satchels_to_add = list() - for(var/A in new_secret_satchels) - var/obj/item/storage/backpack/satchel/flat/F = A - if(QDELETED(F) || F.z != SSmapping.station_start || F.invisibility != INVISIBILITY_MAXIMUM) - continue - var/list/savable_obj = list() - for(var/obj/O in F) - if(is_type_in_typecache(O, satchel_blacklist) || (O.flags_1 & ADMIN_SPAWNED_1)) - continue - if(O.persistence_replacement) - savable_obj += O.persistence_replacement - else - savable_obj += O.type - if(isemptylist(savable_obj)) - continue - var/list/data = list() - data["x"] = F.x - data["y"] = F.y - data["saved_obj"] = pick(savable_obj) - satchels_to_add += list(data) - - var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json") - var/list/file_data = list() - fdel(json_file) - file_data["data"] = old_secret_satchels + satchels_to_add - WRITE_FILE(json_file, json_encode(file_data)) - /datum/controller/subsystem/persistence/proc/CollectChiselMessages() var/json_file = file("data/npc_saves/ChiselMessages[SSmapping.config.map_name].json") @@ -373,84 +249,6 @@ SUBSYSTEM_DEF(persistence) /datum/controller/subsystem/persistence/proc/SaveChiselMessage(obj/structure/chisel_message/M) saved_messages += list(M.pack()) // dm eats one list - -/datum/controller/subsystem/persistence/proc/CollectTrophies() - var/json_file = file("data/npc_saves/TrophyItems.json") - var/list/file_data = list() - file_data["data"] = remove_duplicate_trophies(saved_trophies) - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/datum/controller/subsystem/persistence/proc/SavePanicBunker() - var/json_file = file("data/bunker_passthrough.json") - var/list/file_data = list() - file_data["data"] = GLOB.bunker_passthrough - fdel(json_file) - WRITE_FILE(json_file,json_encode(file_data)) - -/datum/controller/subsystem/persistence/proc/remove_duplicate_trophies(list/trophies) - var/list/ukeys = list() - . = list() - for(var/trophy in trophies) - var/tkey = "[trophy["path"]]-[trophy["message"]]" - if(ukeys[tkey]) - continue - else - . += list(trophy) - ukeys[tkey] = TRUE - -/datum/controller/subsystem/persistence/proc/SaveTrophy(obj/structure/displaycase/trophy/T) - if(!T.added_roundstart && T.showpiece) - var/list/data = list() - data["path"] = T.showpiece.type - data["message"] = T.trophy_message - data["placer_key"] = T.placer_key - saved_trophies += list(data) - -/datum/controller/subsystem/persistence/proc/CollectRoundtype() - saved_modes[3] = saved_modes[2] - saved_modes[2] = saved_modes[1] - saved_modes[1] = SSticker.mode.config_tag - var/json_file = file("data/RecentModes.json") - var/list/file_data = list() - file_data["data"] = saved_modes - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode) - saved_storytellers.len = 3 - saved_storytellers[3] = saved_storytellers[2] - saved_storytellers[2] = saved_storytellers[1] - saved_storytellers[1] = mode.storyteller.name - average_dynamic_threat = (mode.max_threat + average_dynamic_threat) / 2 - var/json_file = file("data/RecentStorytellers.json") - var/list/file_data = list() - file_data["data"] = saved_storytellers + average_dynamic_threat - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/datum/controller/subsystem/persistence/proc/CollectRulesets(var/datum/game_mode/dynamic/mode) - saved_dynamic_rules[3] = saved_dynamic_rules[2] - saved_dynamic_rules[2] = saved_dynamic_rules[1] - saved_dynamic_rules[1] = list() - for(var/r in mode.executed_rules) - var/datum/dynamic_ruleset/rule = r - saved_dynamic_rules[1] += rule.config_tag - var/json_file = file("data/RecentRulesets.json") - var/list/file_data = list() - file_data["data"] = saved_dynamic_rules - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/datum/controller/subsystem/persistence/proc/RecordMaps() - saved_maps = saved_maps?.len ? list("[SSmapping.config.map_name]") | saved_maps : list("[SSmapping.config.map_name]") - var/json_file = file("data/RecentMaps.json") - var/list/file_data = list() - file_data["maps"] = saved_maps - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - - /datum/controller/subsystem/persistence/proc/CollectAntagReputation() var/ANTAG_REP_MAXIMUM = CONFIG_GET(number/antag_rep_maximum) diff --git a/code/controllers/subsystem/persistence/cleanable_debris.dm b/code/controllers/subsystem/persistence/cleanable_debris.dm new file mode 100644 index 0000000000..a0817c460b --- /dev/null +++ b/code/controllers/subsystem/persistence/cleanable_debris.dm @@ -0,0 +1,177 @@ +/** + * Persistence for cleanable debris. + */ +/datum/controller/subsystem/persistence + /// tracks if we already loaded debris. Unlike everything else, this can actually be a major problem if some badmin procs it twice. + var/loaded_debris = FALSE + +/datum/controller/subsystem/persistence/LoadMapPersistence() + . = ..() + if(CONFIG_GET(flag/persistent_debris)) + LoadMapDebris() + +/datum/controller/subsystem/persistence/SaveMapPersistence() + . = ..() + if(CONFIG_GET(flag/persistent_debris)) + SaveMapDebris() + +/datum/controller/subsystem/persistence/proc/LoadMapDebris() + if(CONFIG_GET(flag/persistent_debris_only)) + wipe_existing_debris() + if(!fexists("[get_map_persistence_path()]/debris.json")) + return + if(loaded_debris) + return + loaded_debris = TRUE + var/list/allowed_turf_typecache = typecacheof(/turf/open) - typecacheof(/turf/open/space) + var/list/allowed_z_cache = list() + for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION)) + allowed_z_cache[num2text(z)] = TRUE + var/list/data = json_decode(file2text("[get_map_persistence_path()]/debris.json")) + var/list/z_lookup = list() + var/loaded = 0 + var/list/loaded_by_type = list() + var/nopath = 0 + var/badloc = 0 + var/noturf = 0 + /// reverse it + for(var/z in SSmapping.z_to_station_z_index) + var/sz = SSmapping.z_to_station_z_index[z] + z_lookup[num2text(sz)] = text2num(z) + for(var/z in data) + var/actual_z = z_lookup[z] + var/list/L1 = data[z] + for(var/x in L1) + var/list/L2 = data[z][x] + for(var/y in L2) + var/turf/tile = locate(text2num(x), text2num(y), actual_z) + if(!tile) + noturf++ + continue + var/list/objects = data[z][x][y] + for(var/_L in objects) + var/list/objdata + var/path + if(islist(_L)) + objdata = _L + path = text2path(objdata["__PATH__"]) + else + path = text2path(_L) + objdata = objects[_L] + if(!path) + nopath++ + continue + if(!IsValidDebrisLocation(tile, allowed_turf_typecache, allowed_z_cache, path, TRUE)) + badloc++ + continue + var/obj/effect/decal/cleanable/instantiated = new path(tile) + loaded_by_type[path] += 1 + loaded++ + if(objdata) + instantiated.PersistenceLoad(objdata) + var/list/bytype = list() + for(var/path in loaded_by_type) + bytype += "[path] - [loaded_by_type[path]]" + subsystem_log( + {"Debris loading completed: + Errors: + No path: [nopath] + Invalid location: [badloc] + No turf on map: [noturf] + Total loaded: [loaded] + By type: + [bytype.Join("\n")]"} + ) + +/datum/controller/subsystem/persistence/proc/SaveMapDebris() + if(fexists("[get_map_persistence_path()]/debris.json")) + fdel("[get_map_persistence_path()]/debris.json") + if(CONFIG_GET(flag/persistent_debris_wipe_on_nuke) && station_was_destroyed) + return // local janitor cheers on nukeop team to save some work + var/list/data = list() + var/list/z_lookup = SSmapping.z_to_station_z_index + var/list/debris = RelevantPersistentDebris() + var/obj/effect/decal/cleanable/saving + var/global_max = CONFIG_GET(number/persistent_debris_global_max) + var/type_max = CONFIG_GET(number/persistent_debris_type_max) + var/stored = 0 + var/list/stored_by_type = list() + for(var/i in debris) + saving = i + var/list/serializing = list() + var/path = saving.PersistenceSave(serializing) + if(!path) + continue + if(stored_by_type[path] > type_max) + continue + var/text_z = num2text(z_lookup[num2text(saving.z)]) + var/text_y = num2text(saving.y) + var/text_x = num2text(saving.x) + LAZYINITLIST(data[text_z]) + LAZYINITLIST(data[text_z][text_x]) + LAZYINITLIST(data[text_z][text_x][text_y]) + if(saving.persistence_allow_stacking) + serializing["__PATH__"] = path + data[text_z][text_x][text_y] += list(serializing) + else + data[text_z][text_x][text_y][path] = serializing + stored++ + if(stored > global_max) + var/w = "Persistent debris saving globally aborted due to global max >= [global_max]. Either janitors never do their jobs or something is wrong." + message_admins(w) + subsystem_log(w) + return + stored_by_type[path] = stored_by_type[path]? stored_by_type[path] + 1 : 1 + if(stored_by_type[path] > type_max) + var/w = "Persistent debris saving aborted for type [path] due to type max >= [global_max]. Either janitors never do their jobs or something is wrong." + message_admins(w) + subsystem_log(w) + + var/list/bytype = list() + for(var/path in stored_by_type) + bytype += "[path] - [stored_by_type[path]]" + subsystem_log( + {"Debris saving completed: + Total: [stored] + By type: + [bytype.Join("\n")]"} + ) + WRITE_FILE(file("[get_map_persistence_path()]/debris.json"), json_encode(data)) + +/datum/controller/subsystem/persistence/proc/IsValidDebrisLocation(turf/tile, list/allowed_typecache, list/allowed_zcache, obj/effect/decal/cleanable/type, loading = FALSE) + if(!allowed_typecache[tile.type]) + return FALSE + var/area/A = tile.loc + if(!A.persistent_debris_allowed) + return FALSE + if(!allowed_zcache[num2text(tile.z)]) + return FALSE + if(loading) + if(!initial(type.persistence_allow_stacking)) + var/obj/effect/decal/cleanable/C = locate(type) in tile + if(!QDELETED(C)) + return FALSE + // Saving verifies allow stacking in the save proc. + for(var/obj/structure/window/W in tile) + if(W.fulltile) + return FALSE + return TRUE + +/datum/controller/subsystem/persistence/proc/wipe_existing_debris() + var/list/existing = RelevantPersistentDebris() + QDEL_LIST(existing) + +/datum/controller/subsystem/persistence/proc/RelevantPersistentDebris() + var/list/allowed_turf_typecache = typecacheof(/turf/open) - typecacheof(/turf/open/space) + var/list/allowed_z_cache = list() + for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION)) + allowed_z_cache[num2text(z)] = TRUE + . = list() + for(var/obj/effect/decal/cleanable/C in world) + if(!C.loc || QDELETED(C)) + continue + if(!C.persistent) + continue + if(!IsValidDebrisLocation(C.loc, allowed_turf_typecache, allowed_z_cache, C.type, FALSE)) + continue + . += C diff --git a/code/controllers/subsystem/persistence/panic_bunker.dm b/code/controllers/subsystem/persistence/panic_bunker.dm new file mode 100644 index 0000000000..93ada2835c --- /dev/null +++ b/code/controllers/subsystem/persistence/panic_bunker.dm @@ -0,0 +1,26 @@ +/** + * Persists panic bunker whitelisting for a configured period of time + */ +/datum/controller/subsystem/persistence/LoadServerPersistence() + . = ..() + LoadPanicBunker() + +/datum/controller/subsystem/persistence/SaveServerPersistence() + . = ..() + SavePanicBunker() + +/datum/controller/subsystem/persistence/proc/LoadPanicBunker() + var/bunker_path = file("data/bunker_passthrough.json") + if(fexists(bunker_path)) + var/list/json = json_decode(file2text(bunker_path)) + GLOB.bunker_passthrough = json["data"] + for(var/ckey in GLOB.bunker_passthrough) + if(daysSince(GLOB.bunker_passthrough[ckey]) >= CONFIG_GET(number/max_bunker_days)) + GLOB.bunker_passthrough -= ckey + +/datum/controller/subsystem/persistence/proc/SavePanicBunker() + var/json_file = file("data/bunker_passthrough.json") + var/list/file_data = list() + file_data["data"] = GLOB.bunker_passthrough + fdel(json_file) + WRITE_FILE(json_file,json_encode(file_data)) diff --git a/code/controllers/subsystem/persistence/poly_parrot.dm b/code/controllers/subsystem/persistence/poly_parrot.dm new file mode 100644 index 0000000000..64743e9623 --- /dev/null +++ b/code/controllers/subsystem/persistence/poly_parrot.dm @@ -0,0 +1,11 @@ +/** + * Persists poly messages across rounds + */ +/datum/controller/subsystem/persistence/LoadGamePersistence() + . = ..() + LoadPoly() + +/datum/controller/subsystem/persistence/proc/LoadPoly() + for(var/mob/living/simple_animal/parrot/Poly/P in GLOB.alive_mob_list) + twitterize(P.speech_buffer, "polytalk") + break //Who's been duping the bird?! diff --git a/code/controllers/subsystem/persistence/recent_votes_etc.dm b/code/controllers/subsystem/persistence/recent_votes_etc.dm new file mode 100644 index 0000000000..f1b902d6ab --- /dev/null +++ b/code/controllers/subsystem/persistence/recent_votes_etc.dm @@ -0,0 +1,107 @@ +/** + * Stores recently played gamemodes, maps, etc. + */ +/datum/controller/subsystem/persistence + var/list/saved_modes = list(1,2,3) + var/list/saved_dynamic_rules = list(list(),list(),list()) + var/list/saved_storytellers = list("foo","bar","baz") + var/list/average_dynamic_threat = 50 + var/list/saved_maps + +/datum/controller/subsystem/persistence/SaveServerPersistence() + . = ..() + CollectRoundtype() + if(istype(SSticker.mode, /datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + CollectStoryteller(mode) + CollectRulesets(mode) + RecordMaps() + +/datum/controller/subsystem/persistence/LoadServerPersistence() + . = ..() + LoadRecentModes() + LoadRecentStorytellers() + LoadRecentRulesets() + LoadRecentMaps() + +/datum/controller/subsystem/persistence/proc/CollectRoundtype() + saved_modes[3] = saved_modes[2] + saved_modes[2] = saved_modes[1] + saved_modes[1] = SSticker.mode.config_tag + var/json_file = file("data/RecentModes.json") + var/list/file_data = list() + file_data["data"] = saved_modes + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode) + saved_storytellers.len = 3 + saved_storytellers[3] = saved_storytellers[2] + saved_storytellers[2] = saved_storytellers[1] + saved_storytellers[1] = mode.storyteller.name + average_dynamic_threat = (mode.max_threat + average_dynamic_threat) / 2 + var/json_file = file("data/RecentStorytellers.json") + var/list/file_data = list() + file_data["data"] = saved_storytellers + average_dynamic_threat + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/datum/controller/subsystem/persistence/proc/CollectRulesets(var/datum/game_mode/dynamic/mode) + saved_dynamic_rules[3] = saved_dynamic_rules[2] + saved_dynamic_rules[2] = saved_dynamic_rules[1] + saved_dynamic_rules[1] = list() + for(var/r in mode.executed_rules) + var/datum/dynamic_ruleset/rule = r + saved_dynamic_rules[1] += rule.config_tag + var/json_file = file("data/RecentRulesets.json") + var/list/file_data = list() + file_data["data"] = saved_dynamic_rules + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/datum/controller/subsystem/persistence/proc/RecordMaps() + saved_maps = saved_maps?.len ? list("[SSmapping.config.map_name]") | saved_maps : list("[SSmapping.config.map_name]") + var/json_file = file("data/RecentMaps.json") + var/list/file_data = list() + file_data["maps"] = saved_maps + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/datum/controller/subsystem/persistence/proc/LoadRecentModes() + var/json_file = file("data/RecentModes.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + saved_modes = json["data"] + +/datum/controller/subsystem/persistence/proc/LoadRecentRulesets() + var/json_file = file("data/RecentRulesets.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + saved_dynamic_rules = json["data"] + +/datum/controller/subsystem/persistence/proc/LoadRecentStorytellers() + var/json_file = file("data/RecentStorytellers.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + saved_storytellers = json["data"] + if(saved_storytellers.len > 3) + average_dynamic_threat = saved_storytellers[4] + saved_storytellers.len = 3 + +/datum/controller/subsystem/persistence/proc/LoadRecentMaps() + var/json_file = file("data/RecentMaps.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + saved_maps = json["maps"] diff --git a/code/controllers/subsystem/persistence/secret_satchels.dm b/code/controllers/subsystem/persistence/secret_satchels.dm new file mode 100644 index 0000000000..fa2a445317 --- /dev/null +++ b/code/controllers/subsystem/persistence/secret_satchels.dm @@ -0,0 +1,79 @@ +/** + * Secret satchel persistence - allows storing of items in underfloor satchels that's loaded later. + */ +/datum/controller/subsystem/persistence + var/list/satchel_blacklist = list() //this is a typecache + var/list/new_secret_satchels = list() //these are objects + var/list/old_secret_satchels = list() + +/datum/controller/subsystem/persistence/LoadGamePersistence() + . = ..() + LoadSatchels() + +/datum/controller/subsystem/persistence/SaveGamePersistence() + . = ..() + CollectSecretSatchels() + +/datum/controller/subsystem/persistence/proc/LoadSatchels() + var/placed_satchel = 0 + var/path + + var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json") + var/list/json = list() + if(fexists(json_file)) + json = json_decode(file2text(json_file)) + + old_secret_satchels = json["data"] + var/obj/item/storage/backpack/satchel/flat/F + if(old_secret_satchels && old_secret_satchels.len >= 10) //guards against low drop pools assuring that one player cannot reliably find his own gear. + var/pos = rand(1, old_secret_satchels.len) + F = new() + old_secret_satchels.Cut(pos, pos+1 % old_secret_satchels.len) + F.x = old_secret_satchels[pos]["x"] + F.y = old_secret_satchels[pos]["y"] + F.z = SSmapping.station_start + path = text2path(old_secret_satchels[pos]["saved_obj"]) + + if(F) + if(isfloorturf(F.loc) && !isplatingturf(F.loc)) + F.hide(1) + if(ispath(path)) + var/spawned_item = new path(F) + spawned_objects[spawned_item] = TRUE + placed_satchel++ + var/free_satchels = 0 + for(var/turf/T in shuffle(block(locate(TRANSITIONEDGE,TRANSITIONEDGE,SSmapping.station_start), locate(world.maxx-TRANSITIONEDGE,world.maxy-TRANSITIONEDGE,SSmapping.station_start)))) //Nontrivially expensive but it's roundstart only + if(isfloorturf(T) && !isplatingturf(T)) + new /obj/item/storage/backpack/satchel/flat/secret(T) + free_satchels++ + if((free_satchels + placed_satchel) == 10) //ten tiles, more than enough to kill anything that moves + break + +/datum/controller/subsystem/persistence/proc/CollectSecretSatchels() + satchel_blacklist = typecacheof(list(/obj/item/stack/tile/plasteel, /obj/item/crowbar)) + var/list/satchels_to_add = list() + for(var/A in new_secret_satchels) + var/obj/item/storage/backpack/satchel/flat/F = A + if(QDELETED(F) || F.z != SSmapping.station_start || F.invisibility != INVISIBILITY_MAXIMUM) + continue + var/list/savable_obj = list() + for(var/obj/O in F) + if(is_type_in_typecache(O, satchel_blacklist) || (O.flags_1 & ADMIN_SPAWNED_1)) + continue + if(O.persistence_replacement) + savable_obj += O.persistence_replacement + else + savable_obj += O.type + if(isemptylist(savable_obj)) + continue + var/list/data = list() + data["x"] = F.x + data["y"] = F.y + data["saved_obj"] = pick(savable_obj) + satchels_to_add += list(data) + + var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json") + var/list/file_data = list() + fdel(json_file) + file_data["data"] = old_secret_satchels + satchels_to_add + WRITE_FILE(json_file, json_encode(file_data)) diff --git a/code/controllers/subsystem/persistence/trophies.dm b/code/controllers/subsystem/persistence/trophies.dm new file mode 100644 index 0000000000..770ca99125 --- /dev/null +++ b/code/controllers/subsystem/persistence/trophies.dm @@ -0,0 +1,84 @@ +/** + * Stores trophies in curator display cases + */ +/datum/controller/subsystem/persistence + var/list/saved_trophies = list() + +/datum/controller/subsystem/persistence/LoadGamePersistence() + . = ..() + LoadTrophies() + +/datum/controller/subsystem/persistence/SaveGamePersistence() + . = ..() + CollectTrophies() + +/datum/controller/subsystem/persistence/proc/LoadTrophies() + if(fexists("data/npc_saves/TrophyItems.sav")) //legacy compatability to convert old format to new + var/savefile/S = new /savefile("data/npc_saves/TrophyItems.sav") + var/saved_json + S >> saved_json + if(!saved_json) + return + saved_trophies = json_decode(saved_json) + fdel("data/npc_saves/TrophyItems.sav") + else + var/json_file = file("data/npc_saves/TrophyItems.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + saved_trophies = json["data"] + SetUpTrophies(saved_trophies.Copy()) + +/datum/controller/subsystem/persistence/proc/SetUpTrophies(list/trophy_items) + for(var/A in GLOB.trophy_cases) + var/obj/structure/displaycase/trophy/T = A + if (T.showpiece) + continue + T.added_roundstart = TRUE + + var/trophy_data = pick_n_take(trophy_items) + + if(!islist(trophy_data)) + continue + + var/list/chosen_trophy = trophy_data + + if(!chosen_trophy || isemptylist(chosen_trophy)) //Malformed + continue + + var/path = text2path(chosen_trophy["path"]) //If the item no longer exist, this returns null + if(!path) + continue + + T.showpiece = new /obj/item/showpiece_dummy(T, path) + T.trophy_message = chosen_trophy["message"] + T.placer_key = chosen_trophy["placer_key"] + T.update_icon() + +/datum/controller/subsystem/persistence/proc/CollectTrophies() + var/json_file = file("data/npc_saves/TrophyItems.json") + var/list/file_data = list() + file_data["data"] = remove_duplicate_trophies(saved_trophies) + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/datum/controller/subsystem/persistence/proc/remove_duplicate_trophies(list/trophies) + var/list/ukeys = list() + . = list() + for(var/trophy in trophies) + var/tkey = "[trophy["path"]]-[trophy["message"]]" + if(ukeys[tkey]) + continue + else + . += list(trophy) + ukeys[tkey] = TRUE + +/datum/controller/subsystem/persistence/proc/SaveTrophy(obj/structure/displaycase/trophy/T) + if(!T.added_roundstart && T.showpiece) + var/list/data = list() + data["path"] = T.showpiece.type + data["message"] = T.trophy_message + data["placer_key"] = T.placer_key + saved_trophies += list(data) diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index 5e9371f754..79c12c7fea 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -85,6 +85,15 @@ var/mob/M = teleatom M.cancel_camera() + var/static/list/bread_cache = typecacheof(/obj/item/reagent_containers/food/snacks/store/bread) + var/list/breadlist = typecache_filter_list(teleatom.GetAllContents(), bread_cache) + if(breadlist.len && (channel == TELEPORT_CHANNEL_BLUESPACE || channel == TELEPORT_CHANNEL_QUANTUM)) + for(var/obj/item/reagent_containers/food/snacks/store/bread/bread in breadlist) + bread.bread_teleport() + else if(istype(teleatom, /obj/item/reagent_containers/food/snacks/store/bread)) + var/obj/item/reagent_containers/food/snacks/store/bread/bread = teleatom + bread.bread_teleport() + return TRUE /proc/tele_play_specials(atom/movable/teleatom, atom/location, datum/effect_system/effect, sound) diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm index edd2047bd6..2c2cc46344 100644 --- a/code/game/area/Space_Station_13_areas.dm +++ b/code/game/area/Space_Station_13_areas.dm @@ -41,6 +41,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station power_environ = FALSE valid_territory = FALSE outdoors = TRUE + persistent_debris_allowed = FALSE ambientsounds = SPACE blob_allowed = FALSE //Eating up space doesn't count for victory as a blob. considered_hull_exterior = TRUE diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index b9415f3cf8..ff177898cb 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -24,6 +24,10 @@ var/clockwork_warp_allowed = TRUE /// Message to display when the clockwork warp fails var/clockwork_warp_fail = "The structure there is too dense for warping to pierce. (This is normal in high-security areas.)" + /// Persistent debris alowed + var/persistent_debris_allowed = TRUE + /// Dirty flooring allowed + var/dirt_buildup_allowed = TRUE /// If mining tunnel generation is allowed in this area var/tunnel_allowed = FALSE diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index 4ba8d9f3d3..75a181b922 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -6,6 +6,7 @@ name = "gas sensor" icon = 'icons/obj/stationobjs.dmi' icon_state = "gsensor1" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 0) var/on = TRUE diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 34745a65f6..29883a073a 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -774,6 +774,9 @@ stun_projectile_sound = 'sound/weapons/gunshot_smg.ogg' armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 80, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) +/obj/machinery/porta_turret/syndicate/pod/toolbox + max_integrity = 100 + /obj/machinery/porta_turret/syndicate/shuttle/target(atom/movable/target) if(target) setDir(get_dir(base, target))//even if you can't shoot, follow the target diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 34996177af..5a6d5e54e9 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,6 +1,13 @@ /obj/effect/decal/cleanable gender = PLURAL layer = ABOVE_NORMAL_TURF_LAYER + /// Is this kind of cleanable decal persistent + var/persistent = FALSE + /// Can we stack multiple in one tile? + var/persistence_allow_stacking = FALSE + /// Are we deleted by turf changes? + var/wiped_by_floor_change = FALSE + var/list/random_icon_states = null var/blood_state = "" //I'm sorry but cleanable/blood code is ass, and so is blood_DNA var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints @@ -29,9 +36,21 @@ addtimer(CALLBACK(src, /datum.proc/_AddElement, list(/datum/element/beauty, beauty)), 0) +/** + * A data list is passed into this. + * This should return null to skip saving, or the type of data to save. Type must be /cleanable. + */ +/obj/effect/decal/cleanable/proc/PersistenceSave(list/data) + return type + +/** + * Loads from a data list. + */ +/obj/effect/decal/cleanable/proc/PersistenceLoad(list/data) + return + /obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal - if(mergeable_decal) - qdel(C) + return mergeable_decal /obj/effect/decal/cleanable/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/reagent_containers/glass) || istype(W, /obj/item/reagent_containers/food/drinks)) diff --git a/code/game/objects/effects/decals/cleanable/gibs.dm b/code/game/objects/effects/decals/cleanable/gibs.dm index 7df0153ddc..4b63cbecc8 100644 --- a/code/game/objects/effects/decals/cleanable/gibs.dm +++ b/code/game/objects/effects/decals/cleanable/gibs.dm @@ -6,6 +6,7 @@ random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6") mergeable_decal = FALSE bloodiness = 0 //This isn't supposed to be bloody. + persistent = TRUE var/body_colors = "#e3ba84" //a default color just in case. var/gibs_reagent_id = /datum/reagent/liquidgibs var/gibs_bloodtype = "A+" @@ -31,6 +32,10 @@ flesh.color = body_colors add_overlay(flesh) +/obj/effect/decal/cleanable/blood/gibs/PersistenceSave(list/data) + . = ..() + return /obj/effect/decal/cleanable/blood/gibs/old + /obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target) return @@ -159,6 +164,10 @@ . = ..() update_icon() +/obj/effect/decal/cleanable/blood/gibs/slime/PersistenceSave(list/data) + . = ..() + return type + /obj/effect/decal/cleanable/blood/gibs/slime/update_icon() add_atom_colour(body_colors, FIXED_COLOUR_PRIORITY) cut_overlays() @@ -198,6 +207,10 @@ . = ..() update_icon() +/obj/effect/decal/cleanable/blood/gibs/synth/PersistenceSave(list/data) + . = ..() + return type + //IPCs /obj/effect/decal/cleanable/blood/gibs/ipc desc = "They look sharp yet oozing." @@ -209,6 +222,10 @@ . = ..() update_icon() +/obj/effect/decal/cleanable/blood/gibs/ipc/PersistenceSave(list/data) + . = ..() + return type + /obj/effect/decal/cleanable/blood/gibs/ipc/update_icon() add_atom_colour(blood_DNA_to_color(), FIXED_COLOUR_PRIORITY) cut_overlays() diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index fa16a95faf..6d051b6c8c 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -8,12 +8,18 @@ bloodiness = BLOOD_AMOUNT_PER_DECAL color = BLOOD_COLOR_HUMAN //default so we don't have white splotches everywhere. beauty = -100 + persistent = TRUE + var/fixed_color = FALSE /obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) + if(C.fixed_color) + qdel(C) + return FALSE if (C.blood_DNA) blood_DNA |= C.blood_DNA + qdel(C) update_icon() - ..() + return FALSE /obj/effect/decal/cleanable/blood/transfer_blood_dna() ..() @@ -24,7 +30,22 @@ update_icon() /obj/effect/decal/cleanable/blood/update_icon() - color = blood_DNA_to_color() + . = ..() + if(!fixed_color) + add_atom_colour(blood_DNA_to_color(), FIXED_COLOUR_PRIORITY) + +/obj/effect/decal/cleanable/blood/PersistenceSave(list/data) + . = ..() + data["color"] = color + +/obj/effect/decal/cleanable/blood/PersistenceLoad(list/data) + . = ..() + if(data["color"]) + fixed_color = TRUE + add_atom_colour(data["color"], FIXED_COLOUR_PRIORITY) + name = "dried blood" + desc = "Looks like it's been here a while. Eew" + bloodiness = 0 /obj/effect/decal/cleanable/blood/old name = "dried blood" @@ -38,6 +59,7 @@ /obj/effect/decal/cleanable/blood/splats random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") + persistence_allow_stacking = TRUE /obj/effect/decal/cleanable/blood/splatter random_icon_states = list("splatter1", "splatter2", "splatter3", "splatter4", "splatter5") @@ -48,16 +70,43 @@ random_icon_states = null beauty = -50 +/obj/effect/decal/cleanable/blood/tracks/PersistenceSave(list/data) + . = ..() + data["dir"] = dir + +/obj/effect/decal/cleanable/blood/tracks/PersistenceLoad(list/data) + . = ..() + if(data["dir"]) + setDir(text2num(data["dir"])) + /obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose name = "blood" icon_state = "ltrails_1" desc = "Your instincts say you shouldn't be following these." random_icon_states = null beauty = -50 + persistent = TRUE + persistence_allow_stacking = TRUE var/list/existing_dirs = list() + var/fixed_color = FALSE + +/obj/effect/decal/cleanable/trail_holder/PersistenceSave(list/data) + . = ..() + data["dir"] = dir + data["color"] = color + +/obj/effect/decal/cleanable/trail_holder/PersistenceLoad(list/data) + . = ..() + if(data["dir"]) + setDir(text2num(data["dir"])) + if(data["color"]) + fixed_color = TRUE + add_atom_colour(data["color"], FIXED_COLOUR_PRIORITY) /obj/effect/decal/cleanable/trail_holder/update_icon() - color = blood_DNA_to_color() + . = ..() + if(!fixed_color) + add_atom_colour(blood_DNA_to_color(), FIXED_COLOUR_PRIORITY) /obj/effect/cleanable/trail_holder/Initialize() . = ..() @@ -84,8 +133,19 @@ var/entered_dirs = 0 var/exited_dirs = 0 blood_state = BLOOD_STATE_BLOOD //the icon state to load images from + persistent = TRUE + persistence_allow_stacking = TRUE var/list/shoe_types = list() +/obj/effect/decal/cleanable/blood/footprints/PersistenceSave(list/data) + . = ..() + data["dir"] = dir + +/obj/effect/decal/cleanable/blood/footprints/PersistenceLoad(list/data) + . = ..() + if(data["dir"]) + setDir(text2num(data["dir"])) + /obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O) if(ishuman(O)) var/mob/living/carbon/human/H = O @@ -143,7 +203,7 @@ return if(color != C.color) return - ..() + return ..() /obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 044c5c6e86..0f7d7c185d 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -12,6 +12,8 @@ icon_state = "ash" mergeable_decal = FALSE beauty = -50 + persistent = TRUE + persistence_allow_stacking = TRUE /obj/effect/decal/cleanable/ash/Initialize() . = ..() @@ -38,6 +40,8 @@ icon = 'icons/obj/shards.dmi' icon_state = "tiny" beauty = -100 + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/glass/Initialize() . = ..() @@ -53,10 +57,33 @@ name = "dirt" desc = "Someone should clean that up." icon_state = "dirt" + alpha = 127 canSmoothWith = list(/obj/effect/decal/cleanable/dirt, /turf/closed/wall, /obj/structure/falsewall) smooth = SMOOTH_FALSE mouse_opacity = MOUSE_OPACITY_TRANSPARENT beauty = -75 + mergeable_decal = TRUE + persistent = TRUE + wiped_by_floor_change = TRUE + +/obj/effect/decal/cleanable/dirt/Initialize(mapload) + . = ..() + alpha = CONFIG_GET(number/dirt_alpha_starting) + +/obj/effect/decal/cleanable/dirt/proc/dirty(strength = 1) + if(alpha < 255) + alpha += strength + if(alpha > 255) + alpha = 255 + +/obj/effect/decal/cleanable/dirt/PersistenceSave(list/data) + . = ..() + data["alpha"] = alpha + +/obj/effect/decal/cleanable/dirt/PersistenceLoad(list/data) + . = ..() + if(data["alpha"]) + alpha = text2num(data["alpha"]) /obj/effect/decal/cleanable/dirt/Initialize() . = ..() @@ -94,6 +121,8 @@ light_color = LIGHT_COLOR_GREEN icon_state = "greenglow" beauty = -300 + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/greenglow/Initialize(mapload) . = ..() @@ -122,6 +151,8 @@ icon_state = "molten" mergeable_decal = FALSE beauty = -150 + persistent = TRUE + persistence_allow_stacking = TRUE /obj/effect/decal/cleanable/molten_object/large name = "big gooey grey mass" @@ -136,6 +167,7 @@ icon_state = "vomit_1" random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4") beauty = -150 + persistent = TRUE /obj/effect/decal/cleanable/vomit/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) if(ishuman(user)) @@ -151,6 +183,10 @@ reagents.trans_to(H, reagents.total_volume) qdel(src) +/obj/effect/decal/cleanable/vomit/PersistenceSave(list/data) + . = ..() + return /obj/effect/decal/cleanable/vomit/old + /obj/effect/decal/cleanable/vomit/old name = "crusty dried vomit" desc = "You try not to look at the chunks, and fail." @@ -166,12 +202,16 @@ icon = 'icons/effects/tomatodecal.dmi' random_icon_states = list("tomato_floor1", "tomato_floor2", "tomato_floor3") beauty = -100 + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/plant_smudge name = "plant smudge" gender = NEUTER icon = 'icons/effects/tomatodecal.dmi' random_icon_states = list("smashed_plant") + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/egg_smudge name = "smashed egg" @@ -179,6 +219,8 @@ gender = NEUTER icon = 'icons/effects/tomatodecal.dmi' random_icon_states = list("smashed_egg1", "smashed_egg2", "smashed_egg3") + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/pie_smudge //honk name = "smashed pie" @@ -186,6 +228,8 @@ gender = NEUTER icon = 'icons/effects/tomatodecal.dmi' random_icon_states = list("smashed_pie") + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/chem_pile name = "chemical pile" @@ -193,6 +237,8 @@ gender = NEUTER icon = 'icons/obj/objects.dmi' icon_state = "ash" + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/shreds name = "shreds" @@ -200,6 +246,8 @@ icon_state = "shreds" gender = PLURAL mergeable_decal = FALSE + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/shreds/ex_act(severity, target) if(severity == 1) //so shreds created during an explosion aren't deleted by the explosion. @@ -222,6 +270,8 @@ desc = "The herpes of arts and crafts." icon = 'icons/effects/atmospherics.dmi' gender = NEUTER + mergeable_decal = TRUE + persistent = TRUE /obj/effect/decal/cleanable/glitter/pink name = "pink glitter" @@ -247,3 +297,5 @@ icon = 'icons/effects/blood.dmi' icon_state = "xfloor1" random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") + mergeable_decal = TRUE + persistent = TRUE diff --git a/code/game/objects/effects/decals/cleanable/robots.dm b/code/game/objects/effects/decals/cleanable/robots.dm index 2765330977..8f8f27c3c6 100644 --- a/code/game/objects/effects/decals/cleanable/robots.dm +++ b/code/game/objects/effects/decals/cleanable/robots.dm @@ -11,6 +11,7 @@ bloodiness = BLOOD_AMOUNT_PER_DECAL mergeable_decal = FALSE beauty = -50 + persistent = TRUE /obj/effect/decal/cleanable/robot_debris/Initialize(mapload, list/datum/disease/diseases) . = ..() @@ -52,6 +53,7 @@ blood_state = BLOOD_STATE_OIL bloodiness = BLOOD_AMOUNT_PER_DECAL beauty = -100 + persistent = TRUE /obj/effect/decal/cleanable/oil/Initialize() . = ..() @@ -61,8 +63,10 @@ /obj/effect/decal/cleanable/oil/streak random_icon_states = list("streak1", "streak2", "streak3", "streak4", "streak5") beauty = -50 + persistent = TRUE /obj/effect/decal/cleanable/oil/slippery + persistent = FALSE /obj/effect/decal/cleanable/oil/slippery/Initialize() AddComponent(/datum/component/slippery, 80, (NO_SLIP_WHEN_WALKING | SLIDE)) diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm index 8cfdad0432..387b8c167d 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -5,6 +5,8 @@ icon_state = "rune1" plane = ABOVE_WALL_PLANE //makes the graffiti visible over a wall. gender = NEUTER + persistent = TRUE + persistence_allow_stacking = TRUE mergeable_decal = FALSE var/do_icon_rotate = TRUE var/rotation = 0 @@ -29,3 +31,35 @@ if(main) paint_colour = main add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY) + +/obj/effect/decal/cleanable/crayon/PersistenceSave(list/data) + . = ..() + if(icon != initial(icon)) // no support for alticons yet, awful system anyways + return null + data["icon_state"] = icon_state + data["paint_color"] = paint_colour + if(do_icon_rotate) + data["rotation"] = rotation + data["name"] = name + if(pixel_x != initial(pixel_x)) + data["pixel_x"] = pixel_x + if(pixel_y != initial(pixel_y)) + data["pixel_y"] = pixel_y + +/obj/effect/decal/cleanable/crayon/PersistenceLoad(list/data) + . = ..() + if(data["name"]) + name = data["name"] + if(do_icon_rotate && data["rotation"]) + var/matrix/M = matrix() + M.Turn(text2num(data["rotation"])) + transform = M + if(data["paint_color"]) + paint_colour = data["paint_color"] + add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY) + if(data["icon_state"]) + icon_state = data["icon_state"] + if(data["pixel_x"]) + pixel_x = data["pixel_x"] + if(data["pixel_y"]) + pixel_y = data["pixel_y"] diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index eb444cdb12..70edd3f43d 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -264,6 +264,30 @@ effective or pretty fucking useless. GLOB.active_jammers -= src update_icon() +/*portable turret*/ +/obj/item/storage/toolbox/emergency/turret + desc = "You feel a strange urge to hit this with a wrench." + +/obj/item/storage/toolbox/emergency/turret/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/analyzer(src) + new /obj/item/wirecutters(src) + +/obj/item/storage/toolbox/emergency/turret/attackby(obj/item/I, mob/living/user, params) + if(I.tool_behaviour == TOOL_WRENCH && user.a_intent == INTENT_HARM) + user.visible_message("[user] bashes [src] with [I]!", \ + "You bash [src] with [I]!", null, COMBAT_MESSAGE_RANGE) + playsound(src, "sound/items/drill_use.ogg", 80, TRUE, -1) + var/obj/machinery/porta_turret/syndicate/pod/toolbox/turret = new(get_turf(loc)) + turret.faction = list("[REF(user)]") + qdel(src) + return + + ..() + /obj/item/headsetupgrader name = "headset upgrader" desc = "A tool that can be used to upgrade a normal headset to be able to protect from flashbangs." diff --git a/code/game/objects/items/grenades/plastic.dm b/code/game/objects/items/grenades/plastic.dm index 014487332c..69f89d0150 100644 --- a/code/game/objects/items/grenades/plastic.dm +++ b/code/game/objects/items/grenades/plastic.dm @@ -19,6 +19,7 @@ var/boom_sizes = list(0, 0, 3) var/can_attach_mob = FALSE var/full_damage_on_mobs = FALSE + var/can_gib_mobs = FALSE /obj/item/grenade/plastic/Initialize() . = ..() @@ -72,7 +73,7 @@ explosion(get_step(T, aim_dir), boom_sizes[1], boom_sizes[2], boom_sizes[3]) else explosion(location, boom_sizes[1], boom_sizes[2], boom_sizes[3]) - if(ismob(target)) + if(ismob(target) && can_gib_mobs) var/mob/M = target M.gib() qdel(src) diff --git a/code/game/objects/items/miscellaneous.dm b/code/game/objects/items/miscellaneous.dm index 913d73bdad..224c4ffb9b 100644 --- a/code/game/objects/items/miscellaneous.dm +++ b/code/game/objects/items/miscellaneous.dm @@ -1,17 +1,3 @@ -/obj/item/caution - desc = "Caution! Wet Floor!" - name = "wet floor sign" - icon = 'icons/obj/janitor.dmi' - icon_state = "caution" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - force = 1 - throwforce = 3 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("warned", "cautioned", "smashed") - /obj/item/choice_beacon name = "choice beacon" desc = "Hey, why are you viewing this?!! Please let Centcom know about this odd occurance." diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm index a9cd4c33a7..e4d1dd8516 100644 --- a/code/game/objects/items/robot/robot_parts.dm +++ b/code/game/objects/items/robot/robot_parts.dm @@ -392,6 +392,7 @@ created_name = new_name else created_name = "" + log_game("[key_name(usr)] renamed [src] ([REF(src)]) to [created_name].") else if(href_list["Master"]) forced_ai = select_active_ai(usr) diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index c0fd65bd28..9f126f492b 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -793,7 +793,7 @@ GLOBAL_LIST_INIT(plastic_recipes, list( new /datum/stack_recipe("shower curtain", /obj/structure/curtain, 10, time = 10, one_per_turf = 1, on_floor = 1), \ new /datum/stack_recipe("duct", /obj/item/stack/ducts,1), \ new /datum/stack_recipe("laser pointer case", /obj/item/glasswork/glass_base/laserpointer_shell, 30), \ - new /datum/stack_recipe("wet floor sign", /obj/item/caution, 2))) + new /datum/stack_recipe("wet floor sign", /obj/item/clothing/suit/caution, 2))) /obj/item/stack/sheet/plastic name = "plastic" diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 73246276c2..7a067257cb 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -103,6 +103,39 @@ GLOBAL_LIST_EMPTY(rubber_toolbox_icons) /obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() return +/obj/item/storage/toolbox/mechanical/old/clean // the assistant traitor toolbox, damage scales with TC inside + name = "toolbox" + desc = "An old, blue toolbox. It menaces with a sickening miasma of robust energies. You sure about this, Brain?" + icon_state = "toolbox_blue_clean" + force = 19 + throwforce = 22 + wound_bonus = 0 + bare_wound_bonus = 10 + +/obj/item/storage/toolbox/mechanical/old/clean/proc/calc_damage() + var/power = 0 + for (var/obj/item/stack/telecrystal/TC in GetAllContents()) + power += TC.amount + force = 19 + power + throwforce = 22 + power + +/obj/item/storage/toolbox/mechanical/old/clean/attack(mob/target, mob/living/user) + calc_damage() // one damage for one telecrystal equals about thirty seven(?) damage if you pour ALL your tc + ..() + +/obj/item/storage/toolbox/mechanical/old/clean/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + calc_damage() + ..() + +/obj/item/storage/toolbox/mechanical/old/clean/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/clothing/gloves/color/yellow(src) + /obj/item/storage/toolbox/electrical name = "electrical toolbox" icon_state = "yellow" diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm index 21b75e8af3..3af0d0278a 100644 --- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm @@ -70,7 +70,7 @@ new /obj/item/melee/flyswatter(src) new /obj/item/flashlight(src) for(var/i in 1 to 3) - new /obj/item/caution(src) + new /obj/item/clothing/suit/caution(src) new /obj/item/holosign_creator(src) new /obj/item/lightreplacer(src) new /obj/item/soap(src) diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm index 10a9f2afd4..0c99188c49 100644 --- a/code/game/objects/structures/janicart.dm +++ b/code/game/objects/structures/janicart.dm @@ -73,7 +73,7 @@ l.janicart_insert(user,src) else to_chat(user, fail_msg) - else if(istype(I, /obj/item/caution)) + else if(istype(I, /obj/item/clothing/suit/caution)) if(signs < max_signs) put_in_cart(I, user) signs++ @@ -104,7 +104,7 @@ items += list("Spray bottle" = image(icon = myspray.icon, icon_state = myspray.icon_state)) if(myreplacer) items += list("Light replacer" = image(icon = myreplacer.icon, icon_state = myreplacer.icon_state)) - var/obj/item/caution/sign = locate() in src + var/obj/item/clothing/suit/caution/sign = locate() in src if(sign) items += list("Sign" = image(icon = sign.icon, icon_state = sign.icon_state)) diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm index 493e88d442..7e00b1cf63 100644 --- a/code/game/turfs/open.dm +++ b/code/game/turfs/open.dm @@ -1,5 +1,12 @@ /turf/open plane = FLOOR_PLANE + /// Does dirt buildup happen on us? + var/dirt_buildup_allowed = FALSE + /// Dirt level. + var/dirtyness = 0 + /// Dirt level to spawn dirt. Null to use config. + var/dirt_spawn_threshold + /// Slowdown applied to mobs on us. var/slowdown = 0 //negative for faster, positive for slower var/postdig_icon_change = FALSE diff --git a/code/game/turfs/simulated/chasm.dm b/code/game/turfs/simulated/chasm.dm index 5e8f55feed..9490d728fe 100644 --- a/code/game/turfs/simulated/chasm.dm +++ b/code/game/turfs/simulated/chasm.dm @@ -9,6 +9,7 @@ canSmoothWith = list(/turf/open/floor/fakepit, /turf/open/chasm) density = TRUE //This will prevent hostile mobs from pathing into chasms, while the canpass override will still let it function like an open turf bullet_bounce_sound = null //abandon all hope ye who enter + dirt_buildup_allowed = FALSE /turf/open/chasm/Initialize() . = ..() diff --git a/code/game/turfs/simulated/dirtystation.dm b/code/game/turfs/simulated/dirtystation.dm index e29d75a248..add8467ceb 100644 --- a/code/game/turfs/simulated/dirtystation.dm +++ b/code/game/turfs/simulated/dirtystation.dm @@ -8,6 +8,9 @@ //Making the station dirty, one tile at a time. Called by master controller's setup_objects /turf/open/floor/proc/MakeDirty() + if(CONFIG_GET(flag/persistent_debris_only)) + return + if(prob(66)) //fastest possible exit 2/3 of the time return @@ -38,7 +41,7 @@ return //Construction zones. Blood, sweat, and oil. Oh, and dirt. - var/static/list/engine_dirt_areas = typecacheof(list(/area/engine, + var/static/list/engine_dirt_areas = typecacheof(list(/area/engine, /area/crew_quarters/heads/chief, /area/ruin/space/derelict/assembly_line, /area/science/robotics, diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm index ddec9750d9..6a4ddea63b 100644 --- a/code/game/turfs/simulated/floor.dm +++ b/code/game/turfs/simulated/floor.dm @@ -5,6 +5,7 @@ name = "floor" icon = 'icons/turf/floors.dmi' baseturfs = /turf/open/floor/plating + dirt_buildup_allowed = TRUE footstep = FOOTSTEP_FLOOR barefootstep = FOOTSTEP_HARD_BAREFOOT @@ -137,6 +138,9 @@ burnt = 1 /turf/open/floor/proc/make_plating() + for(var/obj/effect/decal/cleanable/C in src) + if(C.wiped_by_floor_change) + qdel(C) return ScrapeAway(flags = CHANGETURF_INHERIT_AIR) /turf/open/floor/ChangeTurf(path, new_baseturf, flags) diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm index 35ee06a584..ad3cf86864 100644 --- a/code/game/turfs/simulated/floor/plating.dm +++ b/code/game/turfs/simulated/floor/plating.dm @@ -61,6 +61,9 @@ to_chat(user, "You begin reinforcing the floor...") if(do_after(user, 30, target = src)) if (R.get_amount() >= 2 && !istype(src, /turf/open/floor/engine)) + for(var/obj/effect/decal/cleanable/decal in src) + if(decal.wiped_by_floor_change) + qdel(decal) PlaceOnTop(/turf/open/floor/engine, flags = CHANGETURF_INHERIT_AIR) playsound(src, 'sound/items/deconstruct.ogg', 80, 1) R.use(2) @@ -78,6 +81,9 @@ to_chat(user, "You begin adding glass to the floor...") if(do_after(user, 5, target = src)) if (G.get_amount() >= 2 && !istype(src, /turf/open/transparent/glass)) + for(var/obj/effect/decal/cleanable/decal in src) + if(decal.wiped_by_floor_change) + qdel(decal) PlaceOnTop(/turf/open/transparent/glass, flags = CHANGETURF_INHERIT_AIR) playsound(src, 'sound/items/deconstruct.ogg', 80, 1) G.use(2) @@ -95,6 +101,9 @@ to_chat(user, "You begin adding reinforced glass to the floor...") if(do_after(user, 10, target = src)) if (RG.get_amount() >= 2 && !istype(src, /turf/open/transparent/glass/reinforced)) + for(var/obj/effect/decal/cleanable/decal in src) + if(decal.wiped_by_floor_change) + qdel(decal) PlaceOnTop(/turf/open/transparent/glass/reinforced, flags = CHANGETURF_INHERIT_AIR) playsound(src, 'sound/items/deconstruct.ogg', 80, 1) RG.use(2) @@ -110,6 +119,9 @@ var/obj/item/stack/tile/W = C if(!W.use(1)) return + for(var/obj/effect/decal/cleanable/decal in src) + if(decal.wiped_by_floor_change) + qdel(decal) if(istype(W, /obj/item/stack/tile/material)) var/turf/newturf = PlaceOnTop(/turf/open/floor/material, flags = CHANGETURF_INHERIT_AIR) newturf.set_custom_materials(W.custom_materials) diff --git a/code/game/turfs/simulated/floor/reinf_floor.dm b/code/game/turfs/simulated/floor/reinf_floor.dm index 42b4707b50..f8c6ff1ce2 100644 --- a/code/game/turfs/simulated/floor/reinf_floor.dm +++ b/code/game/turfs/simulated/floor/reinf_floor.dm @@ -43,6 +43,9 @@ return TRUE if(floor_tile) new floor_tile(src, 2) + for(var/obj/effect/decal/cleanable/C in src) + if(C.wiped_by_floor_change) + qdel(C) ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return TRUE diff --git a/code/game/turfs/simulated/lava.dm b/code/game/turfs/simulated/lava.dm index 2f223f2c40..e7471478fb 100644 --- a/code/game/turfs/simulated/lava.dm +++ b/code/game/turfs/simulated/lava.dm @@ -6,6 +6,7 @@ gender = PLURAL //"That's some lava." baseturfs = /turf/open/lava //lava all the way down slowdown = 2 + dirt_buildup_allowed = FALSE light_range = 2 light_power = 0.75 diff --git a/code/game/turfs/simulated/reebe_void.dm b/code/game/turfs/simulated/reebe_void.dm index 14aa02065d..3f3bf31f9b 100644 --- a/code/game/turfs/simulated/reebe_void.dm +++ b/code/game/turfs/simulated/reebe_void.dm @@ -6,6 +6,7 @@ planetary_atmos = TRUE bullet_bounce_sound = null //forever falling tiled_dirt = FALSE + dirt_buildup_allowed = FALSE /turf/open/indestructible/reebe_void/Initialize(mapload) . = ..() diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index 765c88842e..9c9639e57c 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -3,6 +3,7 @@ icon_state = "0" name = "\proper space" intact = 0 + dirt_buildup_allowed = FALSE temperature = TCMB thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT diff --git a/code/modules/antagonists/blob/blob/blobs/resource.dm b/code/modules/antagonists/blob/blob/blobs/resource.dm index 76aecf717d..2ed9744327 100644 --- a/code/modules/antagonists/blob/blob/blobs/resource.dm +++ b/code/modules/antagonists/blob/blob/blobs/resource.dm @@ -26,7 +26,7 @@ return flick("blob_resource_glow", src) if(overmind) - overmind.add_points(1) + overmind.add_points(2) resource_delay = world.time + 40 + overmind.resource_blobs.len * 2.5 //4 seconds plus a quarter second for each resource blob the overmind has else resource_delay = world.time + 40 diff --git a/code/modules/antagonists/blob/blob/powers.dm b/code/modules/antagonists/blob/blob/powers.dm index dff8e22552..cd53f624a6 100644 --- a/code/modules/antagonists/blob/blob/powers.dm +++ b/code/modules/antagonists/blob/blob/powers.dm @@ -147,7 +147,7 @@ set category = "Blob" set name = "Create Factory Blob (60)" set desc = "Create a spore tower that will spawn spores to harass your enemies." - createSpecial(60, /obj/structure/blob/factory, 7, 1) + createSpecial(60, /obj/structure/blob/factory, 5, 1) /mob/camera/blob/verb/create_blobbernaut() set category = "Blob" @@ -195,6 +195,7 @@ to_chat(blobber, "The [blobstrain.name] reagent [blobstrain.shortdesc ? "[blobstrain.shortdesc]" : "[blobstrain.description]"]") else to_chat(src, "You could not conjure a sentience for your blobbernaut. Your points have been refunded. Try again later.") + B.naut = FALSE add_points(40) /mob/camera/blob/verb/relocate_core() diff --git a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm index 73ae89a19b..28901ea0b2 100644 --- a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm +++ b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm @@ -30,6 +30,7 @@ var/mutable_appearance/alert_overlay = mutable_appearance('icons/effects/clockwork_effects.dmi', "ratvar_alert") notify_ghosts("The Justiciar's light calls to you! Reach out to Ratvar in [get_area_name(src)] to be granted a shell to spread his glory!", null, source = src, alert_overlay = alert_overlay) INVOKE_ASYNC(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 10, null, FALSE, 0) + SSpersistence.station_was_destroyed = TRUE /obj/structure/destructible/clockwork/massive/ratvar/Destroy() GLOB.ratvar_awakens-- diff --git a/code/modules/antagonists/morph/morph.dm b/code/modules/antagonists/morph/morph.dm index e91feda006..5844aad02b 100644 --- a/code/modules/antagonists/morph/morph.dm +++ b/code/modules/antagonists/morph/morph.dm @@ -42,7 +42,9 @@ /obj/screen, /obj/singularity, /mob/living/simple_animal/hostile/morph, - /obj/effect)) + /obj/effect, + /mob/camera + )) var/playstyle_string = "You are a morph, an abomination of science created primarily with changeling cells. \ You may take the form of anything nearby by shift-clicking it. This process will alert any nearby \ diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm index f5a8d21278..5a976b0abb 100644 --- a/code/modules/cargo/console.dm +++ b/code/modules/cargo/console.dm @@ -179,6 +179,10 @@ if((pack.hidden && !(obj_flags & EMAGGED)) || (pack.contraband && !contraband) || pack.DropPodOnly) return + if(self_paid && !pack.can_private_buy) + say("This cannot be bought privately.") + return + var/name = "*None Provided*" var/rank = "*None Provided*" var/ckey = usr.ckey diff --git a/code/modules/cargo/packs/service.dm b/code/modules/cargo/packs/service.dm index 0c7c63f867..2bb3934ac7 100644 --- a/code/modules/cargo/packs/service.dm +++ b/code/modules/cargo/packs/service.dm @@ -261,9 +261,9 @@ /obj/item/reagent_containers/glass/bucket, /obj/item/reagent_containers/glass/bucket, /obj/item/mop, - /obj/item/caution, - /obj/item/caution, - /obj/item/caution, + /obj/item/clothing/suit/caution, + /obj/item/clothing/suit/caution, + /obj/item/clothing/suit/caution, /obj/item/storage/bag/trash, /obj/item/reagent_containers/spray/cleaner, /obj/item/reagent_containers/rag, diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index 149f17f4d6..6dfcd72c10 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -541,6 +541,21 @@ flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT allowed = list(/obj/item/clothing/mask/facehugger/toy) +/obj/item/clothing/suit/caution + name = "wet floor sign" + desc = "Caution! Wet Floor!" + icon_state = "caution" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + force = 1 + throwforce = 3 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + body_parts_covered = CHEST|GROIN + attack_verb = list("warned", "cautioned", "smashed") + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + // WINTER COATS /obj/item/clothing/suit/hooded/wintercoat diff --git a/code/modules/food_and_drinks/food/snacks_bread.dm b/code/modules/food_and_drinks/food/snacks_bread.dm index 0d7c715654..3b133d8675 100644 --- a/code/modules/food_and_drinks/food/snacks_bread.dm +++ b/code/modules/food_and_drinks/food/snacks_bread.dm @@ -7,6 +7,12 @@ foodtype = GRAIN dunkable = TRUE +/obj/item/reagent_containers/food/snacks/store/bread/proc/bread_teleport() + // you did WHAT? + new /mob/living/simple_animal/hostile/bread(get_turf(src)) + visible_message("[src] begins to deform and grow grotesque tumors!") + qdel(src) + /obj/item/reagent_containers/food/snacks/breadslice icon = 'icons/obj/food/burgerbread.dmi' bitesize = 2 @@ -228,3 +234,25 @@ /obj/item/reagent_containers/food/snacks/butterdog/ComponentInitialize() . = ..() AddComponent(/datum/component/slippery, 80) + +/obj/item/reagent_containers/food/snacks/store/bread/tumor_bread + name = "dead tumor bread" + desc = "It's still within its expiration date, right?." + icon_state = "tumorbread" + slice_path = /obj/item/reagent_containers/food/snacks/breadslice/tumor_bread + list_reagents = list(/datum/reagent/consumable/nutriment = 10, /datum/reagent/toxin = 10) + foodtype = GROSS | GRAIN + tastes = list("wheat and tumors" = 10) + +//teleporting tumor bread kills it +/obj/item/reagent_containers/food/snacks/store/bread/tumor_bread/bread_teleport() + visible_message(src, "[src] explodes in a horrible mess of tumor and flour!") + qdel(src) + +/obj/item/reagent_containers/food/snacks/breadslice/tumor_bread + name = "tumor bread slice" + desc = "A slice of bread filled with tumors!" + icon_state = "tumorbreadslice" + filling_color = "#B2D72C" + list_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/toxin = 2) + foodtype = GROSS | GRAIN diff --git a/code/modules/mapping/map_config.dm b/code/modules/mapping/map_config.dm index d18ca1e1e3..1f4b558e21 100644 --- a/code/modules/mapping/map_config.dm +++ b/code/modules/mapping/map_config.dm @@ -18,6 +18,8 @@ var/map_name = "Box Station" var/map_path = "map_files/BoxStation" var/map_file = "BoxStation.dmm" + /// Persistence key: Defaults to ckey(map_name). If set to "NO_PERSIST", this map will have NO persistence. + var/persistence_key var/traits = null var/space_ruin_levels = 4 @@ -99,6 +101,16 @@ map_path = json["map_path"] map_file = json["map_file"] + + persistence_key = ckey(map_name) + + var/json_persistence_key = json["persistence_key"] + if(json_persistence_key) + if(json_persistence_key == "NO_PERSIST") + persistence_key = null + else + persistence_key = json_persistence_key + // "map_file": "BoxStation.dmm" if (istext(map_file)) if (!fexists("_maps/[map_path]/[map_file]")) diff --git a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm index fb71bb483d..37ef2a7d19 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm @@ -138,6 +138,18 @@ name = "White Fly" icon_state = "whitefly" +/datum/sprite_accessory/deco_wings/oakworm + name = "Oak Worm" + icon_state = "oakworm" + +/datum/sprite_accessory/deco_wings/witchwing + name = "Witch Wing" + icon_state = "witchwing" + +/datum/sprite_accessory/deco_wings/jungle + name = "Jungle" + icon_state = "jungle" + //INSECT WINGS /datum/sprite_accessory/insect_wings diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 341aa6a30f..a82151cc1d 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -15,6 +15,7 @@ block_parry_data = /datum/block_parry_data/unarmed/human default_block_parry_data = /datum/block_parry_data/unarmed/human + causes_dirt_buildup_on_floor = TRUE //Hair colour and style var/hair_color = "000" diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 509633c0d5..440400f889 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -100,3 +100,8 @@ if(dna.species.space_move(src)) return TRUE return ..() + +/mob/living/carbon/human/dirt_buildup(strength) + if(!shoes || !(shoes.body_parts_covered & FEET)) + return // barefoot advantage + return ..() diff --git a/code/modules/mob/living/carbon/human/species_types/xeno.dm b/code/modules/mob/living/carbon/human/species_types/xeno.dm index 2757c624e7..f88ccc876f 100644 --- a/code/modules/mob/living/carbon/human/species_types/xeno.dm +++ b/code/modules/mob/living/carbon/human/species_types/xeno.dm @@ -5,7 +5,7 @@ say_mod = "hisses" default_color = "00FF00" species_traits = list(MUTCOLORS,EYECOLOR,LIPS,CAN_SCAR) - mutant_bodyparts = list("xenotail"="Xenomorph Tail","xenohead"="Standard","xenodorsal"="Standard", "mam_body_markings" = "Xeno","mcolor" = "0F0","mcolor2" = "0F0","mcolor3" = "0F0","taur" = "None", "legs" = "Digitigrade") + mutant_bodyparts = list("xenotail"="Xenomorph Tail","xenohead"="Standard","xenodorsal"="Standard", "mam_body_markings" = "Xeno","mcolor" = "0F0","mcolor2" = "0F0","mcolor3" = "0F0","taur" = "None", "legs" = "Digitigrade","deco_wings"= "None") attack_verb = "slash" attack_sound = 'sound/weapons/slash.ogg' miss_sound = 'sound/weapons/slashmiss.ogg' diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index d4ccc63b34..742d4d8624 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -63,6 +63,8 @@ //Allows mobs to move through dense areas without restriction. For instance, in space or out of holder objects. var/incorporeal_move = FALSE //FALSE is off, INCORPOREAL_MOVE_BASIC is normal, INCORPOREAL_MOVE_SHADOW is for ninjas //and INCORPOREAL_MOVE_JAUNT is blocked by holy water/salt + /// Do we make floors dirty as we move? + var/causes_dirt_buildup_on_floor = FALSE var/list/roundstart_quirks = list() diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index 0cdfe9dbf2..9e093d391e 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -101,7 +101,30 @@ if(lying && !buckled && prob(getBruteLoss()*200/maxHealth)) makeTrail(newloc, T, old_direction) + if(causes_dirt_buildup_on_floor && (movement_type & GROUND)) + dirt_buildup() +/** + * Attempts to make the floor dirty. + */ +/mob/living/proc/dirt_buildup(strength = 1) + var/turf/open/T = loc + if(!istype(T) || !T.dirt_buildup_allowed) + return + var/area/A = T.loc + if(!A.dirt_buildup_allowed) + return + var/multiplier = CONFIG_GET(number/turf_dirty_multiplier) + strength *= multiplier + var/obj/effect/decal/cleanable/dirt/D = locate() in T + if(D) + D.dirty(strength) + else + T.dirtyness += strength + if(T.dirtyness >= (isnull(T.dirt_spawn_threshold)? CONFIG_GET(number/turf_dirt_threshold) : T.dirt_spawn_threshold)) + D = new /obj/effect/decal/cleanable/dirt(T) + D.dirty(T.dirt_spawn_threshold - T.dirtyness) + T.dirtyness = 0 // reset. /mob/living/Move_Pulled(atom/A) . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/bread.dm b/code/modules/mob/living/simple_animal/hostile/bread.dm new file mode 100644 index 0000000000..317827028d --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/bread.dm @@ -0,0 +1,69 @@ +//funny reference to the video 'Expiration Date' + +/mob/living/simple_animal/hostile/bread + name = "tumor bread" + desc = "I have done nothing but teleport bread for three days." + icon_state = "tumorbread" + health = 1 + maxHealth = 1 + turns_per_move = 5 //this isn't player speed =| + speed = 2 //this is player speed + melee_damage_lower = 1 + melee_damage_upper = 2 + obj_damage = 0 + loot = list(/obj/item/reagent_containers/food/snacks/store/bread/tumor_bread) + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 270 + maxbodytemp = INFINITY + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + mob_size = MOB_SIZE_TINY + response_help_continuous = "pokes" + response_help_simple = "poke" + response_disarm_continuous = "shoos" + response_disarm_simple = "shoo" + response_harm_continuous = "punches" + response_harm_simple = "punch" + speak_emote = list("growls") + mouse_opacity = 2 + density = TRUE + ventcrawler = VENTCRAWLER_ALWAYS + verb_say = "growls" + verb_ask = "growls inquisitively" + verb_exclaim = "growls loudly" + verb_yell = "growls loudly" + del_on_death = TRUE + +/mob/living/simple_animal/bread/hostile/Initialize() + . = ..() + var/area/A = get_area(src) + if(A) + notify_ghosts("A tumor bread has been created in \the [A.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_dnr_observers = TRUE) + +/mob/living/simple_animal/hostile/bread/attack_ghost(mob/user) + if(key) //please stop using src. without a good reason. + return + if(CONFIG_GET(flag/use_age_restriction_for_jobs)) + if(!isnum(user.client.player_age)) + return + if(isobserver(user)) + var/mob/dead/observer/O = user + if(!O.can_reenter_round()) + return + if(!SSticker.mode) + to_chat(user, "Can't become a tumor bread before the game has started.") + return + var/be_bread = alert("Become a tumor bread? (Warning, You can no longer be cloned!)",,"Yes","No") + if(be_bread == "No" || QDELETED(src) || !isobserver(user)) + return + if(key) + to_chat(user, "Someone else already took this tumor bread.") + return + sentience_act() + user.transfer_ckey(src, FALSE) + density = TRUE + +/mob/living/simple_animal/hostile/bread/ex_act() + return + +/mob/living/simple_animal/hostile/bread/start_pulling() + return FALSE diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 5fa6a0c22d..e9b97b3597 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -354,7 +354,7 @@ /mob/living/simple_animal/proc/drop_loot() if(loot.len) for(var/i in loot) - new i(loc) + new i(loc, dir = src.dir) /mob/living/simple_animal/death(gibbed) movement_type &= ~FLYING diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 13890ae69e..8863a098fc 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -182,8 +182,9 @@ if(..()) if(reagents.total_volume) if(M.reagents) + log_combat(user, M, "injected with sleepypen", src, reagents.log_list()) reagents.reaction(M, INJECT) - reagents.trans_to(M, reagents.total_volume) + reagents.trans_to(M, reagents.total_volume, log = "sleepypen inject") /obj/item/pen/sleepy/Initialize() diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm index 8bf20fddc6..5b5292a020 100644 --- a/code/modules/power/singularity/narsie.dm +++ b/code/modules/power/singularity/narsie.dm @@ -78,6 +78,7 @@ set_security_level("delta") SSshuttle.registerHostileEnvironment(src) SSshuttle.lockdown = TRUE + SSpersistence.station_was_destroyed = TRUE sleep(600) if(resolved == FALSE) resolved = TRUE diff --git a/code/modules/uplink/uplink_items/uplink_roles.dm b/code/modules/uplink/uplink_items/uplink_roles.dm index b384106396..4edbe2f2c7 100644 --- a/code/modules/uplink/uplink_items/uplink_roles.dm +++ b/code/modules/uplink/uplink_items/uplink_roles.dm @@ -142,6 +142,14 @@ cost = 4 // Has syndie tools + gloves + a robust weapon restricted_roles = list("Assistant", "Curator") //Curator due to this being made of gold - It fits the theme +/datum/uplink_item/role_restricted/oldtoolboxclean // this is the fourth item relating to toolboxes to be placed into this godforsaken bloated uplink + name = "Ancient Toolbox" + desc = "An iconic toolbox design notorious with Assistants everywhere, this design was especially made to become more robust the more telecrystals it has inside it! Tools and insulated gloves included." + item = /obj/item/storage/toolbox/mechanical/old/clean + cost = 2 // with eighteen telecrystals you are still weaker than a desword and without any of its defenses -- the scary part comes from collaborating with allies for a fifty eight force toolbox oh fuck + restricted_roles = list("Assistant") + surplus = 0 + /datum/uplink_item/role_restricted/mimery name = "Guide to Advanced Mimery Series" desc = "The classical two part series on how to further hone your mime skills. Upon studying the series, the user should be able to make 3x1 invisible walls, and shoot bullets out of their fingers. \ @@ -242,3 +250,11 @@ cost = 2 item = /obj/item/clothing/shoes/magboots/crushing restricted_roles = list("Chief Engineer", "Station Engineer", "Atmospheric Technician") + +/datum/uplink_item/role_restricted/turretbox + name = "Disposable Sentry Gun" + desc = "A disposable sentry gun deployment system cleverly disguised as a toolbox, apply wrench for functionality." + item = /obj/item/storage/toolbox/emergency/turret + cost = 11 + restricted_roles = list("Station Engineer") + diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index d10f06b257..0b47c7218f 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -375,7 +375,7 @@ /obj/item/paint/paint_remover = 2, /obj/item/melee/flyswatter = 2, /obj/item/flashlight = 2, - /obj/item/caution = 8, + /obj/item/clothing/suit/caution = 8, /obj/item/holosign_creator = 2, /obj/item/lightreplacer = 2, /obj/item/soap = 2, diff --git a/config/config.txt b/config/config.txt index 72df74beb1..c7bb36af0e 100644 --- a/config/config.txt +++ b/config/config.txt @@ -11,6 +11,7 @@ $include dynamic_config.txt $include plushies/defines.txt $include job_threats.txt $include policy.txt +$include persistence.txt $include respawns.txt # You can use the @ character at the beginning of a config option to lock it from being edited in-game diff --git a/config/game_options.txt b/config/game_options.txt index bc5fdf940e..3c53d9fecb 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -668,4 +668,10 @@ ALLOW_CUSTOM_SKINTONES ## Enables the FoV component, which hides objects and mobs behind the parent from their sight, unless they turn around, duh. ## Camera mobs, AIs, ghosts and some other are of course exempt from this. This also doesn't influence simplemob AI, for the best. -#USE_FIELD_OF_VISION \ No newline at end of file +#USE_FIELD_OF_VISION + +## Default turf threshold to get dirt +TURF_DIRT_THRESHOLD 100 + +## Default alpha of dirt on spawn +DIRT_ALPHA_STARTING 127 diff --git a/config/persistence.txt b/config/persistence.txt new file mode 100644 index 0000000000..7d65a150e2 --- /dev/null +++ b/config/persistence.txt @@ -0,0 +1,14 @@ +### Whether or not cleanable object persistence is on +PERSISTENT_DEBRIS + +### Whether or not ONLY persistent cleanable objects should be spawned, for supported objects +PERSISTENT_DEBRIS_ONLY + +### Max objects to store, total +PERSISTENT_DEBRIS_GLOBAL_MAX 10000 + +### Max objects to store per type, total +PERSISTENT_DEBRIS_TYPE_MAX 2000 + +### Wipe objects on nuke for the above +PERSISTENT_DEBRIS_WIPE_ON_NUKE diff --git a/html/changelogs/AutoChangeLog-pr-13834.yml b/html/changelogs/AutoChangeLog-pr-13834.yml new file mode 100644 index 0000000000..46cedff5d5 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13834.yml @@ -0,0 +1,4 @@ +author: "zeroisthebiggay" +delete-after: True +changes: + - rscadd: "Traitor assistants can now purchase the patented POGBox! Put TC into it for even higher damage!" diff --git a/html/changelogs/AutoChangeLog-pr-13871.yml b/html/changelogs/AutoChangeLog-pr-13871.yml new file mode 100644 index 0000000000..f667abe816 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13871.yml @@ -0,0 +1,5 @@ +author: "NT Cleaning Crews On Break" +delete-after: True +changes: + - rscadd: "Most kinds of dirt, grime, and debris are now persistent. Get to work, jannies." + - rscadd: "Dirt can now be removed by tile replacements. Other cleanable decals can't, though." diff --git a/html/changelogs/AutoChangeLog-pr-13931.yml b/html/changelogs/AutoChangeLog-pr-13931.yml new file mode 100644 index 0000000000..fd903f2d6e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13931.yml @@ -0,0 +1,4 @@ +author: "shellspeed1" +delete-after: True +changes: + - rscadd: "Adds the disposable sentry gun from tg for 11tc each." diff --git a/html/changelogs/AutoChangeLog-pr-13967.yml b/html/changelogs/AutoChangeLog-pr-13967.yml new file mode 100644 index 0000000000..d2e580b531 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13967.yml @@ -0,0 +1,6 @@ +author: "keronshb" +delete-after: True +changes: + - balance: "Blob Resource Tower to 2 points per instead of 1 point per." + - balance: "Blob Factory Towers can be placed 5 tiles apart instead of 7." + - bugfix: "Fixes Blobbernaut Factories consuming Factories if no naut is chosen." diff --git a/html/changelogs/AutoChangeLog-pr-13976.yml b/html/changelogs/AutoChangeLog-pr-13976.yml new file mode 100644 index 0000000000..dac85f8e9a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13976.yml @@ -0,0 +1,4 @@ +author: "timothyteakettle" +delete-after: True +changes: + - rscadd: "you can teleport bread" diff --git a/html/changelogs/AutoChangeLog-pr-13978.yml b/html/changelogs/AutoChangeLog-pr-13978.yml new file mode 100644 index 0000000000..94c19edec4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13978.yml @@ -0,0 +1,4 @@ +author: "silicons" +delete-after: True +changes: + - balance: "c4 can no longer gib mobs" diff --git a/html/changelogs/AutoChangeLog-pr-13980.yml b/html/changelogs/AutoChangeLog-pr-13980.yml new file mode 100644 index 0000000000..6838ba48a2 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13980.yml @@ -0,0 +1,4 @@ +author: "MrJWhit" +delete-after: True +changes: + - rscadd: "Makes gas sensors fireproof." diff --git a/html/changelogs/AutoChangeLog-pr-13983.yml b/html/changelogs/AutoChangeLog-pr-13983.yml new file mode 100644 index 0000000000..ba0bf62e6a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13983.yml @@ -0,0 +1,6 @@ +author: "shellspeed1" +delete-after: True +changes: + - rscadd: "Xenomorph hybrids can now select wings +~~add: Xenomorph hybrids can now speak xenomorph~~" + - rscadd: "Xenomorph tongues are available for customization." diff --git a/html/changelogs/AutoChangeLog-pr-13988.yml b/html/changelogs/AutoChangeLog-pr-13988.yml new file mode 100644 index 0000000000..262e5f804f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13988.yml @@ -0,0 +1,4 @@ +author: "DeltaFire15" +delete-after: True +changes: + - bugfix: "Cargo packs marked as 'no private buying' now actually register as such." diff --git a/html/changelogs/AutoChangeLog-pr-13996.yml b/html/changelogs/AutoChangeLog-pr-13996.yml new file mode 100644 index 0000000000..5bea075460 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13996.yml @@ -0,0 +1,4 @@ +author: "MrJWhit" +delete-after: True +changes: + - tweak: "Bugfix of a morph becoming an AI eye" diff --git a/html/changelogs/AutoChangeLog-pr-13997.yml b/html/changelogs/AutoChangeLog-pr-13997.yml new file mode 100644 index 0000000000..354f66010a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-13997.yml @@ -0,0 +1,4 @@ +author: "MrJWhit" +delete-after: True +changes: + - tweak: "Porting garbage collection tweak from /tg/" diff --git a/html/changelogs/AutoChangeLog-pr-14001.yml b/html/changelogs/AutoChangeLog-pr-14001.yml new file mode 100644 index 0000000000..31c9e560ab --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-14001.yml @@ -0,0 +1,4 @@ +author: "MrJWhit" +delete-after: True +changes: + - tweak: "Fixes two chairs on one table" diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi index 8db32b304b..68e6170969 100644 Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi index d4bb1224cc..21d2f872f1 100644 Binary files a/icons/mob/clothing/suit.dmi and b/icons/mob/clothing/suit.dmi differ diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi index c12720bc8b..935e9ead02 100644 Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ diff --git a/icons/obj/food/burgerbread.dmi b/icons/obj/food/burgerbread.dmi index 7ef3a7d418..c5bf88ab6e 100644 Binary files a/icons/obj/food/burgerbread.dmi and b/icons/obj/food/burgerbread.dmi differ diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi index 1f1709a10c..b0e542f618 100644 Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 71abfdea8e..3d2c19866a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -292,6 +292,7 @@ #include "code\controllers\configuration\entries\fail2topic.dm" #include "code\controllers\configuration\entries\game_options.dm" #include "code\controllers\configuration\entries\general.dm" +#include "code\controllers\configuration\entries\persistence.dm" #include "code\controllers\configuration\entries\plushies.dm" #include "code\controllers\configuration\entries\policy.dm" #include "code\controllers\configuration\entries\resources.dm" @@ -340,7 +341,6 @@ #include "code\controllers\subsystem\pai.dm" #include "code\controllers\subsystem\parallax.dm" #include "code\controllers\subsystem\pathfinder.dm" -#include "code\controllers\subsystem\persistence.dm" #include "code\controllers\subsystem\profiler.dm" #include "code\controllers\subsystem\radiation.dm" #include "code\controllers\subsystem\radio.dm" @@ -363,6 +363,13 @@ #include "code\controllers\subsystem\vis_overlays.dm" #include "code\controllers\subsystem\vore.dm" #include "code\controllers\subsystem\vote.dm" +#include "code\controllers\subsystem\persistence\_persistence.dm" +#include "code\controllers\subsystem\persistence\cleanable_debris.dm" +#include "code\controllers\subsystem\persistence\panic_bunker.dm" +#include "code\controllers\subsystem\persistence\poly_parrot.dm" +#include "code\controllers\subsystem\persistence\recent_votes_etc.dm" +#include "code\controllers\subsystem\persistence\secret_satchels.dm" +#include "code\controllers\subsystem\persistence\trophies.dm" #include "code\controllers\subsystem\processing\chemistry.dm" #include "code\controllers\subsystem\processing\circuit.dm" #include "code\controllers\subsystem\processing\fastprocess.dm" @@ -2716,6 +2723,7 @@ #include "code\modules\mob\living\simple_animal\hostile\banana_spider.dm" #include "code\modules\mob\living\simple_animal\hostile\bear.dm" #include "code\modules\mob\living\simple_animal\hostile\bees.dm" +#include "code\modules\mob\living\simple_animal\hostile\bread.dm" #include "code\modules\mob\living\simple_animal\hostile\carp.dm" #include "code\modules\mob\living\simple_animal\hostile\cat_butcher.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json index f1ab17a264..f72ac63d1f 100644 --- a/tgui/packages/tgui-dev-server/package.json +++ b/tgui/packages/tgui-dev-server/package.json @@ -3,7 +3,7 @@ "name": "tgui-dev-server", "version": "4.2.0", "dependencies": { - "axios": "^0.19.2", + "axios": "^0.21.1", "common": "workspace:*", "esm": "^3.2.25", "glob": "^7.1.4", diff --git a/tgui/packages/tgui/interfaces/Photocopier.js b/tgui/packages/tgui/interfaces/Photocopier.js index fc79571e8c..0e461c403e 100644 --- a/tgui/packages/tgui/interfaces/Photocopier.js +++ b/tgui/packages/tgui/interfaces/Photocopier.js @@ -57,7 +57,6 @@ const Toner = (props, context) => { title="Toner" buttons={