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 ffbbf7cfc3..0a44089a23 100644 --- a/_maps/map_files/BoxStation/BoxStation.dmm +++ b/_maps/map_files/BoxStation/BoxStation.dmm @@ -45275,7 +45275,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 dcad2582de..af5c23905e 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 3e6ae73236..51dffff68c 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 efc3ee8b15..b3e8fd0861 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -568,3 +568,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 7dade1fed7..3648e118ec 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -87,6 +87,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 994e269f16..006186d88c 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 be1aef3f81..e706ddbeee 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -795,7 +795,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/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 4e1e8905d9..fd7e7b7b4e 100644 --- a/code/game/turfs/simulated/floor.dm +++ b/code/game/turfs/simulated/floor.dm @@ -6,6 +6,7 @@ icon = 'icons/turf/floors.dmi' var/base_icon_state = "floor" //sandstorm change - tile floofing baseturfs = /turf/open/floor/plating + dirt_buildup_allowed = TRUE footstep = FOOTSTEP_FLOOR barefootstep = FOOTSTEP_HARD_BAREFOOT @@ -118,6 +119,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 7ab056a4cf..5c0f969f69 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) @@ -119,6 +128,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 bdf1cf7c97..0c8e6bbe55 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm @@ -155,6 +155,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 3ab2ae710f..913de5f1eb 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 ecf1a2f7a1..e9b97b3597 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -352,10 +352,9 @@ . += "Health: [round((health / maxHealth) * 100)]%" /mob/living/simple_animal/proc/drop_loot() - if(!length(loot)) - return - for(var/i in loot) - new i(loc) + if(loot.len) + for(var/i in loot) + new i(loc, dir = src.dir) /mob/living/simple_animal/death(gibbed) movement_type &= ~FLYING 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 f7a2117df3..4edbe2f2c7 100644 --- a/code/modules/uplink/uplink_items/uplink_roles.dm +++ b/code/modules/uplink/uplink_items/uplink_roles.dm @@ -250,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 635e42359f..9f154919c1 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 # Special Sandstorm configs $include sandstorm/config.txt 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-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/tgstation.dme b/tgstation.dme index 733a3a8bb2..4277bc7ac7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -296,6 +296,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" @@ -344,7 +345,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" @@ -367,6 +367,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" @@ -2717,6 +2724,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={