[MIRROR] Trophy case update [MDB IGNORE] (#17616)

This commit is contained in:
SkyratBot
2022-11-22 19:47:18 -08:00
committed by GitHub
parent 58224e565b
commit 6693611532
15 changed files with 628 additions and 201 deletions
+1 -1
View File
@@ -3673,7 +3673,7 @@
/obj/effect/turf_decal/tile/blue/fourcorners,
/obj/structure/displaycase{
start_showpiece_type = /obj/item/dice/d6/space;
trophy_message = "Stolen from dice collector before he could enjoy his day."
desc = "Contains an artifact stolen from dice collector before he could enjoy his day."
},
/turf/open/floor/iron/dark,
/area/awaymission/cabin/caves/mountain)
+1 -1
View File
@@ -128,7 +128,7 @@
#define ACCESS_CHAPEL_OFFICE "chapel_office"
/// Access to the chapel's crematorium.
#define ACCESS_CREMATORIUM "crematorium"
/// Access to the curator's private rooms in the Library, as well as access both into and out of the Library via Maintenance.
/// Access to the curator's private rooms in the Library and the trophy display cases, as well as access both into and out of the Library via Maintenance.
#define ACCESS_LIBRARY "library"
/// Access to the Bar, the Bar's Backroom, the bar sign, the bar robot portal, and the bar's vending machines. Some other bar-things too.
#define ACCESS_BAR "bar"
+1
View File
@@ -98,6 +98,7 @@
#define MAX_NAME_LEN 42
#define MAX_BROADCAST_LEN 512
#define MAX_CHARTER_LEN 80
#define MAX_PLAQUE_LEN 144
// Audio/Visual Flags. Used to determine what sense are required to notice a message.
#define MSG_VISUAL (1<<0)
+133 -65
View File
@@ -25,37 +25,40 @@ SUBSYSTEM_DEF(persistence)
var/rounds_since_engine_exploded = 0
/datum/controller/subsystem/persistence/Initialize()
LoadPoly()
load_poly()
load_wall_engravings()
load_prisoner_tattoos()
LoadTrophies()
LoadRecentMaps()
LoadPhotoPersistence()
LoadRandomizedRecipes()
LoadPanicBunker() //SKYRAT EDIT ADDITION - PANICBUNKER
load_trophies()
load_recent_maps()
load_photo_persistence()
load_randomized_recipes()
load_custom_outfits()
load_delamination_counter()
load_panic_bunker() //SKYRAT EDIT ADDITION - PANICBUNKER
load_adventures()
return SS_INIT_SUCCESS
///Collects all data to persist.
/datum/controller/subsystem/persistence/proc/collect_data()
save_wall_engravings()
save_prisoner_tattoos()
CollectTrophies()
CollectMaps()
SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION.
SaveRandomizedRecipes()
SavePanicBunker()//SKYRAT EDIT ADDITION - PANICBUNKER
SaveScars()
collect_trophies()
collect_maps()
save_photo_persistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION.
save_randomized_recipes()
save_scars()
save_custom_outfits()
save_delamination_counter()
save_panic_bunker() //SKYRAT EDIT ADDITION - PANICBUNKER
/datum/controller/subsystem/persistence/proc/LoadPoly()
///Loads up Poly's speech buffer.
/datum/controller/subsystem/persistence/proc/load_poly()
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?!
///Loads all engravings, and places a select amount in maintenance and the prison.
/datum/controller/subsystem/persistence/proc/load_wall_engravings()
var/json_file = file(ENGRAVING_SAVE_FILE)
if(!fexists(json_file))
@@ -97,6 +100,7 @@ SUBSYSTEM_DEF(persistence)
log_world("Loaded [successfully_loaded_engravings] engraved messages on map [SSmapping.config.map_name]")
///Saves all new engravings in the world.
/datum/controller/subsystem/persistence/proc/save_wall_engravings()
var/list/saved_data = list()
@@ -136,6 +140,7 @@ SUBSYSTEM_DEF(persistence)
return json
///Loads all tattoos, and select a few based on the amount of prisoner spawn positions.
/datum/controller/subsystem/persistence/proc/load_prisoner_tattoos()
var/json_file = file(PRISONER_TATTOO_SAVE_FILE)
if(!fexists(json_file))
@@ -159,6 +164,7 @@ SUBSYSTEM_DEF(persistence)
log_world("Loaded [prison_tattoos_to_use.len] prison tattoos")
///Saves all tattoos, so they can appear on prisoners in future rounds
/datum/controller/subsystem/persistence/proc/save_prisoner_tattoos()
var/json_file = file(PRISONER_TATTOO_SAVE_FILE)
var/list/saved_data = list()
@@ -190,27 +196,68 @@ SUBSYSTEM_DEF(persistence)
return json
/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
/// Loads the trophies from the source file, and places a few in trophy display cases.
/datum/controller/subsystem/persistence/proc/load_trophies()
var/list/raw_saved_trophies = list()
if(fexists("data/npc_saves/TrophyItems.json"))
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())
raw_saved_trophies = json["data"]
fdel("data/npc_saves/TrophyItems.json")
else
var/json_file = file("data/trophy_items.json")
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
raw_saved_trophies = json["data"]
/datum/controller/subsystem/persistence/proc/LoadRecentMaps()
for(var/raw_json in raw_saved_trophies)
var/datum/trophy_data/parsed_trophy_data = new
parsed_trophy_data.load_from_json(raw_json)
saved_trophies += parsed_trophy_data
set_up_trophies()
///trophy data datum, for admin manipulation
/datum/trophy_data
///path of the item the trophy will try to mimic, null if path_string is invalid
var/path
///the message that appears under the item
var/message
///the key of the one who placed the item in the trophy case
var/placer_key
/datum/trophy_data/proc/load_from_json(list/json_data)
path = json_data["path"]
message = json_data["message"]
placer_key = json_data["placer_key"]
/datum/trophy_data/proc/to_json()
var/list/new_data = list()
new_data["path"] = path
new_data["message"] = message
new_data["placer_key"] = placer_key
new_data["is_valid"] = text2path(path) ? TRUE : FALSE
return new_data
/// Returns a list for the admin trophy panel.
/datum/controller/subsystem/persistence/proc/trophy_ui_data()
var/list/ui_data = list()
for(var/datum/trophy_data/data in saved_trophies)
var/list/pdata = data.to_json()
pdata["ref"] = REF(data)
ui_data += list(pdata)
return ui_data
/// Loads up the amount of times maps appeared to alter their appearance in voting and rotation.
/datum/controller/subsystem/persistence/proc/load_recent_maps()
var/map_sav = FILE_RECENT_MAPS
if(!fexists(FILE_RECENT_MAPS))
return
@@ -231,44 +278,45 @@ SUBSYSTEM_DEF(persistence)
if(run >= 2) //If run twice in the last KEEP_ROUNDS_MAP + 1 (including current) rounds, disable map for voting and rotation.
blocked_maps += VM.map_name
/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
/// Puts trophies into trophy cases.
/datum/controller/subsystem/persistence/proc/set_up_trophies()
var/trophy_data = pick_n_take(trophy_items)
var/list/valid_trophies = list()
if(!islist(trophy_data))
for(var/datum/trophy_data/data in saved_trophies)
if(!data) //sanity for incorrect deserialization
continue
var/list/chosen_trophy = trophy_data
if(!length(chosen_trophy)) //Malformed
var/path = text2path(data.path)
if(!path) //If the item no longer exist, ignore it
continue
var/path = text2path(chosen_trophy["path"]) //If the item no longer exist, this returns null
if(!path)
valid_trophies += data
for(var/obj/structure/displaycase/trophy/trophy_case in GLOB.trophy_cases)
if(!valid_trophies.len)
break
if(trophy_case.showpiece)
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_appearance()
trophy_case.set_up_trophy(pick_n_take(valid_trophies))
/datum/controller/subsystem/persistence/proc/GetPhotoAlbums()
///Loads up the photo album source file.
/datum/controller/subsystem/persistence/proc/get_photo_albums()
var/album_path = file("data/photo_albums.json")
if(fexists(album_path))
return json_decode(file2text(album_path))
/datum/controller/subsystem/persistence/proc/GetPhotoFrames()
///Loads up the photo frames source file.
/datum/controller/subsystem/persistence/proc/get_photo_frames()
var/frame_path = file("data/photo_frames.json")
if(fexists(frame_path))
return json_decode(file2text(frame_path))
/// Removes the identifier of a persitent photo frame from the json.
/datum/controller/subsystem/persistence/proc/RemovePhotoFrame(identifier)
/// Removes the identifier of a persistent photo frame from the json.
/datum/controller/subsystem/persistence/proc/remove_photo_frames(identifier)
var/frame_path = file("data/photo_frames.json")
if(!fexists(frame_path))
return
@@ -280,7 +328,8 @@ SUBSYSTEM_DEF(persistence)
fdel(frame_path)
WRITE_FILE(frame_path, frame_json)
/datum/controller/subsystem/persistence/proc/LoadPhotoPersistence()
///Loads photo albums, and populates them; also loads and applies frames to picture frames.
/datum/controller/subsystem/persistence/proc/load_photo_persistence()
var/album_path = file("data/photo_albums.json")
var/frame_path = file("data/photo_frames.json")
if(fexists(album_path))
@@ -303,7 +352,8 @@ SUBSYSTEM_DEF(persistence)
if(json[PF.persistence_id])
PF.load_from_id(json[PF.persistence_id])
/datum/controller/subsystem/persistence/proc/SavePhotoPersistence()
///Saves the contents of photo albums and the picture frames.
/datum/controller/subsystem/persistence/proc/save_photo_persistence()
var/album_path = file("data/photo_albums.json")
var/frame_path = file("data/photo_frames.json")
@@ -339,13 +389,25 @@ SUBSYSTEM_DEF(persistence)
WRITE_FILE(frame_path, frame_json)
/datum/controller/subsystem/persistence/proc/CollectTrophies()
var/json_file = file("data/npc_saves/TrophyItems.json")
///Collects trophies from all existing trophy cases.
/datum/controller/subsystem/persistence/proc/collect_trophies()
for(var/trophy_case in GLOB.trophy_cases)
save_trophy(trophy_case)
var/json_file = file("data/trophy_items.json")
var/list/file_data = list()
file_data["data"] = remove_duplicate_trophies(saved_trophies)
var/list/converted_data = list()
for(var/datum/trophy_data/data in saved_trophies)
converted_data += list(data.to_json())
converted_data = remove_duplicate_trophies(converted_data)
file_data["data"] = converted_data
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
///gets the list of json trophies, and deletes the ones with an identical path and message
/datum/controller/subsystem/persistence/proc/remove_duplicate_trophies(list/trophies)
var/list/ukeys = list()
. = list()
@@ -357,15 +419,17 @@ SUBSYSTEM_DEF(persistence)
. += 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)
///If there is a trophy in the trophy case, saved it, if the trophy was not a holo trophy and has a message attached.
/datum/controller/subsystem/persistence/proc/save_trophy(obj/structure/displaycase/trophy/trophy_case)
if(!trophy_case.holographic_showpiece && trophy_case.showpiece && trophy_case.trophy_message)
var/datum/trophy_data/data = new
data.path = trophy_case.showpiece.type
data.message = trophy_case.trophy_message
data.placer_key = trophy_case.placer_key
saved_trophies += data
/datum/controller/subsystem/persistence/proc/CollectMaps()
///Updates the list of the most recent maps.
/datum/controller/subsystem/persistence/proc/collect_maps()
if(length(saved_maps) > KEEP_ROUNDS_MAP) //Get rid of extras from old configs.
saved_maps.Cut(KEEP_ROUNDS_MAP+1)
var/mapstosave = min(length(saved_maps)+1, KEEP_ROUNDS_MAP)
@@ -380,7 +444,8 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/persistence/proc/LoadRandomizedRecipes()
///Loads all randomized recipes.
/datum/controller/subsystem/persistence/proc/load_randomized_recipes()
var/json_file = file("data/RandomizedChemRecipes.json")
var/json
if(fexists(json_file))
@@ -404,7 +469,8 @@ SUBSYSTEM_DEF(persistence)
else
log_game("Randomized recipe [randomized_type] resulted in conflicting recipes.")
/datum/controller/subsystem/persistence/proc/SaveRandomizedRecipes()
///Saves all randomized recipes.
/datum/controller/subsystem/persistence/proc/save_randomized_recipes()
var/json_file = file("data/RandomizedChemRecipes.json")
var/list/file_data = list()
@@ -418,7 +484,8 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/persistence/proc/SaveScars()
///Saves all scars for everyone's original characters
/datum/controller/subsystem/persistence/proc/save_scars()
for(var/i in GLOB.joined_player_list)
var/mob/living/carbon/human/ending_human = get_mob_by_ckey(i)
if(!istype(ending_human) || !ending_human.mind?.original_character_slot_index || !ending_human.client?.prefs.read_preference(/datum/preference/toggle/persistent_scars))
@@ -434,7 +501,7 @@ SUBSYSTEM_DEF(persistence)
else
original_human.save_persistent_scars()
///Loads the custom outfits of every admin.
/datum/controller/subsystem/persistence/proc/load_custom_outfits()
var/file = file("data/custom_outfits.json")
if(!fexists(file))
@@ -456,6 +523,7 @@ SUBSYSTEM_DEF(persistence)
continue
GLOB.custom_outfits += outfit
///Saves each admin's custom outfit list
/datum/controller/subsystem/persistence/proc/save_custom_outfits()
var/file = file("data/custom_outfits.json")
fdel(file)
+176 -126
View File
@@ -9,28 +9,28 @@
armor = list(MELEE = 30, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 0, FIRE = 70, ACID = 100)
max_integrity = 200
integrity_failure = 0.25
///The showpiece item inside the case
var/obj/item/showpiece = null
var/obj/item/showpiece_type = null //This allows for showpieces that can only hold items if they're the same istype as this.
///This allows for showpieces that can only hold items if they're the same istype as this.
var/obj/item/showpiece_type = null
///Is the displaycase hooked up to a burglar alarm?
var/alert = TRUE
///Is the displaycase open at the moment?
var/open = FALSE
var/openable = TRUE
var/custom_glass_overlay = FALSE ///If we have a custom glass overlay to use.
///If we have a custom glass overlay to use.
var/custom_glass_overlay = FALSE
var/obj/item/electronics/airlock/electronics
var/start_showpiece_type = null //add type for items on display
var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk")
var/trophy_message = ""
///Add type for items on display
var/start_showpiece_type = null
///Displaycase is fixed by glass
var/glass_fix = TRUE
///Represents a signel source of screaming when broken
var/datum/alarm_handler/alarm_manager
///Used for subtypes that have a UI in them. The examine on click while adjecent will not fire, as we already get a popup
var/autoexamine_while_closed = TRUE
/obj/structure/displaycase/Initialize(mapload)
. = ..()
if(start_showpieces.len && !start_showpiece_type)
var/list/showpiece_entry = pick(start_showpieces)
if (showpiece_entry && showpiece_entry["type"])
start_showpiece_type = showpiece_entry["type"]
if (showpiece_entry["trophy_message"])
trophy_message = showpiece_entry["trophy_message"]
if(start_showpiece_type)
showpiece = new start_showpiece_type (src)
update_appearance()
@@ -61,9 +61,8 @@
. += span_notice("Hooked up with an anti-theft system.")
if(showpiece)
. += span_notice("There's \a [showpiece] inside.")
if(trophy_message)
. += "The plaque reads:\n [trophy_message]"
///Removes the showpiece from the displaycase
/obj/structure/displaycase/proc/dump()
if(QDELETED(showpiece))
return
@@ -124,27 +123,27 @@
. += "[initial(icon_state)]_closed"
return
/obj/structure/displaycase/attackby(obj/item/W, mob/living/user, params)
if(W.GetID() && !broken && openable)
/obj/structure/displaycase/attackby(obj/item/tool, mob/living/user, params)
if(tool.GetID() && !broken)
if(allowed(user))
to_chat(user, span_notice("You [open ? "close":"open"] [src]."))
to_chat(user, span_notice("You [open ? "close":"open"] [src]."))
toggle_lock(user)
else
to_chat(user, span_alert("Access denied."))
else if(W.tool_behaviour == TOOL_WELDER && !user.combat_mode && !broken)
else if(tool.tool_behaviour == TOOL_WELDER && !user.combat_mode && !broken)
if(atom_integrity < max_integrity)
if(!W.tool_start_check(user, amount=5))
if(!tool.tool_start_check(user, amount=5))
return
to_chat(user, span_notice("You begin repairing [src]..."))
if(W.use_tool(src, user, 40, amount=5, volume=50))
if(tool.use_tool(src, user, 40, amount=5, volume=50))
atom_integrity = max_integrity
update_appearance()
to_chat(user, span_notice("You repair [src]."))
else
to_chat(user, span_warning("[src] is already in good condition!"))
return
else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases
else if(!alert && tool.tool_behaviour == TOOL_CROWBAR) //Only applies to the lab cage and player made display cases
if(broken)
if(showpiece)
to_chat(user, span_warning("Remove the displayed object first!"))
@@ -153,35 +152,39 @@
qdel(src)
else
to_chat(user, span_notice("You start to [open ? "close":"open"] [src]..."))
if(W.use_tool(src, user, 20))
to_chat(user, span_notice("You [open ? "close":"open"] [src]."))
if(tool.use_tool(src, user, 20))
to_chat(user, span_notice("You [open ? "close":"open"] [src]."))
toggle_lock(user)
else if(open && !showpiece)
insert_showpiece(W, user)
else if(glass_fix && broken && istype(W, /obj/item/stack/sheet/glass))
var/obj/item/stack/sheet/glass/G = W
if(G.get_amount() < 2)
insert_showpiece(tool, user)
return TRUE //cancel the attack chain, wether we successfully placed an item or not
else if(glass_fix && broken && istype(tool, /obj/item/stack/sheet/glass))
var/obj/item/stack/sheet/glass/glass_sheet = tool
if(glass_sheet.get_amount() < 2)
to_chat(user, span_warning("You need two glass sheets to fix the case!"))
return
to_chat(user, span_notice("You start fixing [src]..."))
if(do_after(user, 20, target = src))
G.use(2)
glass_sheet.use(2)
broken = FALSE
atom_integrity = max_integrity
update_appearance()
else
return ..()
/obj/structure/displaycase/proc/insert_showpiece(obj/item/wack, mob/user)
if(showpiece_type && !istype(wack, showpiece_type))
///Handles placing an item into the display case. Returns TRUE if the item failed to be placed inside the container, useful for descendants
/obj/structure/displaycase/proc/insert_showpiece(obj/item/new_showpiece, mob/user)
if(showpiece_type && !istype(new_showpiece, showpiece_type))
to_chat(user, span_notice("This doesn't belong in this kind of display."))
return TRUE
if(user.transferItemToLoc(wack, src))
showpiece = wack
to_chat(user, span_notice("You put [wack] on display."))
if(user.transferItemToLoc(new_showpiece, src))
showpiece = new_showpiece
to_chat(user, span_notice("You put [new_showpiece] on display."))
update_appearance()
///Opens and closes the display case
/obj/structure/displaycase/proc/toggle_lock(mob/user)
playsound(src, 'sound/machines/click.ogg', 20, TRUE)
open = !open
update_appearance()
@@ -200,10 +203,12 @@
add_fingerprint(user)
return
else
//prevents remote "kicks" with TK
//prevents remote "kicks" with TK
if (!Adjacent(user))
return
if (!user.combat_mode)
if(!open && !autoexamine_while_closed)
return
if(!user.is_blind())
user.examinate(src)
return
@@ -290,11 +295,17 @@
/obj/structure/displaycase/trophy
name = "trophy display case"
desc = "Store your trophies of accomplishment in here, and they will stay forever."
var/placer_key = ""
var/added_roundstart = TRUE
var/is_locked = TRUE
integrity_failure = 0
openable = FALSE
req_access = list(ACCESS_LIBRARY)
autoexamine_while_closed = FALSE
///the key of the player who placed the item in the case
var/placer_key = ""
///is the trophy a hologram, not a real item placed by a player?
var/holographic_showpiece = FALSE
///are we about to edit
var/historian_mode = FALSE
///the trophy message
var/trophy_message = ""
/obj/structure/displaycase/trophy/Initialize(mapload)
. = ..()
@@ -304,93 +315,121 @@
GLOB.trophy_cases -= src
return ..()
///Creates a showpiece dummy to display, using persistent data
/obj/structure/displaycase/trophy/proc/set_up_trophy(datum/trophy_data/chosen_trophy)
showpiece = new /obj/item/showpiece_dummy(src, text2path(chosen_trophy.path))
trophy_message = trim(chosen_trophy.message, MAX_PLAQUE_LEN)
if(trophy_message == "")
trophy_message = trim(showpiece.desc, MAX_PLAQUE_LEN)
placer_key = trim(chosen_trophy.placer_key)
holographic_showpiece = TRUE
update_appearance()
/obj/structure/displaycase/trophy/attackby(obj/item/W, mob/living/user, params)
if(!user.Adjacent(src)) //no TK museology
if(istype(W, /obj/item/key/displaycase))
toggle_historian_mode(user)
return
if(user.combat_mode)
return ..()
if(W.tool_behaviour == TOOL_WELDER && !broken)
return ..()
if(user.is_holding_item_of_type(/obj/item/key/displaycase))
if(added_roundstart)
is_locked = !is_locked
to_chat(user, span_notice("You [!is_locked ? "un" : ""]lock the case."))
else
to_chat(user, span_warning("The lock is stuck shut!"))
return
if(is_locked)
to_chat(user, span_warning("The case is shut tight with an old-fashioned physical lock. Maybe you should ask the curator for the key?"))
return
if(!added_roundstart)
to_chat(user, span_warning("You've already put something new in this case!"))
return
if(is_type_in_typecache(W, GLOB.blacklisted_cargo_types))
to_chat(user, span_warning("The case rejects the [W]!"))
return
for(var/a in W.get_all_contents())
if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types))
to_chat(user, span_warning("The case rejects the [W]!"))
return
if(user.transferItemToLoc(W, src))
if(showpiece)
to_chat(user, span_notice("You press a button, and [showpiece] descends into the floor of the case."))
QDEL_NULL(showpiece)
to_chat(user, span_notice("You insert [W] into the case."))
showpiece = W
added_roundstart = FALSE
update_appearance()
placer_key = user.ckey
trophy_message = W.desc //default value
var/chosen_plaque = tgui_input_text(user, "What would you like the plaque to say? Default value is item's description.", "Trophy Plaque", trophy_message)
if(chosen_plaque)
if(user.Adjacent(src))
trophy_message = chosen_plaque
to_chat(user, span_notice("You set the plaque's text."))
else
to_chat(user, span_warning("You are too far to set the plaque's text!"))
SSpersistence.SaveTrophy(src)
return TRUE
else
to_chat(user, span_warning("\The [W] is stuck to your hand, you can't put it in the [src.name]!"))
return
return ..()
/obj/structure/displaycase/trophy/dump()
if (showpiece)
if(added_roundstart)
visible_message(span_danger("The [showpiece] crumbles to dust!"))
new /obj/effect/decal/cleanable/ash(loc)
if(holographic_showpiece)
visible_message(span_danger("[showpiece] fizzles and vanishes!"))
do_sparks(number = 1, cardinal_only = FALSE, source = src)
QDEL_NULL(showpiece)
holographic_showpiece = FALSE
else
return ..()
..()
placer_key = ""
trophy_message = null
/obj/structure/displaycase/trophy/insert_showpiece(obj/item/new_showpiece, mob/user)
if(..())
return TRUE
if(showpiece == new_showpiece)
placer_key = user.ckey
///Toggles the mode that shows the historian panel on the UI, enabling saving the looks and the trophy message of the current trophy
/obj/structure/displaycase/trophy/proc/toggle_historian_mode(mob/user)
historian_mode = !historian_mode
balloon_alert(user, "[historian_mode ? "enabled" : "disabled"] historian mode.")
playsound(src, 'sound/machines/twobeep.ogg', vary = 50)
SStgui.update_uis(src)
/obj/structure/displaycase/trophy/toggle_lock(mob/user)
..()
SStgui.close_uis(src)
/obj/structure/displaycase/trophy/ui_data(mob/user)
var/list/data = list()
data["historian_mode"] = historian_mode
data["holographic_showpiece"] = holographic_showpiece
data["max_length"] = MAX_PLAQUE_LEN
data["has_showpiece"] = showpiece ? TRUE : FALSE
if(showpiece)
data["showpiece_name"] = capitalize(format_text(showpiece.name))
data["showpiece_description"] = trophy_message ? format_text(trophy_message) : null
return data
/obj/structure/displaycase/trophy/ui_static_data(mob/user)
var/list/data = list()
if(showpiece)
data["showpiece_icon"] = icon2base64(getFlatIcon(showpiece, no_anim=TRUE))
return data
/obj/structure/displaycase/trophy/ui_act(action, params)
. = ..()
if(.)
return
switch(action)
if("insert_key")
if(historian_mode)
return
var/obj/item/key/displaycase/trophy_key = usr.get_active_held_item()
if(istype(trophy_key))
toggle_historian_mode(usr)
return TRUE
return
if("change_message")
if(showpiece && !holographic_showpiece)
var/new_trophy_message = tgui_input_text(usr, "Let's make history!", "Trophy Message", trophy_message, MAX_PLAQUE_LEN)
if(!new_trophy_message)
return
trophy_message = new_trophy_message
return TRUE
if("lock")
if(!historian_mode)
return
toggle_historian_mode(usr)
return TRUE
/obj/structure/displaycase/trophy/ui_interact(mob/user, datum/tgui/ui)
if(open)
return
if(isliving(usr))
var/mob/living/living_usr = usr
if(living_usr.combat_mode)
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Trophycase", name)
ui.set_autoupdate(FALSE)
ui.open()
/obj/item/key/displaycase
name = "display case key"
desc = "The key to the curator's display cases."
/obj/item/showpiece_dummy
name = "Cheap replica"
name = "holographic replica"
/obj/item/showpiece_dummy/Initialize(mapload, path)
. = ..()
var/obj/item/I = path
name = initial(I.name)
icon = initial(I.icon)
icon_state = initial(I.icon_state)
var/obj/item/item_path = path
name = initial(item_path.name)
desc = initial(item_path.desc)
icon = initial(item_path.icon)
icon_state = initial(item_path.icon_state)
/obj/structure/displaycase/forsale
name = "vend-a-tray"
@@ -403,6 +442,7 @@
alert = FALSE //No, we're not calling the fire department because someone stole your cookie.
glass_fix = FALSE //Fixable with tools instead.
pass_flags = PASSTABLE ///Can be placed and moved onto a table.
autoexamine_while_closed = FALSE
///The price of the item being sold. Altered by grab intent ID use.
var/sale_price = 20
///The Account which will receive payment for purchases. Set by the first ID to swipe the tray.
@@ -417,6 +457,19 @@
if(!broken && !open)
. += "[initial(icon_state)]_overlay"
/obj/structure/displaycase/forsale/insert_showpiece(obj/item/new_showpiece, mob/user)
if(..())
return TRUE
update_static_data_for_all_viewers()
/obj/structure/displaycase/forsale/dump()
..()
update_static_data_for_all_viewers()
/obj/structure/displaycase/forsale/toggle_lock()
..()
SStgui.update_uis(src)
/obj/structure/displaycase/forsale/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
@@ -426,19 +479,18 @@
/obj/structure/displaycase/forsale/ui_data(mob/user)
var/list/data = list()
var/register = FALSE
if(payments_acc)
register = TRUE
data["owner_name"] = payments_acc.account_holder
if(showpiece)
data["product_name"] = capitalize(showpiece.name)
var/base64 = icon2base64(icon(showpiece.icon, showpiece.icon_state))
data["product_icon"] = base64
data["registered"] = register
data["owner_name"] = payments_acc ? payments_acc.account_holder : null
data["product_name"] = showpiece ?capitalize(format_text(showpiece.name)) : null
data["registered"] = payments_acc ? TRUE : FALSE
data["product_cost"] = sale_price
data["tray_open"] = open
return data
/obj/structure/displaycase/forsale/ui_static_data(mob/user)
var/list/data = list()
data["product_icon"] = showpiece ? icon2base64(getFlatIcon(showpiece, no_anim=TRUE)) : null
return data
/obj/structure/displaycase/forsale/ui_act(action, params)
. = ..()
if(.)
@@ -471,16 +523,16 @@
to_chat(usr, span_notice("You do not possess the funds to purchase this."))
return TRUE
else
account.adjust_money(-sale_price, "Display Case: [capitalize(showpiece)]")
account.adjust_money(-sale_price, "Display Case: [capitalize(showpiece.name)]")
if(payments_acc)
payments_acc.adjust_money(sale_price, "Display Case: [capitalize(showpiece)]")
payments_acc.adjust_money(sale_price, "Display Case: [capitalize(showpiece.name)]")
usr.put_in_hands(showpiece)
to_chat(usr, span_notice("You purchase [showpiece] for [sale_price] credits."))
playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE)
flick("[initial(icon_state)]_vend", src)
showpiece = null
update_appearance()
SStgui.update_uis(src)
update_static_data_for_all_viewers()
return TRUE
if("Open")
if(!payments_acc)
@@ -492,7 +544,6 @@
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
return
toggle_lock()
SStgui.update_uis(src)
if("Register")
if(payments_acc)
return
@@ -522,6 +573,7 @@
SStgui.update_uis(src)
return TRUE
. = TRUE
/obj/structure/displaycase/forsale/attackby(obj/item/I, mob/living/user, params)
if(isidcard(I))
//Card Registration
@@ -530,18 +582,16 @@
to_chat(user, span_warning("This ID card has no account registered!"))
return
if(payments_acc == potential_acc.registered_account)
playsound(src, 'sound/machines/click.ogg', 20, TRUE)
toggle_lock()
return
if(istype(I, /obj/item/modular_computer))
return TRUE
SStgui.update_uis(src)
. = ..()
return ..()
/obj/structure/displaycase/forsale/multitool_act(mob/living/user, obj/item/I)
. = ..()
if(atom_integrity <= (integrity_failure * max_integrity))
if(atom_integrity <= (integrity_failure * max_integrity))
to_chat(user, span_notice("You start recalibrating [src]'s hover field..."))
if(do_after(user, 20, target = src))
broken = FALSE
+1
View File
@@ -96,6 +96,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/list_dna,
/client/proc/list_fingerprints,
/client/proc/message_pda, /*send a message to somebody on PDA*/
/datum/admins/proc/trophy_manager,
/client/proc/fax_panel, /*send a paper to fax*/
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel))
+63
View File
@@ -0,0 +1,63 @@
/datum/admins/proc/trophy_manager()
set name = "Trophy Manager"
set category = "Admin"
if(!check_rights(R_ADMIN))
return
var/datum/trophy_manager/ui = new(usr)
ui.ui_interact(usr)
/// Trophy Admin Management Panel
/datum/trophy_manager
/datum/trophy_manager/ui_state(mob/user)
return GLOB.admin_state
/datum/trophy_manager/ui_close(mob/user)
qdel(src)
/datum/trophy_manager/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "TrophyAdminPanel")
ui.open()
/datum/trophy_manager/ui_data(mob/user)
var/list/data = list()
data["trophies"] = SSpersistence.trophy_ui_data()
return data
/datum/trophy_manager/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return
if (!check_rights(R_ADMIN))
return
var/mob/user = usr
var/datum/trophy_data/trophy = locate(params["ref"]) in SSpersistence.saved_trophies
if(!trophy)
return
switch(action)
if("delete")
SSpersistence.saved_trophies -= trophy
log_admin("[key_name(user)] has deleted a trophy made by [trophy.placer_key].")
message_admins(span_notice("[key_name_admin(user)] has deleted trophy made by [trophy.placer_key]."))
return TRUE
if("edit_message")
var/old_message = trophy.message
var/new_message = tgui_input_text(user, "New trophy message?", "Message Editing", trophy.message, max_length = MAX_PLAQUE_LEN)
if(!new_message)
return
trophy.message = new_message
log_admin("[key_name(user)] has edited the message of trophy made by [trophy.placer_key] from \"[old_message]\" to \"[new_message]\".")
return TRUE
if("edit_path")
var/old_path = trophy.path
var/new_path = tgui_input_text(user, "New trophy path?", "Path Editing", trophy.path)
if(!new_path)
return
if(!text2path(new_path))
to_chat(user, span_warning("Invalid path selected"))
return
trophy.path = new_path
log_admin("[key_name(user)] has edited the item path of trophy made by [trophy.placer_key] from \"[old_path]\" to \"[new_path]\".")
return TRUE
+1 -1
View File
@@ -41,7 +41,7 @@
//Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID.
/obj/item/storage/photo_album/proc/persistence_load()
var/list/data = SSpersistence.GetPhotoAlbums()
var/list/data = SSpersistence.get_photo_albums()
if(data[persistence_id])
populate_from_id_list(data[persistence_id])
+2 -2
View File
@@ -88,7 +88,7 @@
/obj/structure/sign/picture_frame/Destroy()
LAZYREMOVE(SSpersistence.photo_frames, src)
if(persistence_id && del_id_on_destroy)
SSpersistence.RemovePhotoFrame(persistence_id)
SSpersistence.remove_photo_frames(persistence_id)
return ..()
/obj/structure/sign/picture_frame/proc/get_photo_id()
@@ -97,7 +97,7 @@
//Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID.
/obj/structure/sign/picture_frame/proc/persistence_load()
var/list/data = SSpersistence.GetPhotoFrames()
var/list/data = SSpersistence.get_photo_frames()
if(data[persistence_id])
load_from_id(data[persistence_id])
@@ -10,7 +10,7 @@ GLOBAL_LIST_EMPTY(bunker_passthrough)
GLOB.bunker_passthrough |= ckey(ckeytobypass)
GLOB.bunker_passthrough[ckey(ckeytobypass)] = world.realtime
SSpersistence.SavePanicBunker() //we can do this every time, it's okay
SSpersistence.save_panic_bunker() //we can do this every time, it's okay
log_admin("[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.")
message_admins("[key_name_admin(usr)] has added [ckeytobypass] to the current round's bunker bypass list.")
@@ -24,7 +24,7 @@ GLOBAL_LIST_EMPTY(bunker_passthrough)
return
GLOB.bunker_passthrough -= ckey(ckeytobypass)
SSpersistence.SavePanicBunker()
SSpersistence.save_panic_bunker()
log_admin("[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.")
message_admins("[key_name_admin(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.")
@@ -40,12 +40,12 @@ GLOBAL_LIST_EMPTY(bunker_passthrough)
GLOB.bunker_passthrough |= ckey(params)
GLOB.bunker_passthrough[ckey(params)] = world.realtime
SSpersistence.SavePanicBunker() //we can do this every time, it's okay
SSpersistence.save_panic_bunker() //we can do this every time, it's okay
log_admin("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
message_admins("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
return "[params] has been added to the current round's bunker bypass list."
/datum/controller/subsystem/persistence/proc/LoadPanicBunker()
/datum/controller/subsystem/persistence/proc/load_panic_bunker()
var/bunker_path = file("data/bunker_passthrough.json")
if(fexists(bunker_path))
var/list/json = json_decode(file2text(bunker_path))
@@ -54,7 +54,7 @@ GLOBAL_LIST_EMPTY(bunker_passthrough)
if(daysSince(GLOB.bunker_passthrough[ckey]) >= CONFIG_GET(number/max_bunker_days))
GLOB.bunker_passthrough -= ckey
/datum/controller/subsystem/persistence/proc/SavePanicBunker()
/datum/controller/subsystem/persistence/proc/save_panic_bunker()
var/json_file = file("data/bunker_passthrough.json")
var/list/file_data = list()
file_data["data"] = GLOB.bunker_passthrough
+1
View File
@@ -2163,6 +2163,7 @@
#include "code\modules\admin\tag.dm"
#include "code\modules\admin\team_panel.dm"
#include "code\modules\admin\topic.dm"
#include "code\modules\admin\trophy_manager.dm"
#include "code\modules\admin\whitelist.dm"
#include "code\modules\admin\callproc\callproc.dm"
#include "code\modules\admin\smites\bad_luck.dm"
@@ -0,0 +1,78 @@
import { decodeHtmlEntities } from 'common/string';
import { useBackend } from '../backend';
import { Button, Table } from '../components';
import { Window } from '../layouts';
export const TrophyAdminPanel = (props, context) => {
const { act, data } = useBackend(context);
const { trophies } = data;
return (
<Window title="Trophies Admin Panel" width={800} height={600}>
<Window.Content scrollable>
<Table>
<Table.Row header>
<Table.Cell color="label">Path</Table.Cell>
<Table.Cell color="label" />
<Table.Cell color="label">Message</Table.Cell>
<Table.Cell color="label" />
<Table.Cell color="label">Placer Key</Table.Cell>
<Table.Cell color="label" />
</Table.Row>
{!!trophies &&
trophies.map((trophy) => (
<Table.Row key={trophy.ref} className="candystripe">
<Table.Cell
style={{
'word-break': 'break-all',
'word-wrap': 'break-word',
'color': !trophy.is_valid
? 'rgba(255, 0, 0, 0.5)'
: 'inherit',
}}>
{decodeHtmlEntities(trophy.path)}
</Table.Cell>
<Table.Cell>
<Button
icon="edit"
tooltip={'Edit path'}
tooltipPosition="bottom"
onClick={() => act('edit_path', { ref: trophy.ref })}
/>
</Table.Cell>
<Table.Cell
style={{
'word-break': 'break-all',
'word-wrap': 'break-word',
}}>
{decodeHtmlEntities(trophy.message)}
</Table.Cell>
<Table.Cell>
<Button
icon="edit"
tooltip={'Edit message'}
tooltipPosition="bottom"
onClick={() => act('edit_message', { ref: trophy.ref })}
/>
</Table.Cell>
<Table.Cell
style={{
'word-break': 'break-all',
'word-wrap': 'break-word',
}}>
{decodeHtmlEntities(trophy.placer_key)}
</Table.Cell>
<Table.Cell>
<Button
icon="trash"
tooltip={'Delete trophy'}
tooltipPosition="bottom"
onClick={() => act('delete', { ref: trophy.ref })}
/>
</Table.Cell>
</Table.Row>
))}
</Table>
</Window.Content>
</Window>
);
};
+159
View File
@@ -0,0 +1,159 @@
import { decodeHtmlEntities } from 'common/string';
import { useBackend } from '../backend';
import { Icon, Box, Button, Dimmer, Section, Stack } from '../components';
import { Window } from '../layouts';
export const Trophycase = (props, context) => {
const { act, data } = useBackend(context);
return (
<Window width={300} height={380}>
<Window.Content>
<Stack vertical fill>
<Stack.Item>
<ShowpieceName />
</Stack.Item>
<Stack.Item>
<ShowpieceImage />
</Stack.Item>
<Stack.Item grow>
<ShowpieceDescription />
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<HistorianPanel />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
const HistorianPanel = (props, context) => {
const { act, data } = useBackend(context);
const {
has_showpiece,
historian_mode,
holographic_showpiece,
showpiece_description,
} = data;
return (
<Section align="left">
{!historian_mode && (
<Button
icon="key"
content="Insert key for historian mode"
onClick={() => act('insert_key')}
/>
)}
{!!historian_mode && (
<div>
<Button
icon="times"
content="Lock historian mode"
onClick={() => act('lock')}
/>
<Button
icon="pencil"
content="Edit description"
disabled={!has_showpiece || holographic_showpiece}
onClick={() => act('change_message')}
/>
</div>
)}
{!!historian_mode && !!holographic_showpiece && (
<Box>
A holographic trophy is already present. Replace it with a new trophy
to create a new recording.
</Box>
)}
{!!historian_mode && !has_showpiece && <Box>No trophies located.</Box>}
{!!historian_mode &&
!!has_showpiece &&
!holographic_showpiece &&
!!showpiece_description && (
<Box>
Recording has begun. Trophy data will be saved overnight, as long as
the trophy stays within an intact case.
</Box>
)}
{!!historian_mode &&
!!has_showpiece &&
!holographic_showpiece &&
!showpiece_description && (
<Box>
New trophy detected. Please record a description to begin archival.
</Box>
)}
</Section>
);
};
const ShowpieceDescription = (props, context) => {
const { act, data } = useBackend(context);
const {
has_showpiece,
holographic_showpiece,
historian_mode,
max_length,
showpiece_description,
} = data;
return (
<Section fill align="center">
{!has_showpiece && (
<Box fill className="Trophycase-description">
<b>This exhibit is empty. History awaits your contribution!</b>
</Box>
)}
{!!holographic_showpiece && <b>{showpiece_description}</b>}
{!holographic_showpiece && !!has_showpiece && (
<Box fill className="Trophycase-description">
{showpiece_description
? decodeHtmlEntities(showpiece_description)
: "This exhibit is under construction. Get the curator's key to finalize your contribution!"}
</Box>
)}
</Section>
);
};
const ShowpieceImage = (props, context) => {
const { data } = useBackend(context);
const { showpiece_icon } = data;
return showpiece_icon ? (
<Section align="center">
<Box
as="img"
m={1}
src={`data:image/jpeg;base64,${showpiece_icon}`}
height="96px"
width="96px"
style={{
'-ms-interpolation-mode': 'nearest-neighbor',
}}
/>
</Section>
) : (
<Section align="center">
<Box height="96px" width="96px">
<Dimmer fontSize="32px">
<Icon name="landmark" />
</Dimmer>
</Box>
</Section>
);
};
const ShowpieceName = (props, context) => {
const { data } = useBackend(context);
const { showpiece_name } = data;
return (
<Section align="center">
<b>
{showpiece_name
? decodeHtmlEntities(showpiece_name)
: 'Under construction.'}
</b>
</Section>
);
};
@@ -0,0 +1,5 @@
.Trophycase-description {
word-break: break-word;
word-wrap: break-word;
font-style: italic;
}
+1
View File
@@ -67,6 +67,7 @@
@include meta.load-css('./interfaces/Safe.scss');
@include meta.load-css('./interfaces/TachyonArray.scss');
@include meta.load-css('./interfaces/Techweb.scss');
@include meta.load-css('./interfaces/Trophycase.scss');
@include meta.load-css('./interfaces/RequestManager.scss');
@include meta.load-css('./interfaces/UtilityModulesPane.scss');
@include meta.load-css('./interfaces/Fabricator.scss');