[chu3] X-VERSE & LINKED VERSE (#204)

This commit is contained in:
Raymond
2026-02-22 15:23:21 -05:00
committed by GitHub
parent e755bc9380
commit ebb2e2e4a4
17 changed files with 285 additions and 69 deletions
@@ -3,9 +3,10 @@
import { CHU3_MATCHINGS } from "../../libs/config.js";
import type { ChusanMatchingOption, GameOption } from "../../libs/generalTypes.js";
import { t, ts } from "../../libs/i18n.js";
import { DATA, SETTING } from "../../libs/sdk.js";
import { DATA, GAME, SETTING, USER } from "../../libs/sdk.js";
import StatusOverlays from "../StatusOverlays.svelte";
import GameSettingFields from "./GameSettingFields.svelte";
import { EN_REF } from "../../libs/i18n/en_ref.js";
let custom = false
let overlay = false
@@ -16,6 +17,7 @@
let symbols: Record<number, number> = {};
let allItems: Record<string, Record<string, { name: string }>> = {}
let submitting: string | undefined | null;
let settings: Record<string, GameOption> = {};
let existingUrl = "";
SETTING.get().then(s => {
@@ -30,6 +32,7 @@
if (opt.key.substring(0, symbolKey.length) == symbolKey && opt.value)
symbols[parseInt(opt.key.substring(symbolKey.length))] = opt.value;
})
settings = Object.fromEntries(s.map(v => [v.key, v]))
})
async function fetchSymbolData() {
@@ -49,6 +52,14 @@
return true
}
async function updateLvDifficulty() {
if (submitting) return false
submitting = `chusanLvDifficulty`
await SETTING.set("chusanLvDifficulty", settings["chusanLvDifficulty"].value).catch(e => error = e.message).finally(() => submitting = null);
changed = changed.filter(v => v != `chusanLvDifficulty`)
return true
}
// Click on "Custom" option"
function clickCustom() {
custom = true
@@ -66,13 +77,47 @@
existingUrl = opt.matching
}).catch(e => error = e.message)
}
let linkedVerseAvailable = false;
USER.me().then(async me => {
let summary = await GAME.userSummary(me.username, "chu3")
let version = summary.lastVersion.split(".");
if (version[0] == "2" && parseInt(version[1]) >= 40)
linkedVerseAvailable = true;
})
</script>
<StatusOverlays {error} {loading}/>
<div class="matching">
<h2>{t("userbox.header.matching")}</h2>
<p class="notice">{t("settings.cabNotice")}</p>
<h2>{t("userbox.header.matching")}{linkedVerseAvailable ? ` & ${t("userbox.header.linkedVerse")}` : ""}</h2>
<blockquote class="info">
{t("settings.cabNotice")}
{#if linkedVerseAvailable}
{t("userbox.lv.diffnotice")}
{/if}
</blockquote>
{#if linkedVerseAvailable}
<GameSettingFields game="chu3-linked-verse" />
{#if settings["chusanLvDifficulty"]}
<div class="field">
<label for={`chusanLvDifficulty`}>{ts(`userbox.lv.difficulty`)}</label>
<div>
<select bind:value={settings["chusanLvDifficulty"].value} id={`chusanLvDifficulty`} on:change={() => {changed = [...changed, `chusanLvDifficulty`];}}>
{#each {length: 5}, i}
<option value={i + 1}>{t(`userbox.lv.difficulty.${i + 1}` as keyof typeof EN_REF)}</option>
{/each}
</select>
{#if changed.includes(`chusanLvDifficulty`)}
<button transition:slide={{axis: "x"}} disabled={!!submitting} on:click={updateLvDifficulty}>
{t("settings.profile.save")}
</button>
{/if}
</div>
</div>
{/if}
{/if}
<div class="matching-selector">
<button on:click={_ => overlay = true}>{t('userbox.matching.select')}</button>
@@ -31,7 +31,7 @@
// Available (unlocked) options for each kind of item
// In allItems: 'namePlate', 'frame', 'trophy', 'mapIcon', 'systemVoice', 'avatarAccessory'
let allItems: Record<string, Record<string, { name: string }>> = {}
let iKinds = { namePlate: 1, frame: 2, trophy: 3, trophySub1: 4, trophySub2: 5, mapIcon: 8, systemVoice: 9, avatarAccessory: 11 }
let iKinds = { namePlate: 1, frame: 2, trophy: 3, trophySub1: 4, trophySub2: 5, mapIcon: 8, systemVoice: 9, avatarAccessory: 11, stage: 13 }
// In userbox: 'nameplateId', 'frameId', 'trophyId', 'mapIconId', 'voiceId', 'avatar{Wear/Head/Face/Skin/Item/Front/Back}'
let userbox: UserBox
let avatarKinds = ['Wear', 'Head', 'Face', 'Skin', 'Item', 'Front', 'Back'] as const
@@ -256,7 +256,7 @@
type OnlyNumberPropsOf<T extends Record<string, any>> = {[Prop in keyof T as (T[Prop] extends number ? Prop : never)]: T[Prop]}
let userboxSelected: keyof OnlyNumberPropsOf<UserBox> = "avatarWear";
const userboxNewOptions = ["systemVoice", "frame", "trophy", "mapIcon"]
const userboxNewOptions = ["systemVoice", "frame", "trophy", "mapIcon", "stage"]
async function userboxSafeDrop(event: Event & { currentTarget: EventTarget & HTMLInputElement; }) {
if (!event.target) return null;
@@ -317,21 +317,23 @@
{#if !USERBOX_ENABLED.value || !USERBOX_INSTALLED}
<div class="fields">
{#each userItems as { iKey, ubKey, items }, i}
<div class="field">
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
<div>
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
{#each items as option}
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
{/each}
</select>
{#if changed.includes(ubKey)}
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
{t("settings.profile.save")}
</button>
{/if}
{#if items.length > 0}
<div class="field">
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
<div>
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
{#each items as option}
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
{/each}
</select>
{#if changed.includes(ubKey)}
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
{t("settings.profile.save")}
</button>
{/if}
</div>
</div>
</div>
{/if}
{/each}
</div>
{:else}
@@ -372,21 +374,23 @@
</div>
<div class="fields">
{#each userItems.filter(i => userboxNewOptions.includes(i.iKey)) as { iKey, ubKey, items }, i}
<div class="field">
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
<div>
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
{#each items as option}
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
{/each}
</select>
{#if changed.includes(ubKey)}
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
{t("settings.profile.save")}
</button>
{/if}
{#if items.length > 0}
<div class="field">
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
<div>
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
{#each items as option}
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
{/each}
</select>
{#if changed.includes(ubKey)}
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
{t("settings.profile.save")}
</button>
{/if}
</div>
</div>
</div>
{/if}
{/each}
</div>
{/if}
+8 -8
View File
@@ -29,6 +29,14 @@ export const HAS_USERBOX_ASSETS = true
// Matching servers
export const CHU3_MATCHINGS: ChusanMatchingOption[] = [
{
name: "Yukiotoko",
ui: "https://yukiotoko.metatable.sh/",
guide: "https://github.com/MewoLab/AquaDX/blob/v1-dev/docs/chu3-national-matching.md",
matching: "http://yukiotoko.chara.lol:9004/",
reflector: "http://yukiotoko.chara.lol:50201/",
coop: ["Missless", "CozyNet", "GMG"]
},
{
name: "林国对战",
ui: "https://chu3-match.sega.ink/rooms",
@@ -37,12 +45,4 @@ export const CHU3_MATCHINGS: ChusanMatchingOption[] = [
reflector: "http://reflector.naominet.live:18080/",
coop: ["RinNET", "MysteriaNET"],
},
{
name: "Yukiotoko",
ui: "https://yukiotoko.metatable.sh/",
guide: "https://github.com/MewoLab/AquaDX/blob/v1-dev/docs/chu3-national-matching.md",
matching: "http://yukiotoko.chara.lol:9004/",
reflector: "http://yukiotoko.chara.lol:50201/",
coop: ["Missless", "CozyNet", "GMG"]
}
]
+14 -2
View File
@@ -194,6 +194,8 @@ export const EN_REF_SETTINGS = {
'settings.fields.chusanTeamName.desc': 'Customize the text displayed on the top of your profile.',
'settings.fields.chusanInfinitePenguins.name': 'Infinite Penguins',
'settings.fields.chusanInfinitePenguins.desc': 'Set penguin statues for character level prompting to 999.',
'settings.fields.chusanLvUnlockAll.name': 'Unlock Linked Gates',
'settings.fields.chusanLvUnlockAll.desc': 'Incurs a long animated sequence, cannot be undone',
'settings.fields.chusanMatchingReflector.name': 'Matching Server Reflector',
'settings.fields.chusanMatchingReflector.desc': 'URL of the national matching server\'s UDP reflector.',
'settings.fields.chusanMatchingServer.name': 'Matching Server',
@@ -224,7 +226,7 @@ export const EN_REF_SETTINGS = {
'settings.profile.unchanged': 'Unchanged',
'settings.export': 'Export Player Data',
'settings.batchManualExport': "Export in Batch Manual (for Tachi)",
'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings.",
'settings.cabNotice': "These settings only apply for your keychip. If you're playing on someone else's setup, please ask them to change these settings.",
'settings.siteNotice': "These settings only apply to the website.",
'settings.regionNotice': "These settings are shared amongst Mai, Ongeki and Chuni.",
'settings.regionSelector.title': "Prefecture Selector",
@@ -236,6 +238,7 @@ export const EN_REF_USERBOX = {
'userbox.header.general': 'General Settings',
'userbox.header.matching': 'National Matching',
'userbox.header.matching.symbolChat': 'Chat Symbols (Matching)',
'userbox.header.linkedVerse': 'Linked Verse',
'userbox.header.userbox': 'UserBox Settings',
'userbox.header.preview': 'UserBox Preview',
'userbox.nameplateId': 'Nameplate',
@@ -245,6 +248,7 @@ export const EN_REF_USERBOX = {
'userbox.trophyIdSub2': 'Trophy Sub #2 (Title)',
'userbox.mapIconId': 'Map Icon',
'userbox.voiceId': 'System Voice',
'userbox.stageId': 'Stage',
'userbox.avatarWear': 'Avatar Wear',
'userbox.avatarHead': 'Avatar Head',
'userbox.avatarFace': 'Avatar Face',
@@ -265,6 +269,14 @@ export const EN_REF_USERBOX = {
'userbox.matching.symbolChat': 'Message Choice',
'userbox.matching.symbolChat.default': 'Default',
'userbox.lv.diffnotice': 'In Linked Verse, you will be matched separately based on your difficulty.',
'userbox.lv.difficulty': 'Linked Verse Difficulty',
'userbox.lv.difficulty.1': 'LEVEL V (1000 life, MASTER only)',
'userbox.lv.difficulty.2': 'LEVEL IV (3000 life, MASTER only)',
'userbox.lv.difficulty.3': 'LEVEL III (5000 life, MASTER only)',
'userbox.lv.difficulty.4': 'LEVEL II (5000 life, MASTER & EXPERT only)',
'userbox.lv.difficulty.5': 'LEVEL I (5000 life, ALL difficulties)',
'userbox.new.name': 'AquaBox',
'userbox.new.setup': 'Drag and drop your Chuni game folder (Lumi or newer) into the box below to display UserBoxes with their nameplate & avatar. All files are handled in-browser.',
'userbox.new.setup.notice': 'Select the highest folder containing your game data.',
@@ -286,7 +298,7 @@ export const EN_REF_USERBOX = {
export const EN_REF_MAI_PHOTO = {
'maiphoto.title': 'Mai Memorial Photo Gallery',
'maiphoto.url_warning': 'Note: If you want to share a photo with your friend, please save the photo. Do not copy image URL because the URL contains sensitive information.',
'maiphoto.url_warning': 'If you want to share a photo with your friend, please save the photo. Do not copy image URL because the URL contains sensitive information.',
'maiphoto.none': 'No photo found. You can upload photo by clicking upload at the end of each game session.',
}