feat: 💄 aquabox on profiles + avatar fixes

pull/99/head
Raymond 2025-01-03 09:19:21 -05:00
parent 9ba7f5022e
commit 08af00da29
7 changed files with 130 additions and 26 deletions

View File

@ -99,6 +99,7 @@
let USERBOX_SETUP_TEXT = t("userbox.new.setup"); let USERBOX_SETUP_TEXT = t("userbox.new.setup");
let USERBOX_ENABLED = useLocalStorage("userboxNew", false); let USERBOX_ENABLED = useLocalStorage("userboxNew", false);
let USERBOX_PROFILE_ENABLED = useLocalStorage("userboxNewProfile", false);
let USERBOX_INSTALLED = false; let USERBOX_INSTALLED = false;
let USERBOX_SUPPORT = "webkitGetAsEntry" in DataTransferItem.prototype; let USERBOX_SUPPORT = "webkitGetAsEntry" in DataTransferItem.prototype;
@ -155,9 +156,9 @@
</div> </div>
{:else} {:else}
<div class="chuni-userbox-container"> <div class="chuni-userbox-container">
<ChuniUserplateComponent on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level} chuniRating={userbox.playerRating / 100} <ChuniUserplateComponent on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level.toString()} chuniRating={userbox.playerRating / 100}
chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent> chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent>
<ChuniPenguinComponent classPassthrough="chuni-penguin-float" chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack} <ChuniPenguinComponent chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem} chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem}
chuniSkin={userbox.avatarSkin}></ChuniPenguinComponent> chuniSkin={userbox.avatarSkin}></ChuniPenguinComponent>
</div> </div>
@ -219,6 +220,13 @@
<span class="desc">{t(`userbox.new.activate_desc`)}</span> <span class="desc">{t(`userbox.new.activate_desc`)}</span>
</label> </label>
</div> </div>
<div class="field boolean" style:margin-top="1em">
<input type="checkbox" bind:checked={USERBOX_PROFILE_ENABLED.value} id="newUserboxProfile">
<label for="newUserboxProfile">
<span class="name">{t("userbox.new.activate_profile")}</span>
<span class="desc">{t(`userbox.new.activate_profile_desc`)}</span>
</label>
</div>
{/if} {/if}
{#if USERBOX_SUPPORT} {#if USERBOX_SUPPORT}
<p> <p>

View File

@ -29,10 +29,10 @@
{/await} {/await}
<!-- Arms (surfboard) --> <!-- Arms (surfboard) -->
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 0, 0, 85, 160, 0.75) then imageURL} {#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 80, 0, 110, 100, 0.75) then imageURL}
<img class="chuni-penguin-arm-left chuni-penguin-arm" src={imageURL} alt="Left Arm"> <img class="chuni-penguin-arm-left chuni-penguin-arm" src={imageURL} alt="Left Arm">
{/await} {/await}
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 0, 0, 85, 160, 0.75) then imageURL} {#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 80, 0, 110, 100, 0.75) then imageURL}
<img class="chuni-penguin-arm-right chuni-penguin-arm" src={imageURL} alt="Right Arm"> <img class="chuni-penguin-arm-right chuni-penguin-arm" src={imageURL} alt="Right Arm">
{/await} {/await}
@ -77,8 +77,11 @@
</div> </div>
<div class="chuni-penguin-feet"> <div class="chuni-penguin-feet">
<!-- Feet --> <!-- Feet -->
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 0, 410, 167, 80, 0.75) then imageURL} {#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 0, 410, 85, 80, 0.75) then imageURL}
<img src={imageURL} alt="Feet"> <img src={imageURL} alt="Foot">
{/await}
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 85, 410, 85, 80, 0.75) then imageURL}
<img src={imageURL} alt="Foot">
{/await} {/await}
</div> </div>
</div> </div>
@ -88,7 +91,7 @@
0% 0%
transform: translate(-50%, 0%) translate(0%, -50%) transform: translate(-50%, 0%) translate(0%, -50%)
50% 50%
transform: translate(-50%, 10px) translate(0%, -50%) transform: translate(-50%, 5px) translate(0%, -50%)
100% 100%
transform: translate(-50%, 0%) translate(0%, -50%) transform: translate(-50%, 0%) translate(0%, -50%)
@keyframes chuniPenguinArmLeft @keyframes chuniPenguinArmLeft
@ -108,11 +111,19 @@
img img
-webkit-user-drag: none -webkit-user-drag: none
user-select: none
.chuni-penguin .chuni-penguin
height: 512px height: 512px
aspect-ratio: 1/2 aspect-ratio: 1/2
position: relative position: relative
pointer-events: none
&.chuni-penguin-float
position: absolute
top: 50%
left: 50%
transform: translate(-50%, -50%)
.chuni-penguin-body, .chuni-penguin-feet .chuni-penguin-body, .chuni-penguin-feet
transform: translate(-50%, -50%) transform: translate(-50%, -50%)
@ -122,21 +133,31 @@
.chuni-penguin-body .chuni-penguin-body
top: 50% top: 50%
z-index: 1 z-index: 1
animation: chuniPenguinBodyBob 2s infinite cubic-bezier(0.45, 0, 0.55, 1) animation: chuniPenguinBodyBob 1s infinite cubic-bezier(0.45, 0, 0.55, 1)
.chuni-penguin-feet .chuni-penguin-feet
top: 82.5% top: 80%
z-index: 0 z-index: 0
width: 175px
display: flex
justify-content: center
img
margin-left: auto
margin-right: auto
.chuni-penguin-arm .chuni-penguin-arm
transform-origin: 95% 10% transform-origin: 40% 10%
position: absolute position: absolute
top: 40% top: 40%
z-index: 2
.chuni-penguin-arm-left .chuni-penguin-arm-left
left: 0% left: 15%
animation: chuniPenguinArmLeft 1.5s infinite cubic-bezier(0.45, 0, 0.55, 1) transform: translate(-50%, 0)
animation: chuniPenguinArmLeft 1s infinite cubic-bezier(0.45, 0, 0.55, 1) 0.5s
.chuni-penguin-arm-right .chuni-penguin-arm-right
left: 70% left: 95%
animation: chuniPenguinArmRight 1.5s infinite cubic-bezier(0.45, 0, 0.55, 1) transform: translate(-50%, 0) scaleX(-1)
animation: chuniPenguinArmRight 1s infinite cubic-bezier(0.45, 0, 0.55, 1) 0.5s
.chuni-penguin-accessory .chuni-penguin-accessory
transform: translate(-50%, -50%) transform: translate(-50%, -50%)
@ -149,7 +170,7 @@
.chuni-penguin-beak .chuni-penguin-beak
top: 29.5% top: 29.5%
.chuni-penguin-wear .chuni-penguin-wear
top: 57.5% top: 60%
.chuni-penguin-head .chuni-penguin-head
top: 7.5% top: 7.5%
z-index: 10 z-index: 10

View File

@ -4,25 +4,25 @@
const DDSreader = new DDS(ddsDB); const DDSreader = new DDS(ddsDB);
export var chuniLevel: number = 1 export var chuniLevel: string = ""
export var chuniName: string = "AquaDX" export var chuniName: string = "AquaDX"
export var chuniRating: number = 1.23 export var chuniRating: number = 1.23
export var chuniNameplate: number = 1 export var chuniNameplate: number = 1
export var chuniCharacter: number = 0 export var chuniCharacter: number = 0
export var chuniTrophyName: string = "NEWCOMER" export var chuniTrophyName: string = "NEWCOMER"
</script> </script>
{#await DDSreader?.getFile(`nameplate:${chuniNameplate.toString().padStart(8, "0")}`) then nameplateURL} {#await DDSreader?.getFile(`nameplate:${chuniNameplate.toString().padStart(8, "0")}`, `nameplate:00000001`) then nameplateURL}
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:click class="chuni-nameplate" style:background={`url(${nameplateURL})`}> <div on:click class="chuni-nameplate" style:background={`url(${nameplateURL})`}>
{#await DDSreader?.getFile(`characterThumbnail:${chuniCharacter.toString().padStart(6, "0")}`) then characterThumbnailURL} {#await DDSreader?.getFile(`characterThumbnail:${chuniCharacter.toString().padStart(6, "0")}`, `characterThumbnail:000000`) then characterThumbnailURL}
<img class="chuni-character" src={characterThumbnailURL} alt="Character"> <img class="chuni-character" src={characterThumbnailURL} alt="Character">
{/await} {/await}
{#await DDSreader?.getFileFromSheet("surfboard:CHU_UI_title_rank_00_v10.dds", 5, 5 + (75 * 2), 595, 64) then trophyURL} {#await DDSreader?.getFileFromSheet("surfboard:CHU_UI_title_rank_00_v10.dds", 5, 5 + (75 * 2), 595, 64) then trophyURL}
<div class="chuni-trophy"> <div class="chuni-trophy">
{chuniTrophyName} {chuniTrophyName}
<img src={trophyURL} class="chuni-trophy-bg" alt="Trophy">
</div> </div>
<img src={trophyURL} class="chuni-trophy-bg" alt="Trophy">
{/await} {/await}
<div class="chuni-user-info"> <div class="chuni-user-info">
<div class="chuni-user-name"> <div class="chuni-user-name">
@ -57,7 +57,7 @@
cursor: pointer cursor: pointer
.chuni-trophy .chuni-trophy
width: 410px width: 390px
height: 45px height: 45px
background-position: center background-position: center
background-size: cover background-size: cover
@ -74,14 +74,21 @@
font-family: sans-serif font-family: sans-serif
font-weight: bold font-weight: bold
overflow-x: hidden
white-space: nowrap
text-overflow: ellipsis
z-index: 1 z-index: 1
text-shadow: 0 1px white text-shadow: 0 1px white
margin: 0 10px
img img.chuni-trophy-bg
width: 100% width: 410px
height: 100% height: 45px
position: absolute position: absolute
z-index: -1 top: 40px
right: 25px
z-index: -1
.chuni-character .chuni-character
position: absolute position: absolute
@ -115,9 +122,11 @@
.chuni-user-name .chuni-user-name
flex: 1 0 65% flex: 1 0 65%
box-shadow: 0 1px 0 #ccc box-shadow: 0 1px 0 #ccc
white-space: nowrap
text-overflow: ellipsis
.chuni-user-level .chuni-user-level
font-size: 2em font-size: 1.5em
margin-left: 10px margin-left: 10px
.chuni-user-name-text .chuni-user-name-text

View File

@ -189,6 +189,8 @@ export const EN_REF_USERBOX = {
'userbox.new.activate_update': 'Update AquaBox (game files required)', 'userbox.new.activate_update': 'Update AquaBox (game files required)',
'userbox.new.activate': 'Use AquaBox', 'userbox.new.activate': 'Use AquaBox',
'userbox.new.activate_desc': 'Enable displaying UserBoxes with their nameplate & avatar', 'userbox.new.activate_desc': 'Enable displaying UserBoxes with their nameplate & avatar',
'userbox.new.activate_profile': 'Use AquaBox on profiles',
'userbox.new.activate_profile_desc': 'Enable displaying UserBoxes with their nameplate & avatar on profile pages',
'userbox.new.error.invalidFolder': 'The folder you selected is invalid. Ensure that your game\'s version is Lumi or newer and that the "A001" option pack is present.' 'userbox.new.error.invalidFolder': 'The folder you selected is invalid. Ensure that your game\'s version is Lumi or newer and that the "A001" option pack is present.'
} }

View File

@ -266,6 +266,8 @@ export const USERBOX = {
get('/api/v2/game/chu3/user-box', {}), get('/api/v2/game/chu3/user-box', {}),
setUserBox: (d: { field: string, value: number | string }) => setUserBox: (d: { field: string, value: number | string }) =>
post(`/api/v2/game/chu3/user-detail-set`, d), post(`/api/v2/game/chu3/user-detail-set`, d),
getUserProfile: (username: string): Promise<UserBox> =>
get(`/api/v2/game/chu3/user-detail`, {username})
} }
export const CARD = { export const CARD = {

View File

@ -174,6 +174,7 @@ export async function userboxFileProcess(folder: FileSystemEntry, progressUpdate
if (dataFolder) if (dataFolder)
await scanOptionFolder(dataFolder, progressUpdate); await scanOptionFolder(dataFolder, progressUpdate);
useLocalStorage("userboxNew", false).value = true; useLocalStorage("userboxNew", false).value = true;
useLocalStorage("userboxNewProfile", false).value = true;
location.reload(); location.reload();
return null return null

View File

@ -105,6 +105,41 @@
d!.user.rival = isAdd d!.user.rival = isAdd
}).catch(e => error = e.message).finally(() => isLoading = false) }).catch(e => error = e.message).finally(() => isLoading = false)
} }
/* Aquabox */
import { userboxFileProcess, ddsDB, initializeDb } from "../libs/userbox/userbox"
import ChuniPenguinComponent from "../components/settings/userbox/ChuniPenguin.svelte"
import ChuniUserplateComponent from "../components/settings/userbox/ChuniUserplate.svelte";
import {
type UserBox,
type UserItem,
} from "../libs/generalTypes";
import { USERBOX } from "../libs/sdk";
let USERBOX_ACTIVE = useLocalStorage("userboxNewProfile", false);
let USERBOX_INSTALLED = false;
let userbox: UserBox;
let allItems: Record<string, Record<string, { name: string }>> = {};
if (game == "chu3" && USERBOX_ACTIVE.value) {
indexedDB.databases().then(async (dbi) => {
let databaseExists = dbi.some(db => db.name == "userboxChusanDDS");
if (databaseExists) {
await initializeDb();
const profile = await USERBOX.getUserProfile(username).catch(_ => null)
if (!profile) return;
userbox = profile;
console.log(userbox);
allItems = await DATA.allItems('chu3').catch(_ => {
error = t("userbox.error.nodata")
}) as typeof allItems
USERBOX_INSTALLED = databaseExists;
}
})
}
</script> </script>
<main id="user-home" class="content"> <main id="user-home" class="content">
@ -132,6 +167,18 @@
</nav> </nav>
</div> </div>
{#if USERBOX_ACTIVE.value && USERBOX_INSTALLED && game == "chu3"}
<div class="chuni-userbox-container">
<ChuniUserplateComponent chuniCharacter={userbox.characterId} chuniRating={d.user.rating / 100} chuniLevel={userbox.level.toString()}
chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent>
<div class="chuni-penguin-container">
<ChuniPenguinComponent classPassthrough="chuni-penguin-float" chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem}
chuniSkin={userbox.avatarSkin}></ChuniPenguinComponent>
</div>
</div>
{/if}
<div> <div>
<h2>{titleText} {t('UserHome.Statistics')}</h2> <h2>{titleText} {t('UserHome.Statistics')}</h2>
<div class="scoring-info"> <div class="scoring-info">
@ -576,4 +623,18 @@
&:before &:before
content: "+" content: "+"
color: vars.$c-good color: vars.$c-good
.chuni-userbox-container
display: flex
align-items: center
justify-content: center
.chuni-penguin-container
height: 256px
aspect-ratio: 1
position: relative
@media (max-width: 1000px)
.chuni-userbox-container
flex-wrap: wrap
</style> </style>