mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 10:11:09 +00:00
TGUI Outfit Manager and Editor (#58125)
Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
This commit is contained in:
@@ -136,6 +136,9 @@
|
||||
// paintings
|
||||
#define VV_HK_REMOVE_PAINTING "remove_painting"
|
||||
|
||||
//outfits
|
||||
#define VV_HK_TO_OUTFIT_EDITOR "outfit_editor"
|
||||
|
||||
// /obj/effect/proc_holder/spell
|
||||
/// Require casting_clothes to cast spell.
|
||||
#define VV_HK_SPELL_SET_ROBELESS "spell_set_robeless"
|
||||
|
||||
@@ -33,6 +33,7 @@ SUBSYSTEM_DEF(persistence)
|
||||
LoadAntagReputation()
|
||||
LoadRandomizedRecipes()
|
||||
LoadPaintings()
|
||||
load_custom_outfits()
|
||||
|
||||
GLOB.explorer_drone_adventures = load_adventures()
|
||||
return ..()
|
||||
@@ -183,6 +184,7 @@ SUBSYSTEM_DEF(persistence)
|
||||
SaveRandomizedRecipes()
|
||||
SavePaintings()
|
||||
SaveScars()
|
||||
save_custom_outfits()
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/GetPhotoAlbums()
|
||||
var/album_path = file("data/photo_albums.json")
|
||||
@@ -411,3 +413,35 @@ SUBSYSTEM_DEF(persistence)
|
||||
original_human.save_persistent_scars(TRUE)
|
||||
else
|
||||
original_human.save_persistent_scars()
|
||||
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/load_custom_outfits()
|
||||
var/file = file("data/custom_outfits.json")
|
||||
if(!fexists(file))
|
||||
return
|
||||
var/outfits_json = file2text(file)
|
||||
var/list/outfits = json_decode(outfits_json)
|
||||
if(!islist(outfits))
|
||||
return
|
||||
|
||||
for(var/outfit_data in outfits)
|
||||
if(!islist(outfit_data))
|
||||
continue
|
||||
|
||||
var/outfittype = text2path(outfit_data["outfit_type"])
|
||||
if(!ispath(outfittype, /datum/outfit))
|
||||
continue
|
||||
var/datum/outfit/outfit = new outfittype
|
||||
if(!outfit.load_from(outfit_data))
|
||||
continue
|
||||
GLOB.custom_outfits += outfit
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/save_custom_outfits()
|
||||
var/file = file("data/custom_outfits.json")
|
||||
fdel(file)
|
||||
|
||||
var/list/data = list()
|
||||
for(var/datum/outfit/outfit in GLOB.custom_outfits)
|
||||
data += list(outfit.get_json_data())
|
||||
|
||||
WRITE_FILE(file, json_encode(data))
|
||||
|
||||
@@ -273,42 +273,42 @@
|
||||
if(!istype(H))
|
||||
return
|
||||
if(H.back)
|
||||
H.back.add_fingerprint(H,1) //The 1 sets a flag to ignore gloves
|
||||
H.back.add_fingerprint(H, ignoregloves = TRUE)
|
||||
for(var/obj/item/I in H.back.contents)
|
||||
I.add_fingerprint(H,1)
|
||||
I.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.wear_id)
|
||||
H.wear_id.add_fingerprint(H,1)
|
||||
H.wear_id.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.w_uniform)
|
||||
H.w_uniform.add_fingerprint(H,1)
|
||||
H.w_uniform.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.wear_suit)
|
||||
H.wear_suit.add_fingerprint(H,1)
|
||||
H.wear_suit.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.wear_mask)
|
||||
H.wear_mask.add_fingerprint(H,1)
|
||||
H.wear_mask.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.wear_neck)
|
||||
H.wear_neck.add_fingerprint(H,1)
|
||||
H.wear_neck.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.head)
|
||||
H.head.add_fingerprint(H,1)
|
||||
H.head.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.shoes)
|
||||
H.shoes.add_fingerprint(H,1)
|
||||
H.shoes.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.gloves)
|
||||
H.gloves.add_fingerprint(H,1)
|
||||
H.gloves.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.ears)
|
||||
H.ears.add_fingerprint(H,1)
|
||||
H.ears.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.glasses)
|
||||
H.glasses.add_fingerprint(H,1)
|
||||
H.glasses.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.belt)
|
||||
H.belt.add_fingerprint(H,1)
|
||||
H.belt.add_fingerprint(H, ignoregloves = TRUE)
|
||||
for(var/obj/item/I in H.belt.contents)
|
||||
I.add_fingerprint(H,1)
|
||||
I.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.s_store)
|
||||
H.s_store.add_fingerprint(H,1)
|
||||
H.s_store.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.l_store)
|
||||
H.l_store.add_fingerprint(H,1)
|
||||
H.l_store.add_fingerprint(H, ignoregloves = TRUE)
|
||||
if(H.r_store)
|
||||
H.r_store.add_fingerprint(H,1)
|
||||
H.r_store.add_fingerprint(H, ignoregloves = TRUE)
|
||||
for(var/obj/item/I in H.held_items)
|
||||
I.add_fingerprint(H,1)
|
||||
return 1
|
||||
I.add_fingerprint(H, ignoregloves = TRUE)
|
||||
return TRUE
|
||||
|
||||
/// Return a list of all the types that are required to disguise as this outfit type
|
||||
/datum/outfit/proc/get_chameleon_disguise_info()
|
||||
@@ -347,6 +347,33 @@
|
||||
.["implants"] = implants
|
||||
.["accessory"] = accessory
|
||||
|
||||
/// Copy most vars from another outfit to this one
|
||||
/datum/outfit/proc/copy_from(datum/outfit/target)
|
||||
name = target.name
|
||||
uniform = target.uniform
|
||||
suit = target.suit
|
||||
toggle_helmet = target.toggle_helmet
|
||||
back = target.back
|
||||
belt = target.belt
|
||||
gloves = target.gloves
|
||||
shoes = target.shoes
|
||||
head = target.head
|
||||
mask = target.mask
|
||||
neck = target.neck
|
||||
ears = target.ears
|
||||
glasses = target.glasses
|
||||
id = target.id
|
||||
l_pocket = target.l_pocket
|
||||
r_pocket = target.r_pocket
|
||||
suit_store = target.suit_store
|
||||
r_hand = target.r_hand
|
||||
l_hand = target.l_hand
|
||||
internals_slot = target.internals_slot
|
||||
backpack_contents = target.backpack_contents
|
||||
box = target.box
|
||||
implants = target.implants
|
||||
accessory = target.accessory
|
||||
|
||||
/// Prompt the passed in mob client to download this outfit as a json blob
|
||||
/datum/outfit/proc/save_to_file(mob/admin)
|
||||
var/stored_data = get_json_data()
|
||||
@@ -395,3 +422,13 @@
|
||||
implants += imptype
|
||||
accessory = text2path(outfit_data["accessory"])
|
||||
return TRUE
|
||||
|
||||
/datum/outfit/vv_get_dropdown()
|
||||
. = ..()
|
||||
VV_DROPDOWN_OPTION("", "---")
|
||||
VV_DROPDOWN_OPTION(VV_HK_TO_OUTFIT_EDITOR, "Outfit Editor")
|
||||
|
||||
/datum/outfit/vv_do_topic(list/href_list)
|
||||
. = ..()
|
||||
if(href_list[VV_HK_TO_OUTFIT_EDITOR])
|
||||
usr.client.open_outfit_editor(src)
|
||||
|
||||
201
code/modules/admin/outfit_editor.dm
Normal file
201
code/modules/admin/outfit_editor.dm
Normal file
@@ -0,0 +1,201 @@
|
||||
|
||||
/client/proc/open_outfit_editor(datum/outfit/target)
|
||||
var/datum/outfit_editor/ui = new(usr, target)
|
||||
ui.ui_interact(usr)
|
||||
|
||||
#define OUTFIT_EDITOR_NAME "Outfit-O-Tron 9000"
|
||||
/datum/outfit_editor
|
||||
var/client/owner
|
||||
|
||||
var/dummy_key
|
||||
|
||||
var/datum/outfit/drip
|
||||
|
||||
/datum/outfit_editor/New(user, datum/outfit/target)
|
||||
owner = CLIENT_FROM_VAR(user)
|
||||
|
||||
if(ispath(target))
|
||||
drip = new /datum/outfit
|
||||
drip.copy_from(new target)
|
||||
else if(istype(target))
|
||||
drip = target
|
||||
else
|
||||
drip = new /datum/outfit
|
||||
drip.name = "New Outfit"
|
||||
|
||||
/datum/outfit_editor/ui_state(mob/user)
|
||||
return GLOB.admin_state
|
||||
|
||||
/datum/outfit_editor/ui_status(mob/user, datum/ui_state/state)
|
||||
if(QDELETED(drip))
|
||||
return UI_CLOSE
|
||||
return ..()
|
||||
|
||||
/datum/outfit_editor/ui_close(mob/user)
|
||||
clear_human_dummy(dummy_key)
|
||||
qdel(src)
|
||||
|
||||
/datum/outfit_editor/proc/init_dummy()
|
||||
dummy_key = "outfit_editor_[owner]"
|
||||
var/mob/living/carbon/human/dummy/dummy = generate_or_wait_for_human_dummy(dummy_key)
|
||||
var/mob/living/carbon/carbon_target = owner.mob
|
||||
if(istype(carbon_target))
|
||||
carbon_target.dna.transfer_identity(dummy)
|
||||
dummy.updateappearance()
|
||||
|
||||
unset_busy_human_dummy(dummy_key)
|
||||
|
||||
/datum/outfit_editor/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "OutfitEditor", OUTFIT_EDITOR_NAME)
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE)
|
||||
|
||||
/datum/outfit_editor/proc/entry(data)
|
||||
if(ispath(data, /obj/item))
|
||||
var/obj/item/item = data
|
||||
return list(
|
||||
"path" = item,
|
||||
"name" = initial(item.name),
|
||||
"desc" = initial(item.desc),
|
||||
// at this point initializing the item is probably faster tbh
|
||||
"sprite" = icon2base64(icon(initial(item.icon), initial(item.icon_state))),
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
/datum/outfit_editor/proc/serialize_outfit()
|
||||
var/list/outfit_slots = drip.get_json_data()
|
||||
. = list()
|
||||
for(var/key in outfit_slots)
|
||||
var/val = outfit_slots[key]
|
||||
. += list("[key]" = entry(val))
|
||||
|
||||
/datum/outfit_editor/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["outfit"] = serialize_outfit()
|
||||
data["saveable"] = !GLOB.custom_outfits.Find(drip)
|
||||
|
||||
var/datum/preferences/prefs = owner.prefs
|
||||
var/icon/dummysprite = get_flat_human_icon(null,
|
||||
prefs = prefs,
|
||||
dummy_key = dummy_key,
|
||||
showDirs = list(SOUTH),
|
||||
outfit_override = drip)
|
||||
data["dummy64"] = icon2base64(dummysprite)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
/datum/outfit_editor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
if(..())
|
||||
return
|
||||
. = TRUE
|
||||
|
||||
var/slot = params["slot"]
|
||||
switch(action)
|
||||
if("click")
|
||||
choose_item(slot)
|
||||
if("ctrlClick")
|
||||
choose_any_item(slot)
|
||||
if("clear")
|
||||
if(drip.vars.Find(slot))
|
||||
drip.vars[slot] = null
|
||||
|
||||
if("rename")
|
||||
var/newname = stripped_input(owner, "What do you want to name this outfit?", OUTFIT_EDITOR_NAME)
|
||||
if(newname)
|
||||
drip.name = newname
|
||||
if("save")
|
||||
GLOB.custom_outfits |= drip
|
||||
SStgui.update_user_uis(owner.mob)
|
||||
if("delete")
|
||||
GLOB.custom_outfits -= drip
|
||||
SStgui.update_user_uis(owner.mob)
|
||||
if("vv")
|
||||
owner.debug_variables(drip)
|
||||
|
||||
|
||||
/datum/outfit_editor/proc/set_item(slot, obj/item/choice)
|
||||
if(!choice)
|
||||
return
|
||||
if(!ispath(choice))
|
||||
alert(owner, "Invalid item", OUTFIT_EDITOR_NAME, "oh no")
|
||||
return
|
||||
if(initial(choice.icon_state) == null) //hacky check copied from experimentor code
|
||||
var/msg = "Warning: This item's icon_state is null, indicating it is very probably not actually a usable item."
|
||||
if(alert(owner, msg, OUTFIT_EDITOR_NAME, "Use it anyway", "Cancel") != "Use it anyway")
|
||||
return
|
||||
|
||||
if(drip.vars.Find(slot))
|
||||
drip.vars[slot] = choice
|
||||
|
||||
/datum/outfit_editor/proc/choose_any_item(slot)
|
||||
var/obj/item/choice = pick_closest_path(FALSE)
|
||||
|
||||
if(!choice)
|
||||
return
|
||||
|
||||
set_item(slot, choice)
|
||||
|
||||
//this proc will try to give a good selection of items that the user can choose from
|
||||
//it does *not* give a selection of all items that can fit in a slot because lag;
|
||||
//most notably the hand and pocket slots because they accept pretty much anything
|
||||
//also stuff that fits in the belt and back slots are scattered pretty much all over the place
|
||||
/datum/outfit_editor/proc/choose_item(slot)
|
||||
var/list/options = list()
|
||||
|
||||
switch(slot)
|
||||
if("head")
|
||||
options = typesof(/obj/item/clothing/head)
|
||||
if("glasses")
|
||||
options = typesof(/obj/item/clothing/glasses)
|
||||
if("ears")
|
||||
options = typesof(/obj/item/radio/headset)
|
||||
|
||||
if("neck")
|
||||
options = typesof(/obj/item/clothing/neck)
|
||||
if("mask")
|
||||
options = typesof(/obj/item/clothing/mask)
|
||||
|
||||
if("uniform")
|
||||
options = typesof(/obj/item/clothing/under)
|
||||
if("suit")
|
||||
options = typesof(/obj/item/clothing/suit)
|
||||
if("gloves")
|
||||
options = typesof(/obj/item/clothing/gloves)
|
||||
|
||||
if("suit_store")
|
||||
var/obj/item/clothing/suit/suit = drip.suit
|
||||
if(suit)
|
||||
suit = new suit //initial() doesn't like lists
|
||||
options = suit.allowed
|
||||
if(!options.len) //nothing will happen, but don't let the user think it's broken
|
||||
to_chat(owner, "<span class='warning'>No options available for the current suit.</span>")
|
||||
|
||||
if("belt")
|
||||
options = typesof(/obj/item/storage/belt)
|
||||
if("id")
|
||||
options = typesof(/obj/item/card/id)
|
||||
|
||||
if("l_hand")
|
||||
choose_any_item(slot)
|
||||
if("back")
|
||||
options = typesof(/obj/item/storage/backpack)
|
||||
if("r_hand")
|
||||
choose_any_item(slot)
|
||||
|
||||
if("l_pocket")
|
||||
choose_any_item(slot)
|
||||
if("shoes")
|
||||
options = typesof(/obj/item/clothing/shoes)
|
||||
if("r_pocket")
|
||||
choose_any_item(slot)
|
||||
|
||||
if(length(options))
|
||||
set_item(slot, tgui_input_list(owner, "Choose an item", OUTFIT_EDITOR_NAME, options))
|
||||
|
||||
|
||||
#undef OUTFIT_EDITOR_NAME
|
||||
73
code/modules/admin/outfit_manager.dm
Normal file
73
code/modules/admin/outfit_manager.dm
Normal file
@@ -0,0 +1,73 @@
|
||||
/client/proc/outfit_manager()
|
||||
set category = "Debug"
|
||||
set name = "Outfit Manager"
|
||||
|
||||
if(!check_rights(R_DEBUG))
|
||||
return
|
||||
var/datum/outfit_manager/ui = new(usr)
|
||||
ui.ui_interact(usr)
|
||||
|
||||
|
||||
/datum/outfit_manager
|
||||
var/client/owner
|
||||
|
||||
/datum/outfit_manager/New(user)
|
||||
owner = CLIENT_FROM_VAR(user)
|
||||
|
||||
/datum/outfit_manager/ui_state(mob/user)
|
||||
return GLOB.admin_state
|
||||
|
||||
/datum/outfit_manager/ui_close(mob/user)
|
||||
qdel(src)
|
||||
|
||||
/datum/outfit_manager/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "OutfitManager")
|
||||
ui.open()
|
||||
|
||||
/datum/outfit_manager/proc/entry(datum/outfit/outfit)
|
||||
var/vv = FALSE
|
||||
var/datum/outfit/varedit/varoutfit = outfit
|
||||
if(istype(varoutfit))
|
||||
vv = length(varoutfit.vv_values)
|
||||
return list(
|
||||
"name" = "[outfit.name] [vv ? "(VV)" : ""]",
|
||||
"ref" = REF(outfit),
|
||||
)
|
||||
|
||||
/datum/outfit_manager/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/outfits = list()
|
||||
for(var/datum/outfit/custom_outfit in GLOB.custom_outfits)
|
||||
outfits += list(entry(custom_outfit))
|
||||
data["outfits"] = outfits
|
||||
|
||||
return data
|
||||
|
||||
/datum/outfit_manager/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
if(..())
|
||||
return
|
||||
. = TRUE
|
||||
|
||||
switch(action)
|
||||
if("new")
|
||||
owner.open_outfit_editor(new /datum/outfit)
|
||||
if("load")
|
||||
owner.holder.load_outfit(owner.mob)
|
||||
if("copy")
|
||||
var/datum/outfit/outfit = tgui_input_list(owner, "Pick an outfit to copy from", "Outfit Manager", subtypesof(/datum/outfit))
|
||||
if(ispath(outfit))
|
||||
owner.open_outfit_editor(new outfit)
|
||||
|
||||
var/datum/outfit/target_outfit = locate(params["outfit"])
|
||||
if(!istype(target_outfit))
|
||||
return
|
||||
switch(action) //wow we're switching through action again this is horrible optimization smh
|
||||
if("edit")
|
||||
owner.open_outfit_editor(target_outfit)
|
||||
if("save")
|
||||
owner.holder.save_outfit(owner.mob, target_outfit)
|
||||
if("delete")
|
||||
owner.holder.delete_outfit(owner.mob, target_outfit)
|
||||
@@ -1,35 +1,14 @@
|
||||
GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
|
||||
|
||||
/client/proc/outfit_manager()
|
||||
set category = "Debug"
|
||||
set name = "Outfit Manager"
|
||||
|
||||
if(!check_rights(R_DEBUG))
|
||||
return
|
||||
holder.outfit_manager(usr)
|
||||
|
||||
/datum/admins/proc/outfit_manager(mob/admin)
|
||||
var/list/dat = list("<ul>")
|
||||
for(var/datum/outfit/O in GLOB.custom_outfits)
|
||||
var/vv = FALSE
|
||||
var/datum/outfit/varedit/VO = O
|
||||
if(istype(VO))
|
||||
vv = length(VO.vv_values)
|
||||
dat += "<li>[O.name][vv ? "(VV)" : ""]</li> <a href='?_src_=holder;[HrefToken()];save_outfit=1;chosen_outfit=[REF(O)]'>Save</a> <a href='?_src_=holder;[HrefToken()];delete_outfit=1;chosen_outfit=[REF(O)]'>Delete</a>"
|
||||
dat += "</ul>"
|
||||
dat += "<a href='?_src_=holder;[HrefToken()];create_outfit_menu=1'>Create</a><br>"
|
||||
dat += "<a href='?_src_=holder;[HrefToken()];load_outfit=1'>Load from file</a>"
|
||||
admin << browse(dat.Join(),"window=outfitmanager")
|
||||
|
||||
/datum/admins/proc/save_outfit(mob/admin, datum/outfit/O)
|
||||
O.save_to_file(admin)
|
||||
outfit_manager(admin)
|
||||
SStgui.update_user_uis(admin)
|
||||
|
||||
/datum/admins/proc/delete_outfit(mob/admin, datum/outfit/O)
|
||||
GLOB.custom_outfits -= O
|
||||
qdel(O)
|
||||
to_chat(admin,"<span class='notice'>Outfit deleted.</span>", confidential = TRUE)
|
||||
outfit_manager(admin)
|
||||
to_chat(admin,"<span class='notice'>Outfit deleted.</span>")
|
||||
SStgui.update_user_uis(admin)
|
||||
|
||||
/datum/admins/proc/load_outfit(mob/admin)
|
||||
@@ -39,208 +18,16 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
|
||||
var/filedata = file2text(outfit_file)
|
||||
var/json = json_decode(filedata)
|
||||
if(!json)
|
||||
to_chat(admin,"<span class='warning'>JSON decode error.</span>", confidential = TRUE)
|
||||
to_chat(admin,"<span class='warning'>JSON decode error.</span>")
|
||||
return
|
||||
var/otype = text2path(json["outfit_type"])
|
||||
if(!ispath(otype,/datum/outfit))
|
||||
to_chat(admin,"<span class='warning'>Malformed/Outdated file.</span>", confidential = TRUE)
|
||||
to_chat(admin,"<span class='warning'>Malformed/Outdated file.</span>")
|
||||
return
|
||||
var/datum/outfit/O = new otype
|
||||
if(!O.load_from(json))
|
||||
to_chat(admin,"<span class='warning'>Malformed/Outdated file.</span>", confidential = TRUE)
|
||||
to_chat(admin,"<span class='warning'>Malformed/Outdated file.</span>")
|
||||
return
|
||||
GLOB.custom_outfits += O
|
||||
outfit_manager(admin)
|
||||
SStgui.update_user_uis(admin)
|
||||
|
||||
/datum/admins/proc/create_outfit(mob/admin)
|
||||
var/list/uniforms = typesof(/obj/item/clothing/under)
|
||||
var/list/suits = typesof(/obj/item/clothing/suit)
|
||||
var/list/gloves = typesof(/obj/item/clothing/gloves)
|
||||
var/list/shoes = typesof(/obj/item/clothing/shoes)
|
||||
var/list/headwear = typesof(/obj/item/clothing/head)
|
||||
var/list/glasses = typesof(/obj/item/clothing/glasses)
|
||||
var/list/masks = typesof(/obj/item/clothing/mask)
|
||||
var/list/ids = typesof(/obj/item/card/id)
|
||||
|
||||
var/uniform_select = "<select name=\"outfit_uniform\"><option value=\"\">None</option>"
|
||||
for(var/path in uniforms)
|
||||
uniform_select += "<option value=\"[path]\">[path]</option>"
|
||||
uniform_select += "</select>"
|
||||
|
||||
var/suit_select = "<select name=\"outfit_suit\"><option value=\"\">None</option>"
|
||||
for(var/path in suits)
|
||||
suit_select += "<option value=\"[path]\">[path]</option>"
|
||||
suit_select += "</select>"
|
||||
|
||||
var/gloves_select = "<select name=\"outfit_gloves\"><option value=\"\">None</option>"
|
||||
for(var/path in gloves)
|
||||
gloves_select += "<option value=\"[path]\">[path]</option>"
|
||||
gloves_select += "</select>"
|
||||
|
||||
var/shoes_select = "<select name=\"outfit_shoes\"><option value=\"\">None</option>"
|
||||
for(var/path in shoes)
|
||||
shoes_select += "<option value=\"[path]\">[path]</option>"
|
||||
shoes_select += "</select>"
|
||||
|
||||
var/head_select = "<select name=\"outfit_head\"><option value=\"\">None</option>"
|
||||
for(var/path in headwear)
|
||||
head_select += "<option value=\"[path]\">[path]</option>"
|
||||
head_select += "</select>"
|
||||
|
||||
var/glasses_select = "<select name=\"outfit_glasses\"><option value=\"\">None</option>"
|
||||
for(var/path in glasses)
|
||||
glasses_select += "<option value=\"[path]\">[path]</option>"
|
||||
glasses_select += "</select>"
|
||||
|
||||
var/mask_select = "<select name=\"outfit_mask\"><option value=\"\">None</option>"
|
||||
for(var/path in masks)
|
||||
mask_select += "<option value=\"[path]\">[path]</option>"
|
||||
mask_select += "</select>"
|
||||
|
||||
var/id_select = "<select name=\"outfit_id\"><option value=\"\">None</option>"
|
||||
for(var/path in ids)
|
||||
id_select += "<option value=\"[path]\">[path]</option>"
|
||||
id_select += "</select>"
|
||||
|
||||
var/dat = {"
|
||||
<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><title>Create Outfit</title></head><body>
|
||||
<form name="outfit" action="byond://?src=[REF(src)];[HrefToken()]" method="get">
|
||||
<input type="hidden" name="src" value="[REF(src)]">
|
||||
[HrefTokenFormField()]
|
||||
<input type="hidden" name="create_outfit_finalize" value="1">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_name" value="Custom Outfit">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Uniform:</th>
|
||||
<td>
|
||||
[uniform_select]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Suit:</th>
|
||||
<td>
|
||||
[suit_select]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Back:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_back" value="">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Belt:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_belt" value="">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Gloves:</th>
|
||||
<td>
|
||||
[gloves_select]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Shoes:</th>
|
||||
<td>
|
||||
[shoes_select]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Head:</th>
|
||||
<td>
|
||||
[head_select]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Mask:</th>
|
||||
<td>
|
||||
[mask_select]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Ears:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_ears" value="">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Glasses:</th>
|
||||
<td>
|
||||
[glasses_select]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID:</th>
|
||||
<td>
|
||||
[id_select]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Left Pocket:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_l_pocket" value="">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Right Pocket:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_r_pocket" value="">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Suit Store:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_s_store" value="">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Right Hand:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_r_hand" value="">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Left Hand:</th>
|
||||
<td>
|
||||
<input type="text" name="outfit_l_hand" value="">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<input type="submit" value="Save">
|
||||
</form></body></html>
|
||||
"}
|
||||
admin << browse(dat, "window=dressup;size=550x600")
|
||||
|
||||
|
||||
/datum/admins/proc/create_outfit_finalize(mob/admin, list/href_list)
|
||||
var/datum/outfit/O = new
|
||||
|
||||
O.name = href_list["outfit_name"]
|
||||
O.uniform = text2path(href_list["outfit_uniform"])
|
||||
O.shoes = text2path(href_list["outfit_shoes"])
|
||||
O.gloves = text2path(href_list["outfit_gloves"])
|
||||
O.suit = text2path(href_list["outfit_suit"])
|
||||
O.head = text2path(href_list["outfit_head"])
|
||||
O.back = text2path(href_list["outfit_back"])
|
||||
O.mask = text2path(href_list["outfit_mask"])
|
||||
O.glasses = text2path(href_list["outfit_glasses"])
|
||||
O.r_hand = text2path(href_list["outfit_r_hand"])
|
||||
O.l_hand = text2path(href_list["outfit_l_hand"])
|
||||
O.suit_store = text2path(href_list["outfit_s_store"])
|
||||
O.l_pocket = text2path(href_list["outfit_l_pocket"])
|
||||
O.r_pocket = text2path(href_list["outfit_r_pocket"])
|
||||
O.id = text2path(href_list["outfit_id"])
|
||||
O.belt = text2path(href_list["outfit_belt"])
|
||||
O.ears = text2path(href_list["outfit_ears"])
|
||||
|
||||
GLOB.custom_outfits.Add(O)
|
||||
message_admins("[key_name(usr)] created \"[O.name]\" outfit!")
|
||||
SStgui.update_user_uis(admin)
|
||||
|
||||
@@ -1851,28 +1851,6 @@
|
||||
else
|
||||
to_chat(usr, "You may only use this when the game is running.", confidential = TRUE)
|
||||
|
||||
else if(href_list["create_outfit_finalize"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
create_outfit_finalize(usr,href_list)
|
||||
else if(href_list["load_outfit"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
load_outfit(usr)
|
||||
else if(href_list["create_outfit_menu"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
create_outfit(usr)
|
||||
else if(href_list["delete_outfit"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits
|
||||
delete_outfit(usr,O)
|
||||
else if(href_list["save_outfit"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits
|
||||
save_outfit(usr,O)
|
||||
else if(href_list["set_selfdestruct_code"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
@@ -1495,6 +1495,8 @@
|
||||
#include "code\modules\admin\holder2.dm"
|
||||
#include "code\modules\admin\ipintel.dm"
|
||||
#include "code\modules\admin\IsBanned.dm"
|
||||
#include "code\modules\admin\outfit_editor.dm"
|
||||
#include "code\modules\admin\outfit_manager.dm"
|
||||
#include "code\modules\admin\outfits.dm"
|
||||
#include "code\modules\admin\permissionedit.dm"
|
||||
#include "code\modules\admin\player_panel.dm"
|
||||
|
||||
146
tgui/packages/tgui/interfaces/OutfitEditor.js
Normal file
146
tgui/packages/tgui/interfaces/OutfitEditor.js
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Icon, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const OutfitEditor = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { outfit, saveable, dummy64 } = data;
|
||||
return (
|
||||
<Window
|
||||
width={380}
|
||||
height={600}>
|
||||
<Window.Content>
|
||||
<Box
|
||||
as="img"
|
||||
fillPositionedParent
|
||||
width="100%"
|
||||
height="100%"
|
||||
opacity={0.5}
|
||||
py={3}
|
||||
src={`data:image/jpeg;base64,${dummy64}`}
|
||||
style={{
|
||||
'-ms-interpolation-mode': 'nearest-neighbor',
|
||||
}} />
|
||||
<Section
|
||||
fill
|
||||
title={
|
||||
<Stack>
|
||||
<Stack.Item grow={1}
|
||||
style={{
|
||||
'overflow': 'hidden',
|
||||
'white-space': 'nowrap',
|
||||
'text-overflow': 'ellipsis',
|
||||
}}>
|
||||
<Button
|
||||
ml={0.5}
|
||||
color="transparent"
|
||||
icon="pencil-alt"
|
||||
title="Rename this outfit"
|
||||
onClick={() => act("rename", {})} />
|
||||
{outfit.name}
|
||||
</Stack.Item>
|
||||
<Stack.Item align="end" shrink={0}>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="info"
|
||||
tooltip="Ctrl-click a button to select *any* item instead of what will probably fit in that slot."
|
||||
tooltipPosition="bottom-left" />
|
||||
<Button
|
||||
icon="code"
|
||||
tooltip="Edit this outfit on a VV window"
|
||||
tooltipPosition="bottom-left"
|
||||
onClick={() => act("vv")} />
|
||||
<Button
|
||||
color={!saveable && "bad"}
|
||||
icon={saveable ? "save" : "trash-alt"}
|
||||
tooltip={saveable
|
||||
? "Save this outfit to the custom outfit list"
|
||||
: "Remove this outfit from the custom outfit list"}
|
||||
tooltipPosition="bottom-left"
|
||||
onClick={() => act(saveable ? "save" : "delete")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}>
|
||||
<Box textAlign="center">
|
||||
<Stack mb={2}>
|
||||
<OutfitSlot name="Headgear" icon="hard-hat" slot="head" />
|
||||
<OutfitSlot name="Glasses" icon="glasses" slot="glasses" />
|
||||
<OutfitSlot name="Ears" icon="headphones-alt" slot="ears" />
|
||||
</Stack>
|
||||
<Stack mb={2}>
|
||||
<OutfitSlot name="Neck" icon="stethoscope" slot="neck" />
|
||||
<OutfitSlot name="Mask" icon="theater-masks" slot="mask" />
|
||||
</Stack>
|
||||
<Stack mb={2}>
|
||||
<OutfitSlot name="Uniform" icon="tshirt" slot="uniform" />
|
||||
<OutfitSlot name="Suit" icon="user-tie" slot="suit" />
|
||||
<OutfitSlot name="Gloves" icon="mitten" slot="gloves" />
|
||||
</Stack>
|
||||
<Stack mb={2}>
|
||||
<OutfitSlot name="Suit Storage" icon="briefcase-medical" slot="suit_store" />
|
||||
<OutfitSlot name="Back" icon="shopping-bag" slot="back" />
|
||||
<OutfitSlot name="ID" icon="id-card-o" slot="id" />
|
||||
</Stack>
|
||||
<Stack mb={2}>
|
||||
<OutfitSlot name="Belt" icon="band-aid" slot="belt" />
|
||||
<OutfitSlot name="Left Hand" icon="hand-paper" slot="l_hand" />
|
||||
<OutfitSlot name="Right Hand" icon="hand-paper" slot="r_hand" />
|
||||
</Stack>
|
||||
<Stack mb={2}>
|
||||
<OutfitSlot name="Shoes" icon="socks" slot="shoes" />
|
||||
<OutfitSlot name="Left Pocket" icon="envelope-open-o" iconRot={180} slot="l_pocket" />
|
||||
<OutfitSlot name="Right Pocket" icon="envelope-open-o" iconRot={180} slot="r_pocket" />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const OutfitSlot = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { name, icon, iconRot, slot } = props;
|
||||
const { outfit } = data;
|
||||
const currItem = outfit[slot];
|
||||
return (
|
||||
<Stack.Item grow={1} basis={0}>
|
||||
<Button fluid height={2}
|
||||
bold
|
||||
// todo: intuitive way to clear items
|
||||
onClick={e => act(e.ctrlKey ? "ctrlClick" : "click", { slot })} >
|
||||
<Icon name={icon} rotation={iconRot} />
|
||||
{name}
|
||||
</Button>
|
||||
<Box height="32px">
|
||||
{currItem?.sprite && (
|
||||
<>
|
||||
<Box
|
||||
as="img"
|
||||
src={`data:image/jpeg;base64,${currItem?.sprite}`}
|
||||
title={currItem?.desc}
|
||||
style={{
|
||||
'-ms-interpolation-mode': 'nearest-neighbor',
|
||||
}} />
|
||||
<Icon
|
||||
position="absolute"
|
||||
name="times"
|
||||
color="label"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => act("clear", { slot })} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
color="label"
|
||||
style={{
|
||||
'overflow': 'hidden',
|
||||
'white-space': 'nowrap',
|
||||
'text-overflow': 'ellipsis',
|
||||
}}
|
||||
title={currItem?.path}>
|
||||
{currItem?.name || "Empty"}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
);
|
||||
};
|
||||
81
tgui/packages/tgui/interfaces/OutfitManager.js
Normal file
81
tgui/packages/tgui/interfaces/OutfitManager.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const OutfitManager = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { outfits } = data;
|
||||
return (
|
||||
<Window
|
||||
title="Outfit Manager"
|
||||
width={300}
|
||||
height={300}>
|
||||
<Window.Content>
|
||||
<Section
|
||||
fill
|
||||
scrollable
|
||||
title="Custom Outfit Manager"
|
||||
buttons={
|
||||
<>
|
||||
<Button
|
||||
icon="file-upload"
|
||||
tooltip="Load an outfit from a file"
|
||||
tooltipPosition="left"
|
||||
onClick={() => act("load")} />
|
||||
<Button
|
||||
icon="copy"
|
||||
tooltip="Copy an already existing outfit"
|
||||
tooltipPosition="left"
|
||||
onClick={() => act("copy")} />
|
||||
<Button
|
||||
icon="plus"
|
||||
tooltip="Create a new outfit"
|
||||
tooltipPosition="left"
|
||||
onClick={() => act("new")} />
|
||||
</>
|
||||
}>
|
||||
<Stack vertical>
|
||||
{outfits?.map(outfit => (
|
||||
<Stack.Item key={outfit.ref}>
|
||||
<Stack>
|
||||
<Stack.Item grow={1} shrink={1}
|
||||
style={{
|
||||
'overflow': 'hidden',
|
||||
'white-space': 'nowrap',
|
||||
'text-overflow': 'ellipsis',
|
||||
}}>
|
||||
<Button
|
||||
fluid
|
||||
style={{
|
||||
'overflow': 'hidden',
|
||||
'white-space': 'nowrap',
|
||||
'text-overflow': 'ellipsis',
|
||||
}}
|
||||
content={outfit.name}
|
||||
onClick={() => act("edit", { outfit: outfit.ref })} />
|
||||
</Stack.Item>
|
||||
<Stack.Item ml={0.5}>
|
||||
<Button
|
||||
icon="save"
|
||||
tooltip="Save this outfit to a file"
|
||||
tooltipPosition="left"
|
||||
onClick={() => act("save", { outfit: outfit.ref })} />
|
||||
</Stack.Item>
|
||||
<Stack.Item ml={0.5}>
|
||||
<Button
|
||||
color="bad"
|
||||
icon="trash-alt"
|
||||
tooltip="Delete this outfit"
|
||||
tooltipPosition="left"
|
||||
onClick={() => act("delete", { outfit: outfit.ref })} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user