2026-09-15 18:13:46 +07:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Debug Panel — vehicles</ title >
< style >
body { font-family : Consolas , monospace ; background : #14161a ; color : #d7dce2 ; max-width : 860 px ; margin : 24 px auto ; padding : 0 16 px ; }
h1 { font-size : 20 px ; }
h2 { font-size : 15 px ; color : #8fb8ff ; border-bottom : 1 px solid #333 ; padding-bottom : 4 px ; }
. card { background : #1d2026 ; border : 1 px solid #333 ; border-radius : 8 px ; padding : 12 px 16 px ; margin : 12 px 0 ; }
. dot { display : inline-block ; width : 10 px ; height : 10 px ; border-radius : 50 % ; background : #666 ; margin-right : 6 px ; }
. dot . on { background : #4caf50 ; }
button { background : #2b6cb0 ; color : #fff ; border : 0 ; border-radius : 6 px ; padding : 8 px 14 px ; margin : 4 px 4 px 4 px 0 ; cursor : pointer ; font-family : inherit ; }
button : hover { background : #3884d6 ; }
button . ghost { background : #3a3f47 ; }
input , select { background : #101215 ; color : #d7dce2 ; border : 1 px solid #444 ; border-radius : 6 px ; padding : 7 px 9 px ; margin : 2 px ; font-family : inherit ; }
input [ type = number ] { width : 110 px ; }
input [ type = range ] { width : 100 % ; margin : 10 px 0 2 px 0 ; accent-color : #ffb74d ; cursor : pointer ; }
# clock { font-size : 30 px ; letter-spacing : 2 px ; color : #ffd88a ; }
table { border-collapse : collapse ; width : 100 % ; margin-top : 8 px ; }
td , th { border : 1 px solid #333 ; padding : 4 px 8 px ; text-align : left ; font-size : 13 px ; }
. muted { color : #888 ; font-size : 12 px ; }
# log { white-space : pre-wrap ; font-size : 12 px ; max-height : 160 px ; overflow-y : auto ; }
</ style >
</ head >
< body >
< h1 >< span id = "dot" class = "dot" ></ span > Vehicle debug</ h1 >
< div class = "card" >
< h2 > Status</ h2 >
< div id = "status" > connecting…</ div >
< div class = "muted" > refreshes every 2 sec</ div >
</ div >
< div class = "card" >
< h2 > Time of day</ h2 >
< div id = "clock" > 12:00</ div >
< input id = "timeslider" type = "range" min = "0" max = "1439" step = "1" value = "720" aria-label = "Time of day" >
< div class = "muted" > 00:00 — night · 06:00 — morning · 12:00 — noon · 18:00 — evening · 23:59 — night</ div >
</ div >
2026-09-16 18:35:25 +07:00
< div class = "card" >
< h2 > Weather / Atmosphere (สภาพอากาศ)</ h2 >
< div >
< button style = "background:#fbc02d;color:#222;font-weight:bold" onclick = "setWeather(1)" > ☀️ Sunny (แดดออก)</ button >
< button style = "background:#78909c" onclick = "setWeather(2)" > ⛅ Cloudy (มีเมฆมาก)</ button >
< button style = "background:#1976d2" onclick = "setWeather(3)" > 🌧️ Rain (ฝนตก)</ button >
< button style = "background:#512da8" onclick = "setWeather(4)" > ⛈️ Storm (พายุฝนฟ้าคะนอง)</ button >
< button style = "background:#757575" onclick = "setWeather(5)" > 🌫️ Fog (หมอกลงจัด)</ button >
< button style = "background:#455a64" onclick = "setWeather(6)" > 🌥️ Overcast (มืดครึ้ม)</ button >
< button style = "background:#303f9f" onclick = "setWeather(7)" > 🌙 Night / Snow (หิมะ/กลางคืน)</ button >
</ div >
< div style = "margin-top:8px" >
Custom Weather ID: < input id = "customWeatherId" type = "number" style = "width:90px" value = "1" >
Transition (sec): < input id = "weatherTransition" type = "number" style = "width:70px" value = "5" >
< button class = "ghost" onclick = "setCustomWeather()" > Apply Custom Weather</ button >
</ div >
< div class = "muted" style = "margin-top:6px" > ซิงค์สภาพอากาศของโลกและบรรยากาศ client ทันที</ div >
</ div >
2026-09-15 18:13:46 +07:00
< div class = "card" >
< h2 > Garage (server vehicles)</ h2 >
< div style = "margin-bottom:10px;padding:8px;background:#252932;border-radius:6px" >
< div style = "font-weight:bold;margin-bottom:6px;color:#ffb74d" > ⚡ Quick Spawn Special Vehicles:</ div >
< button style = "background:#8e24aa" onclick = "spawnSpecificVehicle(81006020)" > 🚀 FG Vision Aric (Flying Car) [81006020]</ button >
2026-09-15 20:29:33 +07:00
< button style = "background:#e91e63" onclick = "spawnSpecificVehicle(81007104)" > 🏍️ Shikage Freeman800 (Honda NT1100) [81007104]</ button >
2026-09-15 18:13:46 +07:00
< button style = "background:#00897b" onclick = "spawnSpecificVehicle(81007007)" > 🚁 Reed Ranger Helicopter [81007007]</ button >
< button style = "background:#f4511e" onclick = "spawnSpecificVehicle(81006011)" > 🏍️ Sunset M125 Superbike [81006011]</ button >
2026-09-15 20:29:33 +07:00
< div style = "font-size:11px;color:#81c784;margin-top:6px" > 💡 หมายเหตุ: รถล้อเดียวของ Taffy (塔菲独轮车) เป็นสกิลกดขี่ของตัวละคร Taffy (กด E / Active Skill) ให้สลับเป็นตัวละคร Taffy แล้วกด E เพื่อแปลงค้อนเป็นรถล้อเดียวขี่ได้ทันที</ div >
2026-09-15 18:13:46 +07:00
</ div >
< div > Category: < select id = "fleetcat" onchange = "filterFleet()" ></ select ></ div >
< div style = "margin-top:6px" > Vehicle: < select id = "fleet" ></ select ></ div >
< div style = "margin-top:8px" >
< button onclick = "spawnNear()" > 1. Spawn selected nearby</ button >
< button onclick = "enterLast()" > 2. Seat into latest</ button >
</ div >
< div style = "margin-top:8px" >
< button class = "ghost" onclick = "resync()" > Verified (39)</ button >
< button class = "ghost" onclick = "unlockAll()" > SyncAllUnlockedVehicles (all 150)</ button >
</ div >
< div class = "muted" > By default only the 39 verified vehicles load into the game. All 150 (incl. broken/unverified) — only via SyncAllUnlockedVehicles.</ div >
< div style = "margin-top:8px" >
Custom ID: < input id = "rawId" type = "number" style = "width:130px" placeholder = "81007068" >
< button class = "ghost" onclick = "spawnRaw()" > Spawn by ID</ button >
</ div >
< div class = "muted" > Custom ID — for testing excluded/broken ones (Broken Model, Undrivable): the server lets it through with a warning, the client has the final say.</ div >
< div class = "muted" > Pushes SyncAllUnlockedVehicles — the vehicle list in the phone/garage updates without relogging.</ div >
</ div >
< div class = "card" >
< h2 > Summoned vehicles</ h2 >
< div id = "summoned" > —</ div >
</ div >
< div class = "card" >
< h2 > Scene ("raid") switch</ h2 >
< div style = "margin-bottom:8px" >
Preset:
< select id = "scenepreset" onchange = "onPresetChange()" style = "min-width:380px" >
< option value = "longqi" > Longqi Village (raid 23300999) (raid 23300999)</ option >
< option value = "longqi_tea" > Longqi Village — Tea Field (Scenic)</ option >
< option value = "longqi_temple" > Longqi Village — Hushi Zen Temple</ option >
< option value = "longqi_wukang" > Longqi Village — Wukang Mansion</ option >
< option value = "maincity" > Main City (WorldMap_Release) (raid 23300888)</ option >
< option value = "airport" > Main City — Airport Terminal (raid 23300888)</ option >
</ select >
< button style = "background:#2e7d32;margin-left:8px" onclick = "switchToPreset()" > Switch to preset</ button >
</ div >
< div style = "margin-bottom:8px" >
Raid < input id = "s_raid" type = "number" style = "width:130px" value = "23300999" >
Instance < input id = "s_instance" type = "number" style = "width:130px" value = "20001223" >
Universe < input id = "s_universe" type = "number" style = "width:130px" value = "76000888" >
</ div >
< div >
X < input id = "s_x" type = "number" step = "0.5" style = "width:90px" value = "-4719.8" >
Y < input id = "s_y" type = "number" step = "0.5" style = "width:90px" value = "168.5" >
Z < input id = "s_z" type = "number" step = "0.5" style = "width:90px" value = "-2844.7" >
Facing < input id = "s_f" type = "number" step = "1" style = "width:80px" value = "0" >
< button onclick = "switchSceneCustom()" style = "margin-left:8px" > Switch scene</ button >
</ div >
< div class = "muted" style = "margin-top:6px" > Switches world region and initiates client-side scene load. Preset or custom coordinates can be specified.</ div >
</ div >
< div class = "card" >
< h2 > Player teleport</ h2 >
< div >
X < input id = "tx" type = "number" step = "0.5" value = "0" >
Y < input id = "ty" type = "number" step = "0.5" value = "0" >
Z < input id = "tz" type = "number" step = "0.5" value = "0" >
Facing < input id = "tf" type = "number" step = "1" value = "0" >
</ div >
< div style = "margin-top:8px" >
< button onclick = "teleport()" > Teleport</ button >
< button class = "ghost" onclick = "fillCurrent()" > Use current</ button >
< button class = "ghost" onclick = "fillSpawn()" > Spawn point</ button >
</ div >
</ div >
2026-09-16 02:19:44 +07:00
< div class = "card" style = "border-color:#ab47bc" >
< h2 style = "color:#ce93d8" > 👗 Outfit / Appearance (สลับชุด / ซ่อนเสื้อผ้า)</ h2 >
< div >
< button style = "background:#8e24aa;color:white" onclick = "toggleClothes()" > Toggle Clothes / ซ่อน-แสดงชุด</ button >
</ div >
< div class = "muted" style = "margin-top:6px" > สลับการแสดงผลเสื้อผ้า (SkinnedMeshRenderer) ของตัวละครที่กำลังเล่นอยู่ โดยยังคงแสดงโมเดลบอดี้เดิมไว้</ div >
</ div >
2026-09-15 18:13:46 +07:00
< div class = "card" style = "border-color:#e53935" >
< h2 style = "color:#ff8a80" > Emergency Unstuck & Black Screen Recovery</ h2 >
< div >
< button style = "background:#d32f2f" onclick = "unstuckBlackScreen()" > Fix / Recover Black Screen</ button >
< button style = "background:#f57c00" onclick = "rollback10s()" > Rollback Position (10s Ago)</ button >
</ div >
< div class = "muted" style = "margin-top:6px" > Immediately forces client transition reset, restores user input, and rolls back coordinates to before the glitch.</ div >
</ div >
< div class = "card" >
< h2 > Combat & Monster Spawner</ h2 >
< div style = "margin-bottom:6px" >
Monster:
< select id = "enemypreset" onchange = "onEnemyPresetChange()" >
< option value = "40900579" > Melee Combatant (Verified) [40900579]</ option >
< option value = "40950138" > Tactical Ranged Soldier (Verified) [40950138]</ option >
< option value = "100101" > Street Thug (Melee) [100101]</ option >
< option value = "100102" > Street Thug (Ranged Pistol) [100102]</ option >
< option value = "100201" > Elite Enforcer (Katana) [100201]</ option >
< option value = "100301" > Heavy Shield Brawler [100301]</ option >
< option value = "200101" > Chaos Mutant Boss [200101]</ option >
< option value = "200201" > Armored Mech Boss [200201]</ option >
< option value = "custom" > Custom ID...</ option >
</ select >
ID: < input id = "enemyId" type = "number" style = "width:100px" value = "40900579" >
</ div >
< div >
Camp:
< select id = "enemyCamp" >
2026-09-16 02:19:44 +07:00
< option value = "0" > Enemy / Hostile (0)</ option >
< option value = "1" > Ally / Friendly (1)</ option >
< option value = "2" > Neutral (2)</ option >
2026-09-15 18:13:46 +07:00
</ select >
Count: < input id = "enemyCount" type = "number" style = "width:70px" value = "1" min = "1" max = "10" >
< button style = "background:#e53935;margin-left:8px" onclick = "spawnEnemy()" > Spawn Opponent</ button >
</ div >
< div class = "muted" style = "margin-top:6px" > Spawns enemies near the player for live combat testing. Use with active weapon & combat sandbox.</ div >
</ div >
2026-09-15 23:33:46 +07:00
< div class = "card" style = "border-color:#4caf50" >
< h2 style = "color:#81c784" > 🎬 Realtime 3D Cutscenes & Timelines (In-Engine 3D)</ h2 >
< div style = "margin-bottom:8px" >
Preset:
< select id = "timelinepreset" onchange = "onTimelinePresetChange()" style = "min-width:380px" >
< option value = "SwitchChar_tafei_01" > 🐰 Taffy 3D Entrance (คัทซีนเปิดตัว Taffy ถือค้อนกระโดดลงมา)</ option >
< option value = "SwitchChar_nanzhujue_03" > 🗡️ Male MC 3D Entrance (คัทซีนเปิดตัวเอกชาย)</ option >
< option value = "SwitchChar_saimo_05" > ⚡ Seymour 3D Showcase (คัทซีนเปิดตัว Seymour)</ option >
< option value = "SwitchChar_character_13" > 🌸 Rin 3D Entrance (คัทซีนเปิดตัว Rin)</ option >
< option value = "SwitchChar_character_19" > 🎸 Bansy 3D Entrance (คัทซีนเปิดตัว Bansy)</ option >
< option value = "SwitchChar_character_20" > 🐺 Garm 3D Entrance (คัทซีนเปิดตัว Garm)</ option >
< option value = "SwitchChar_character_22" > 🎯 Aileen 3D Showcase (คัทซีนเปิดตัว Aileen)</ option >
< option value = "SwitchChar_common_12_SA1" > 💥 Ground Slam Action (คัทซีนกระโดดกระแทกพื้น)</ option >
< option value = "SwitchChar_common_20a_SA1" > 🪂 High Altitude Combat Drop A (ดิ่งพสุธาสู่สนามรบ A)</ option >
< option value = "SwitchChar_common_20b_SA1" > 🪂 High Altitude Combat Drop B (ดิ่งพสุธาสู่สนามรบ B)</ option >
< option value = "SwitchChar_common_24" > 🤸 Acrobatic Flip Entrance (คัทซีนตีลังกาลงสู่พื้น)</ option >
< option value = "SwitchChar_common_25_SA1" > 🌪️ Heavy Impact Shockwave (คัทซีนกระแทกพื้นคลื่นพลัง)</ option >
< option value = "TL_SEYM_010_Q010_S01" > 📜 Seymour Story Quest Scene 1 (คัทซีนเควสต์เนื้อเรื่อง Seymour)</ option >
< option value = "TL_NewMilk" > 🥛 Milk Delivery Mission (คัทซีนภารกิจส่งนม)</ option >
< option value = "custom" > Custom Timeline Name...</ option >
</ select >
</ div >
< div style = "margin-bottom:8px" >
Name / Path: < input id = "timelineName" type = "text" style = "width:280px" value = "SwitchChar_tafei_01" >
< button style = "background:#2e7d32;margin-left:8px" onclick = "playTimeline()" > ▶️ Play 3D Timeline (Realtime)</ button >
< button class = "ghost" onclick = "unstuckBlackScreen()" > ⏹️ Stop / Reset</ button >
</ div >
< div class = "muted" > 💡 เรนเดอร์โมเดล 3D และมุมกล้อง Cinemachine สดๆ ใน Engine ไม่ต้องพึ่งพาไฟล์วิดีโอ หมดปัญหาไฟล์วิดีโอหาย</ div >
</ div >
2026-09-15 18:13:46 +07:00
< div class = "card" >
< h2 > Panel log</ h2 >
< div id = "log" ></ div >
</ div >
< script >
let lastStatus = null ;
let fleetData = [];
const catNames = { sport : 'Sport / Luxury / Unique' , regular : 'Regular Cars' , suv : 'SUV / Pickups' , van : 'Vans / Bus' , truck : 'Trucks / Heavy Duty' , moto : 'Motorcycles / Scooters' , water : 'Watercraft' , air : 'Aircraft / Flying Car' , special : 'Special / Test / Props' };
let timeDragging = false ;
function fmtClock ( totalMin ) {
const h = Math . floor ( totalMin / 60 ) % 24 ;
const m = totalMin % 60 ;
return String ( h ). padStart ( 2 , '0' ) + ':' + String ( m ). padStart ( 2 , '0' );
}
function sliderMinutes () { return parseInt ( document . getElementById ( 'timeslider' ). value || '720' , 10 ); }
async function pushTime () {
const total = sliderMinutes ();
const body = {
hour : Math . floor ( total / 60 ) % 24 ,
minute : total % 60 ,
fix : true ,
transition : 5
};
const r = await api ( '/api/time/set' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ( body ) });
log ( 'time: ' + JSON . stringify ( r ));
}
function syncTimeUI ( s ) {
const slider = document . getElementById ( 'timeslider' );
const clock = document . getElementById ( 'clock' );
if ( ! s . online ) {
slider . disabled = true ;
return ;
}
slider . disabled = false ;
if ( typeof s . hour === 'number' && typeof s . minute === 'number' && s . hasTime && ! timeDragging ) {
slider . value = s . hour * 60 + s . minute ;
clock . textContent = fmtClock ( s . hour * 60 + s . minute );
} else if ( ! timeDragging ) {
clock . textContent = fmtClock ( sliderMinutes ());
}
}
function log ( t ) {
const el = document . getElementById ( 'log' );
el . textContent = new Date (). toLocaleTimeString () + ' ' + t + '\n' + el . textContent ;
}
async function api ( path , opts ) {
const r = await fetch ( path , opts );
return await r . json ();
}
async function refresh () {
try {
const s = await api ( '/api/status' );
lastStatus = s ;
document . getElementById ( 'dot' ). className = 'dot' + ( s . online ? ' on' : '' );
if ( ! s . online ) {
document . getElementById ( 'status' ). textContent = 'client not in game (no live session)' ;
document . getElementById ( 'summoned' ). textContent = '—' ;
syncTimeUI ( s );
return ;
}
const p = s . player ;
const w = s . world || { raidId : '—' , instanceId : '—' };
document . getElementById ( 'status' ). innerHTML =
`pid= ${ s . pid } ready= ${ s . ready } unit= ${ s . activeUnit } raid= ${ w . raidId } instance= ${ w . instanceId } <br>` +
`pos=( ${ p . x . toFixed ( 1 ) } , ${ p . y . toFixed ( 1 ) } , ${ p . z . toFixed ( 1 ) } ) yaw= ${ p . yaw . toFixed ( 1 ) } fix= ${ p . hasFix } <br>` +
`fleet= ${ s . fleetCount } summoned= ${ s . summoned . length } ` +
( s . inVehicle ? `<br>in vehicle: entity= ${ s . inVehicle } seat= ${ s . inSeat } <button onclick="vehicleExit()">Exit</button>` : '' );
document . getElementById ( 'summoned' ). innerHTML = s . summoned . length
? '<table><tr><th>entityId</th><th>configId</th><th>coordinates</th><th></th></tr>' +
s . summoned . map ( v => `<tr><td> ${ v . entityId } </td><td> ${ v . configId } </td>` +
`<td> ${ v . hasFix ? ` ${ v . x . toFixed ( 1 ) } , ${ v . y . toFixed ( 1 ) } , ${ v . z . toFixed ( 1 ) } ` : '—' } </td>` +
`<td><button onclick="vehicleEnter( ${ v . entityId } )">Enter</button> ` +
`<button onclick="vehicleToMe( ${ v . entityId } )">To me</button> ` +
`<button onclick="vehicleGoto( ${ v . entityId } )">To it</button> ` +
`<button class="ghost" onclick="vehicleRemove( ${ v . entityId } )">Remove</button></td></tr>` ). join ( '' ) + '</table>'
: 'empty — click "Spawn selected vehicle nearby" above' ;
syncTimeUI ( s );
} catch ( e ) { log ( 'status error: ' + e ); }
}
async function loadFleet () {
try {
fleetData = await api ( '/api/fleet' );
const cats = [... new Set ( fleetData . map ( v => v . cat ))];
document . getElementById ( 'fleetcat' ). innerHTML =
cats . map ( c => `<option value=" ${ c } "> ${ catNames [ c ] || c } </option>` ). join ( '' );
filterFleet ();
} catch ( e ) { log ( 'fleet error: ' + e ); }
}
function filterFleet () {
const c = document . getElementById ( 'fleetcat' ). value ;
const list = fleetData . filter ( v => v . cat === c );
document . getElementById ( 'fleet' ). innerHTML =
list . map ( v => `<option value=" ${ v . id } "> ${ v . id } — ${ v . name } </option>` ). join ( '' );
}
async function enterLast () {
const r = await api ( '/api/vehicle/enter' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : '{}' });
log ( 'enter: ' + JSON . stringify ( r ));
refresh ();
}
async function resync () {
const r = await api ( '/api/garage/resync' , { method : 'POST' });
log ( 'resync verified: ' + JSON . stringify ( r ));
}
async function unlockAll () {
const r = await api ( '/api/garage/unlock-all' , { method : 'POST' });
log ( 'unlock-all: ' + JSON . stringify ( r ));
}
async function spawnSpecificVehicle ( id ) {
const r = await api ( '/api/vehicle/spawn' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ({ configId : id , distance : 6 }) });
log ( 'spawn ' + id + ': ' + JSON . stringify ( r ));
refresh ();
}
async function spawnNear () {
const sel = document . getElementById ( 'fleet' );
const body = { configId : parseInt ( sel . value || '0' , 10 ), distance : 6 };
const r = await api ( '/api/vehicle/spawn' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ( body ) });
log ( 'spawn: ' + JSON . stringify ( r ));
refresh ();
}
async function spawnRaw () {
const id = parseInt ( document . getElementById ( 'rawId' ). value || '0' , 10 );
if ( ! id ) { log ( 'spawnRaw: enter an ID' ); return ; }
const r = await api ( '/api/vehicle/spawn' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ({ configId : id , distance : 6 }) });
log ( 'spawnRaw: ' + JSON . stringify ( r ));
refresh ();
}
async function vehicleEnter ( entityId ) {
const r = await api ( '/api/vehicle/enter' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ({ entityId }) });
log ( 'enter: ' + JSON . stringify ( r ));
refresh ();
}
async function vehicleExit () {
const r = await api ( '/api/vehicle/exit' , { method : 'POST' });
log ( 'exit: ' + JSON . stringify ( r ));
refresh ();
}
async function vehicleToMe ( entityId ) {
const r = await api ( '/api/vehicle/to-me' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ({ entityId }) });
log ( 'to-me: ' + JSON . stringify ( r ));
}
async function vehicleGoto ( entityId ) {
const r = await api ( '/api/vehicle/goto' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ({ entityId }) });
log ( 'goto: ' + JSON . stringify ( r ));
}
async function vehicleRemove ( entityId ) {
const r = await api ( '/api/vehicle/remove' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ({ entityId }) });
log ( 'remove: ' + JSON . stringify ( r ));
refresh ();
}
async function teleport () {
const body = {
x : parseFloat ( document . getElementById ( 'tx' ). value ),
y : parseFloat ( document . getElementById ( 'ty' ). value ),
z : parseFloat ( document . getElementById ( 'tz' ). value ),
facing : parseFloat ( document . getElementById ( 'tf' ). value )
};
const r = await api ( '/api/player/teleport' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ( body ) });
log ( 'teleport: ' + JSON . stringify ( r ));
}
function fillCurrent () {
if ( ! lastStatus || ! lastStatus . online ) return ;
const p = lastStatus . player ;
document . getElementById ( 'tx' ). value = p . x . toFixed ( 1 );
document . getElementById ( 'ty' ). value = p . y . toFixed ( 1 );
document . getElementById ( 'tz' ). value = p . z . toFixed ( 1 );
document . getElementById ( 'tf' ). value = p . yaw . toFixed ( 1 );
}
function fillSpawn () {
if ( ! lastStatus || ! lastStatus . online || ! lastStatus . spawn ) return ;
document . getElementById ( 'tx' ). value = lastStatus . spawn . x ;
document . getElementById ( 'ty' ). value = lastStatus . spawn . y ;
document . getElementById ( 'tz' ). value = lastStatus . spawn . z ;
}
const scenePresets = {
longqi : { raid : 23300999 , instance : 20001223 , universe : 76000888 , x : - 4719.8 , y : 168.5 , z : - 2844.7 , facing : 0 },
longqi_tea : { raid : 23300999 , instance : 20001223 , universe : 76000888 , x : - 4818.4 , y : 192.3 , z : - 2690.1 , facing : 117 },
longqi_temple : { raid : 23300999 , instance : 20001223 , universe : 76000888 , x : - 4306.0 , y : 156.8 , z : - 3240.8 , facing : 133 },
longqi_wukang : { raid : 23300999 , instance : 20001223 , universe : 76000888 , x : 158.7 , y : 18.0 , z : 1057.2 , facing : - 48.8 },
maincity : { raid : 23300888 , instance : 20001222 , universe : 76000888 , x : 3142.67 , y : 0 , z : 2522.78 , facing : 0 },
airport : { raid : 23300888 , instance : 20001222 , universe : 76000888 , x : - 4468.3 , y : - 6.15 , z : - 2597.5 , facing : 85.5 }
};
function onPresetChange () {
const sel = document . getElementById ( 'scenepreset' ). value ;
const p = scenePresets [ sel ];
if ( ! p ) return ;
document . getElementById ( 's_raid' ). value = p . raid ;
document . getElementById ( 's_instance' ). value = p . instance ;
document . getElementById ( 's_universe' ). value = p . universe ;
document . getElementById ( 's_x' ). value = p . x ;
document . getElementById ( 's_y' ). value = p . y ;
document . getElementById ( 's_z' ). value = p . z ;
document . getElementById ( 's_f' ). value = p . facing ;
}
async function switchToPreset () {
onPresetChange ();
await switchSceneCustom ();
}
async function switchSceneCustom () {
const body = {
raidId : parseInt ( document . getElementById ( 's_raid' ). value || '23300999' , 10 ),
instanceId : parseInt ( document . getElementById ( 's_instance' ). value || '20001223' , 10 ),
universeId : parseInt ( document . getElementById ( 's_universe' ). value || '76000888' , 10 ),
x : parseFloat ( document . getElementById ( 's_x' ). value || '0' ),
y : parseFloat ( document . getElementById ( 's_y' ). value || '0' ),
z : parseFloat ( document . getElementById ( 's_z' ). value || '0' ),
facing : parseFloat ( document . getElementById ( 's_f' ). value || '0' )
};
const r = await api ( '/api/world/switch-scene' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ( body ) });
log ( 'switch scene: ' + JSON . stringify ( r ));
refresh ();
}
async function unstuckBlackScreen () {
const r = await api ( '/api/unstuck/blackscreen' , { method : 'POST' });
log ( 'unstuck black screen: ' + JSON . stringify ( r ));
refresh ();
}
async function rollback10s () {
const r = await api ( '/api/player/rollback-10s' , { method : 'POST' });
log ( 'rollback 10s: ' + JSON . stringify ( r ));
refresh ();
}
2026-09-16 02:19:44 +07:00
async function toggleClothes () {
const r = await api ( '/api/player/toggle-clothes' , { method : 'POST' });
log ( 'toggle clothes: ' + JSON . stringify ( r ));
}
2026-09-16 18:35:25 +07:00
async function setWeather ( weatherId ) {
const transition = parseInt ( document . getElementById ( 'weatherTransition' ) ? . value || '5' , 10 );
const body = { weatherId : parseInt ( weatherId , 10 ), transition };
const r = await api ( '/api/weather/set' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ( body ) });
log ( 'set weather: ' + JSON . stringify ( r ));
}
async function setCustomWeather () {
const id = parseInt ( document . getElementById ( 'customWeatherId' ). value || '1' , 10 );
await setWeather ( id );
}
2026-09-15 18:13:46 +07:00
function onEnemyPresetChange () {
const sel = document . getElementById ( 'enemypreset' ). value ;
if ( sel !== 'custom' ) {
document . getElementById ( 'enemyId' ). value = sel ;
}
}
async function spawnEnemy () {
const body = {
2026-09-16 02:19:44 +07:00
enemyId : parseInt ( document . getElementById ( 'enemyId' ). value || '40900579' , 10 ),
camp : parseInt ( document . getElementById ( 'enemyCamp' ). value || '0' , 10 ),
2026-09-15 18:13:46 +07:00
count : parseInt ( document . getElementById ( 'enemyCount' ). value || '1' , 10 )
};
const r = await api ( '/api/enemy/spawn' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ( body ) });
log ( 'spawn enemy: ' + JSON . stringify ( r ));
}
2026-09-15 23:33:46 +07:00
function onTimelinePresetChange () {
const sel = document . getElementById ( 'timelinepreset' ). value ;
if ( sel !== 'custom' ) {
document . getElementById ( 'timelineName' ). value = sel ;
}
}
async function playTimeline () {
const body = {
name : document . getElementById ( 'timelineName' ). value || 'SwitchChar_tafei_01'
};
const r = await api ( '/api/timeline/play' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' }, body : JSON . stringify ( body ) });
log ( 'play timeline: ' + JSON . stringify ( r ));
}
2026-09-15 18:13:46 +07:00
loadFleet ();
refresh ();
setInterval ( refresh , 2000 );
document . getElementById ( 'timeslider' ). addEventListener ( 'input' , e => {
timeDragging = true ;
document . getElementById ( 'clock' ). textContent = fmtClock ( parseInt ( e . target . value || '720' , 10 ));
});
document . getElementById ( 'timeslider' ). addEventListener ( 'change' , async () => {
timeDragging = false ;
await pushTime ();
refresh ();
});
</ script >
</ body >
</ html >