TGUI Outfit Manager and Editor (#58125)

Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
This commit is contained in:
Trigg
2021-04-11 14:47:03 -03:00
committed by GitHub
parent 48f9b2711b
commit 1542aa66bc
10 changed files with 603 additions and 261 deletions

View File

@@ -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"

View File

@@ -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))

View File

@@ -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)

View 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

View 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)

View File

@@ -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)
/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)
/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)

View File

@@ -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

View File

@@ -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"

View 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>
);
};

View 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>
);
};