feat: WS sends selected_game_id on connect, gear indicator auto-adapts to game mapping (Forza/AC/iRacing)
This commit is contained in:
@@ -84,7 +84,12 @@ async def scene_render(request: Request, scene_id: str):
|
|||||||
async def websocket_endpoint(ws: WebSocket):
|
async def websocket_endpoint(ws: WebSocket):
|
||||||
cid = await ws_manager.connect(ws)
|
cid = await ws_manager.connect(ws)
|
||||||
try:
|
try:
|
||||||
await ws.send_json({"type": "connected", "client_id": cid})
|
cfg = get_config()
|
||||||
|
await ws.send_json({
|
||||||
|
"type": "connected",
|
||||||
|
"client_id": cid,
|
||||||
|
"selected_game_id": cfg.get("selected_game_id"),
|
||||||
|
})
|
||||||
while True:
|
while True:
|
||||||
msg = await ws.receive_json()
|
msg = await ws.receive_json()
|
||||||
if msg.get("type") == "ping":
|
if msg.get("type") == "ping":
|
||||||
|
|||||||
@@ -46,6 +46,60 @@ html,body{width:100%;height:100%;overflow:hidden;background:#000;
|
|||||||
</div></div></div>
|
</div></div></div>
|
||||||
<script>
|
<script>
|
||||||
const M={aspect_ratio:"1:1",render_mode:"contain",gear_style:"auto"};
|
const M={aspect_ratio:"1:1",render_mode:"contain",gear_style:"auto"};
|
||||||
|
let GAME_ID='';
|
||||||
|
|
||||||
|
// Forza: 0=R, 1-8=1-8 (no neutral in UDP)
|
||||||
|
// AC/F1: -1=R, 0=N, 1-8=1-8
|
||||||
|
// iRacing: 0=N, 1=R, 2=1st... (offset +2)
|
||||||
|
function gearLabel(g){
|
||||||
|
if(!g&&g!==0)return'-';
|
||||||
|
const style=M.gear_style||'auto';
|
||||||
|
const isForza=GAME_ID&&(GAME_ID.includes('forza'));
|
||||||
|
const isIRacing=GAME_ID&&GAME_ID.includes('iracing');
|
||||||
|
|
||||||
|
if(style==='forza'||isForza){
|
||||||
|
if(g===0)return'R';
|
||||||
|
if(g>=1&&g<=8)return String(g);
|
||||||
|
return String(g);
|
||||||
|
}
|
||||||
|
if(style==='ac'||(!isForza&&!isIRacing)){
|
||||||
|
if(g===-1)return'R';
|
||||||
|
if(g===0)return'N';
|
||||||
|
if(g>=1&&g<=8)return String(g);
|
||||||
|
return String(g);
|
||||||
|
}
|
||||||
|
if(isIRacing){
|
||||||
|
if(g===0)return'N';
|
||||||
|
if(g===1)return'R';
|
||||||
|
if(g>=2&&g<=9)return String(g-1);
|
||||||
|
return String(g);
|
||||||
|
}
|
||||||
|
// fallback
|
||||||
|
if(g===0)return'N';
|
||||||
|
if(g>0)return String(g);
|
||||||
|
return String(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gearBarIndex(g){
|
||||||
|
if(!g&&g!==0)return-1;
|
||||||
|
const isForza=GAME_ID&&GAME_ID.includes('forza');
|
||||||
|
const isIRacing=GAME_ID&&GAME_ID.includes('iracing');
|
||||||
|
|
||||||
|
if(isForza){
|
||||||
|
if(g===0)return 0; // R at index 0
|
||||||
|
if(g>=1&&g<=8)return g+1; // 1->2
|
||||||
|
}
|
||||||
|
if(isIRacing){
|
||||||
|
if(g===0)return 1; // N at index 1
|
||||||
|
if(g===1)return 0; // R at index 0
|
||||||
|
if(g>=2&&g<=9)return g; // 2->2(index for '1')
|
||||||
|
}
|
||||||
|
// AC/F1/default
|
||||||
|
if(g===-1)return 0; // R
|
||||||
|
if(g===0)return 1; // N
|
||||||
|
if(g>=1&&g<=8)return g+1; // 1->2
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
(function(){
|
(function(){
|
||||||
const box=document.getElementById('box'),gearTxt=document.getElementById('gear-txt'),
|
const box=document.getElementById('box'),gearTxt=document.getElementById('gear-txt'),
|
||||||
gearEl=document.getElementById('gear-el'),barRow=document.getElementById('bar-row');
|
gearEl=document.getElementById('gear-el'),barRow=document.getElementById('bar-row');
|
||||||
@@ -91,21 +145,8 @@ const M={aspect_ratio:"1:1",render_mode:"contain",gear_style:"auto"};
|
|||||||
|
|
||||||
function updateGear(g){
|
function updateGear(g){
|
||||||
if(g===lastGear)return;lastGear=g;
|
if(g===lastGear)return;lastGear=g;
|
||||||
|
const label=gearLabel(g);
|
||||||
// Determine mapping based on actual gear value
|
const activeIndex=gearBarIndex(g);
|
||||||
let label,activeIndex;
|
|
||||||
if(g<0){// AC style: negative is reverse
|
|
||||||
label='R';activeIndex=0;
|
|
||||||
}else if(g===0){
|
|
||||||
// ambiguous: could be R (Forza) or N (AC)
|
|
||||||
// Show R by default for Forza, but check gear_style
|
|
||||||
label='R';activeIndex=0;
|
|
||||||
}else{
|
|
||||||
// Forward gear: g is 1-based in Forza, but some games might use 0-based
|
|
||||||
label=String(g);
|
|
||||||
if(g>=1&&g<=8)activeIndex=g+1; // gear 1 -> index 2
|
|
||||||
else activeIndex=-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
gearTxt.textContent=label;
|
gearTxt.textContent=label;
|
||||||
gearEl.className='gear-circle active';
|
gearEl.className='gear-circle active';
|
||||||
@@ -131,8 +172,10 @@ const M={aspect_ratio:"1:1",render_mode:"contain",gear_style:"auto"};
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connect(){
|
function connect(){
|
||||||
ws=new WebSocket(wsUrl);ws.onopen=()=>{if(rt){clearTimeout(rt);rt=null}};
|
ws=new WebSocket(wsUrl);ws.onopen=()=>{if(rt){clearTimeout(rt);rt=null}};
|
||||||
ws.onmessage=(e)=>{try{const m=JSON.parse(e.data);if(m.type==='telemetry')updateGear(m.data.gear||0)}catch(e){}};
|
ws.onmessage=(e)=>{try{const m=JSON.parse(e.data);
|
||||||
|
if(m.type==='connected'){GAME_ID=m.selected_game_id||'';console.log('[Gear] game:',GAME_ID)}
|
||||||
|
if(m.type==='telemetry')updateGear(m.data.gear||0)}catch(e){}};
|
||||||
ws.onclose=()=>{rt=setTimeout(connect,2000)};
|
ws.onclose=()=>{rt=setTimeout(connect,2000)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ const PageDashboard = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_openDashboard(themeId) {
|
_openDashboard(themeId) {
|
||||||
const url = `${location.origin}/dashboard/${themeId}`;
|
const gameParam = this._selectedGameId ? `?game=${this._selectedGameId}` : '';
|
||||||
|
const url = `${location.origin}/dashboard/${themeId}${gameParam}`;
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
Toast.show('链接已复制,可在局域网设备访问', 'success');
|
Toast.show('链接已复制,可在局域网设备访问', 'success');
|
||||||
|
|||||||
Reference in New Issue
Block a user