diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index 468b806784..cc461902ec 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -1,438 +1,226 @@
-//wrapper
-/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
- var/datum/teleport/instant/science/D = new
- if(D.start(arglist(args)))
- return 1
- return 0
+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()
-/datum/teleport
- var/atom/movable/teleatom //atom to teleport
- var/atom/destination //destination to teleport to
- var/precision = 0 //teleport precision
- var/datum/effect_system/effectin //effect to show right before teleportation
- var/datum/effect_system/effectout //effect to show right after teleportation
- var/soundin //soundfile to play before teleportation
- var/soundout //soundfile to play after teleportation
- var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation)
+ var/list/obj/structure/chisel_message/chisel_messages = list()
+ var/list/saved_messages = list()
-/datum/teleport/proc/start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
- if(!initTeleport(arglist(args)))
- return 0
- return 1
+ var/list/saved_trophies = list()
-/datum/teleport/proc/initTeleport(ateleatom,adestination,aprecision,afteleport,aeffectin,aeffectout,asoundin,asoundout)
- if(!setTeleatom(ateleatom))
- return 0
- if(!setDestination(adestination))
- return 0
- if(!setPrecision(aprecision))
- return 0
- setEffects(aeffectin,aeffectout)
- setForceTeleport(afteleport)
- setSounds(asoundin,asoundout)
- return 1
-
-//must succeed
-/datum/teleport/proc/setPrecision(aprecision)
- if(isnum(aprecision))
- precision = aprecision
- return 1
- return 0
-
-//must succeed
-/datum/teleport/proc/setDestination(atom/adestination)
- if(istype(adestination))
- destination = adestination
- return 1
- return 0
-
-//must succeed in most cases
-/datum/teleport/proc/setTeleatom(atom/movable/ateleatom)
- if(istype(ateleatom, /obj/effect) && !istype(ateleatom, /obj/effect/dummy/chameleon))
- qdel(ateleatom)
- return 0
- if(istype(ateleatom))
- teleatom = ateleatom
- return 1
- return 0
-
-//custom effects must be properly set up first for instant-type teleports
-//optional
-/datum/teleport/proc/setEffects(datum/effect_system/aeffectin=null,datum/effect_system/aeffectout=null)
- effectin = istype(aeffectin) ? aeffectin : null
- effectout = istype(aeffectout) ? aeffectout : null
- return 1
-
-//optional
-/datum/teleport/proc/setForceTeleport(afteleport)
- force_teleport = afteleport
- return 1
-
-//optional
-/datum/teleport/proc/setSounds(asoundin=null,asoundout=null)
- soundin = isfile(asoundin) ? asoundin : null
- soundout = isfile(asoundout) ? asoundout : null
- return 1
-
-//placeholder
-/datum/teleport/proc/teleportChecks()
- return 1
-
-/datum/teleport/proc/playSpecials(atom/location,datum/effect_system/effect,sound)
- if(location)
- if(effect)
- INVOKE_ASYNC(src, .proc/do_effect, location, effect)
- if(sound)
- INVOKE_ASYNC(src, .proc/do_sound, location, sound)
-
-/datum/teleport/proc/do_effect(atom/location, datum/effect_system/effect)
- src = null
- effect.attach(location)
- effect.start()
-
-/datum/teleport/proc/do_sound(atom/location, sound)
- src = null
- playsound(location, sound, 60, 1)
-
-//do the monkey dance
-/datum/teleport/proc/doTeleport()
-
- var/turf/destturf
- var/turf/curturf = get_turf(teleatom)
- destturf = get_teleport_turf(get_turf(destination), precision)
-
- if(!destturf || !curturf || destturf.is_transition_turf())
- return 0
-
- var/area/A = get_area(curturf)
- if(A.noteleport)
- return 0
-
- playSpecials(curturf,effectin,soundin)
- if(force_teleport)
- teleatom.forceMove(destturf)
- if(ismegafauna(teleatom))
- message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_COORDJMP(curturf)] to [ADMIN_COORDJMP(destturf)].")
- playSpecials(destturf,effectout,soundout)
- else
- if(teleatom.Move(destturf))
- playSpecials(destturf,effectout,soundout)
- if(ismegafauna(teleatom))
- message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_COORDJMP(curturf)] to [ADMIN_COORDJMP(destturf)].")
- return 1
-
-/datum/teleport/proc/teleport()
- if(teleportChecks())
- return doTeleport()
- return 0
-
-/datum/teleport/instant //teleports when datum is created
-
- start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
- if(..())
- if(teleport())
- return 1
- return 0
-
-
-/datum/teleport/instant/science
-
-/datum/teleport/instant/science/setEffects(datum/effect_system/aeffectin,datum/effect_system/aeffectout)
- if(aeffectin==null || aeffectout==null)
- var/datum/effect_system/spark_spread/aeffect = new
- aeffect.set_up(5, 1, teleatom)
- effectin = effectin || aeffect
- effectout = effectout || aeffect
- return 1
- else
- return ..()
-
-/datum/teleport/instant/science/setPrecision(aprecision)
+/datum/controller/subsystem/persistence/Initialize()
+ LoadSatchels()
+ LoadPoly()
+ LoadChiselMessages()
+ LoadTrophies()
..()
- if(istype(teleatom, /obj/item/storage/backpack/holding))
- precision = rand(1,100)
- var/list/bagholding = teleatom.search_contents_for(/obj/item/storage/backpack/holding)
- if(bagholding.len)
- precision = max(rand(1,100)*bagholding.len,100)
- if(isliving(teleatom))
- var/mob/living/MM = teleatom
- to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!")
- return 1
-
-// Safe location finder
-
-/proc/find_safe_turf(zlevel = ZLEVEL_STATION, list/zlevels, extended_safety_checks = FALSE)
- if(!zlevels)
- zlevels = list(zlevel)
- var/cycles = 1000
- for(var/cycle in 1 to cycles)
- // DRUNK DIALLING WOOOOOOOOO
- var/x = rand(1, world.maxx)
- var/y = rand(1, world.maxy)
- var/z = pick(zlevels)
- var/random_location = locate(x,y,z)
-
- if(!isfloorturf(random_location))
- continue
- var/turf/open/floor/F = random_location
- if(!F.air)
- continue
-
- var/datum/gas_mixture/A = F.air
- var/list/A_gases = A.gases
- var/trace_gases
- for(var/id in A_gases)
- if(id in GLOB.hardcoded_gases)
- continue
- trace_gases = TRUE
- break
-
- // Can most things breathe?
- if(trace_gases)
- continue
- if(!(A_gases["o2"] && A_gases["o2"][MOLES] >= 16))
- continue
- if(A_gases["plasma"])
- continue
- if(A_gases["co2"] && A_gases["co2"][MOLES] >= 10)
- continue
-
- // Aim for goldilocks temperatures and pressure
- if((A.temperature <= 270) || (A.temperature >= 360))
- continue
- var/pressure = A.return_pressure()
- if((pressure <= 20) || (pressure >= 550))
- continue
-
- if(extended_safety_checks)
-//wrapper
-/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
- var/datum/teleport/instant/science/D = new
- if(D.start(arglist(args)))
- return 1
- return 0
-
-/datum/teleport
- var/atom/movable/teleatom //atom to teleport
- var/atom/destination //destination to teleport to
- var/precision = 0 //teleport precision
- var/datum/effect_system/effectin //effect to show right before teleportation
- var/datum/effect_system/effectout //effect to show right after teleportation
- var/soundin //soundfile to play before teleportation
- var/soundout //soundfile to play after teleportation
- var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation)
-
-/datum/teleport/proc/start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
- if(!initTeleport(arglist(args)))
- return 0
- return 1
-
-/datum/teleport/proc/initTeleport(ateleatom,adestination,aprecision,afteleport,aeffectin,aeffectout,asoundin,asoundout)
- if(!setTeleatom(ateleatom))
- return 0
- if(!setDestination(adestination))
- return 0
- if(!setPrecision(aprecision))
- return 0
- setEffects(aeffectin,aeffectout)
- setForceTeleport(afteleport)
- setSounds(asoundin,asoundout)
- return 1
-
-//must succeed
-/datum/teleport/proc/setPrecision(aprecision)
- if(isnum(aprecision))
- precision = aprecision
- return 1
- return 0
-
-//must succeed
-/datum/teleport/proc/setDestination(atom/adestination)
- if(istype(adestination))
- destination = adestination
- return 1
- return 0
-
-//must succeed in most cases
-/datum/teleport/proc/setTeleatom(atom/movable/ateleatom)
- if(istype(ateleatom, /obj/effect) && !istype(ateleatom, /obj/effect/dummy/chameleon))
- qdel(ateleatom)
- return 0
- if(istype(ateleatom))
- teleatom = ateleatom
- return 1
- return 0
-
-//custom effects must be properly set up first for instant-type teleports
-//optional
-/datum/teleport/proc/setEffects(datum/effect_system/aeffectin=null,datum/effect_system/aeffectout=null)
- effectin = istype(aeffectin) ? aeffectin : null
- effectout = istype(aeffectout) ? aeffectout : null
- return 1
-
-//optional
-/datum/teleport/proc/setForceTeleport(afteleport)
- force_teleport = afteleport
- return 1
-
-//optional
-/datum/teleport/proc/setSounds(asoundin=null,asoundout=null)
- soundin = isfile(asoundin) ? asoundin : null
- soundout = isfile(asoundout) ? asoundout : null
- return 1
-
-//placeholder
-/datum/teleport/proc/teleportChecks()
- return 1
-
-/datum/teleport/proc/playSpecials(atom/location,datum/effect_system/effect,sound)
- if(location)
- if(effect)
- INVOKE_ASYNC(src, .proc/do_effect, location, effect)
- if(sound)
- INVOKE_ASYNC(src, .proc/do_sound, location, sound)
-
-/datum/teleport/proc/do_effect(atom/location, datum/effect_system/effect)
- src = null
- effect.attach(location)
- effect.start()
-
-/datum/teleport/proc/do_sound(atom/location, sound)
- src = null
- playsound(location, sound, 60, 1)
-
-//do the monkey dance
-/datum/teleport/proc/doTeleport()
-
- var/turf/destturf
- var/turf/curturf = get_turf(teleatom)
- destturf = get_teleport_turf(get_turf(destination), precision)
-
- if(!destturf || !curturf || destturf.is_transition_turf())
- return 0
-
- var/area/A = get_area(curturf)
- if(A.noteleport)
- return 0
-
- playSpecials(curturf,effectin,soundin)
- if(force_teleport)
- teleatom.forceMove(destturf)
- if(ismegafauna(teleatom))
- message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_COORDJMP(curturf)] to [ADMIN_COORDJMP(destturf)].")
- playSpecials(destturf,effectout,soundout)
+/datum/controller/subsystem/persistence/proc/LoadSatchels()
+ var/placed_satchel = 0
+ var/path
+ var/obj/item/storage/backpack/satchel/flat/F = new()
+ if(fexists("data/npc_saves/SecretSatchels.sav")) //legacy compatability to convert old format to new
+ var/savefile/secret_satchels = new /savefile("data/npc_saves/SecretSatchels.sav")
+ var/sav_text
+ secret_satchels[SSmapping.config.map_name] >> sav_text
+ fdel("data/npc_saves/SecretSatchels.sav")
+ if(sav_text)
+ old_secret_satchels = splittext(sav_text,"#")
+ if(old_secret_satchels.len >= 20)
+ var/satchel_string = pick_n_take(old_secret_satchels)
+ var/list/chosen_satchel = splittext(satchel_string,"|")
+ if(chosen_satchel.len == 3)
+ F.x = text2num(chosen_satchel[1])
+ F.y = text2num(chosen_satchel[2])
+ F.z = ZLEVEL_STATION_PRIMARY
+ path = text2path(chosen_satchel[3])
else
- if(teleatom.Move(destturf))
- playSpecials(destturf,effectout,soundout)
- if(ismegafauna(teleatom))
- message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_COORDJMP(curturf)] to [ADMIN_COORDJMP(destturf)].")
- return 1
+ var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json")
+ if(!fexists(json_file))
+ return
+ var/list/json = list()
+ json = json_decode(file2text(json_file))
+ old_secret_satchels = json["data"]
+ if(old_secret_satchels.len)
+ if(old_secret_satchels.len >= 20) //guards against low drop pools assuring that one player cannot reliably find his own gear.
+ var/pos = rand(1, old_secret_satchels.len)
+ old_secret_satchels.Cut(pos, pos+1)
+ F.x = old_secret_satchels[pos]["x"]
+ F.y = old_secret_satchels[pos]["y"]
+ F.z = ZLEVEL_STATION_PRIMARY
+ path = text2path(old_secret_satchels[pos]["saved_obj"])
+ if(!ispath(path))
+ return
+ if(isfloorturf(F.loc) && !istype(F.loc, /turf/open/floor/plating/))
+ F.hide(1)
+ new path(F)
+ placed_satchel++
+ var/list/free_satchels = list()
+ for(var/turf/T in shuffle(block(locate(TRANSITIONEDGE,TRANSITIONEDGE,ZLEVEL_STATION_PRIMARY), locate(world.maxx-TRANSITIONEDGE,world.maxy-TRANSITIONEDGE,ZLEVEL_STATION_PRIMARY)))) //Nontrivially expensive but it's roundstart only
+ if(isfloorturf(T) && !istype(T, /turf/open/floor/plating/))
+ free_satchels += new /obj/item/storage/backpack/satchel/flat/secret(T)
+ if(!isemptylist(free_satchels) && ((free_satchels.len + placed_satchel) >= (50 - old_secret_satchels.len) * 0.1)) //up to six tiles, more than enough to kill anything that moves
+ break
-/datum/teleport/proc/teleport()
- if(teleportChecks())
- return doTeleport()
- return 0
+/datum/controller/subsystem/persistence/proc/LoadPoly()
+ for(var/mob/living/simple_animal/parrot/Poly/P in GLOB.living_mob_list)
+ twitterize(P.speech_buffer, "polytalk")
+ break //Who's been duping the bird?!
-/datum/teleport/instant //teleports when datum is created
-
- start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
- if(..())
- if(teleport())
- return 1
- return 0
-
-
-/datum/teleport/instant/science
-
-/datum/teleport/instant/science/setEffects(datum/effect_system/aeffectin,datum/effect_system/aeffectout)
- if(aeffectin==null || aeffectout==null)
- var/datum/effect_system/spark_spread/aeffect = new
- aeffect.set_up(5, 1, teleatom)
- effectin = effectin || aeffect
- effectout = effectout || aeffect
- return 1
+/datum/controller/subsystem/persistence/proc/LoadChiselMessages()
+ var/list/saved_messages = list()
+ if(fexists("data/npc_saves/ChiselMessages.sav")) //legacy compatability to convert old format to new
+ var/savefile/chisel_messages_sav = new /savefile("data/npc_saves/ChiselMessages.sav")
+ var/saved_json
+ chisel_messages_sav[SSmapping.config.map_name] >> saved_json
+ if(!saved_json)
+ return
+ saved_messages = json_decode(saved_json)
+ fdel("data/npc_saves/ChiselMessages.sav")
else
- return ..()
+ var/json_file = file("data/npc_saves/ChiselMessages[SSmapping.config.map_name].json")
+ if(!fexists(json_file))
+ return
+ var/list/json
+ json = json_decode(file2text(json_file))
-/datum/teleport/instant/science/setPrecision(aprecision)
- ..()
- if(istype(teleatom, /obj/item/storage/backpack/holding))
- precision = rand(1,100)
+ if(!json)
+ return
+ saved_messages = json["data"]
- var/list/bagholding = teleatom.search_contents_for(/obj/item/storage/backpack/holding)
- if(bagholding.len)
- precision = max(rand(1,100)*bagholding.len,100)
- if(isliving(teleatom))
- var/mob/living/MM = teleatom
- to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!")
- return 1
-
-// Safe location finder
-
-/proc/find_safe_turf(zlevel = ZLEVEL_STATION_PRIMARY, list/zlevels, extended_safety_checks = FALSE)
- if(!zlevels)
- zlevels = list(zlevel)
- var/cycles = 1000
- for(var/cycle in 1 to cycles)
- // DRUNK DIALLING WOOOOOOOOO
- var/x = rand(1, world.maxx)
- var/y = rand(1, world.maxy)
- var/z = pick(zlevels)
- var/random_location = locate(x,y,z)
-
- if(!isfloorturf(random_location))
- continue
- var/turf/open/floor/F = random_location
- if(!F.air)
+ for(var/item in saved_messages)
+ if(!islist(item))
continue
- var/datum/gas_mixture/A = F.air
- var/list/A_gases = A.gases
- var/trace_gases
- for(var/id in A_gases)
- if(id in GLOB.hardcoded_gases)
+ var/xvar = item["x"]
+ var/yvar = item["y"]
+ var/zvar = item["z"]
+
+ if(!xvar || !yvar || !zvar)
+ continue
+
+ var/turf/T = locate(xvar, yvar, zvar)
+ if(!isturf(T))
+ continue
+
+ if(locate(/obj/structure/chisel_message) in T)
+ continue
+
+ var/obj/structure/chisel_message/M = new(T)
+
+ if(!QDELETED(M))
+ M.unpack(item)
+
+ 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 = 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
+ 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()
+
+/datum/controller/subsystem/persistence/proc/CollectSecretSatchels()
+ var/list/satchels = list()
+ satchel_blacklist = typecacheof(list(/obj/item/stack/tile/plasteel, /obj/item/crowbar))
+ for(var/A in new_secret_satchels)
+ var/obj/item/storage/backpack/satchel/flat/F = A
+ if(QDELETED(F) || F.z != ZLEVEL_STATION_PRIMARY || 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.admin_spawned)
continue
- trace_gases = TRUE
- break
-
- // Can most things breathe?
- if(trace_gases)
- continue
- if(!(A_gases["o2"] && A_gases["o2"][MOLES] >= 16))
- continue
- if(A_gases["plasma"])
- continue
- if(A_gases["co2"] && A_gases["co2"][MOLES] >= 10)
+ 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 += list(data)
+ var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json")
+ var/list/file_data = list()
+ file_data["data"] = satchels
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
- // Aim for goldilocks temperatures and pressure
- if((A.temperature <= 270) || (A.temperature >= 360))
- continue
- var/pressure = A.return_pressure()
- if((pressure <= 20) || (pressure >= 550))
- continue
+/datum/controller/subsystem/persistence/proc/CollectChiselMessages()
+ var/json_file = file("data/npc_saves/ChiselMessages[SSmapping.config.map_name].json")
- if(extended_safety_checks)
- if(istype(F, /turf/open/lava)) //chasms aren't /floor, and so are pre-filtered
- var/turf/open/lava/L = F
- if(!L.is_safe())
- continue
+ for(var/obj/structure/chisel_message/M in chisel_messages)
+ saved_messages += list(M.pack())
- // DING! You have passed the gauntlet, and are "probably" safe.
- return F
+ log_world("Saved [saved_messages.len] engraved messages on map [SSmapping.config.map_name]")
+ var/list/file_data = list()
+ file_data["data"] = saved_messages
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
-/proc/get_teleport_turfs(turf/center, precision = 0)
- if(!precision)
- return list(center)
- var/list/posturfs = list()
- for(var/turf/T in range(precision,center))
- if(T.is_transition_turf())
- continue // Avoid picking these.
- var/area/A = T.loc
- if(!A.noteleport)
- posturfs.Add(T)
- return posturfs
+/datum/controller/subsystem/persistence/proc/SaveChiselMessage(obj/structure/chisel_message/M)
+ saved_messages += list(M.pack()) // dm eats one list
-/proc/get_teleport_turf(turf/center, precision = 0)
- return safepick(get_teleport_turfs(center, precision))
+
+/datum/controller/subsystem/persistence/proc/CollectTrophies()
+ var/json_file = file("data/npc_saves/TrophyItems.json")
+ var/list/file_data = list()
+ file_data["data"] = saved_trophies
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
+
+/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)