Add world boss window (AVA2 uitrack world-boss tab, read-only)

mt_boss lists the server's timed boss regens from the MTF1 "bosses"
query: alive/max, countdown, map-local position and period, paged by 8,
with per-boss spawn reminders that only fire on a fresh alive snapshot.
Teleport is left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-10-15 22:36:04 +09:00
co-authored by Claude Opus 5.5
parent 13a462b6fc
commit ffe097ec57
8 changed files with 468 additions and 8 deletions
@@ -6,7 +6,8 @@ import net
import ui
from mt_wiki_data import Number
BITS={'mob':1,'chest':2,'stats':4,'sources':8,'dungeons':16,'calendar':32}
BITS={'mob':1,'chest':2,'stats':4,'sources':8,'dungeons':16,'calendar':32,'bosses':256}
WHOLE=('stats','dungeons','bosses') # one reply with every row; no subject or page
g_service=None
g_tick=None
_serial=0
@@ -27,7 +28,7 @@ class Service(object):
global _serial
if kind not in BITS or not 0<=subject<=4294967295 or not 0<=offset<=4096 or len(self.queue)>=8:
callback(None,'invalid');return 0
if (kind in ('stats','dungeons') and (subject or offset)) or (kind=='calendar' and subject!=1):
if (kind in WHOLE and (subject or offset)) or (kind=='calendar' and subject!=1):
callback(None,'invalid');return 0
_serial=(_serial%2147483647)+1
self.queue.append(dict(id=_serial,kind=kind,subject=subject,offset=offset,callback=callback,retries=0))
@@ -53,7 +54,7 @@ class Service(object):
if not self.mask&BITS[p['kind']]:
self.Finish(None,'unsupported');return
text='/mt_features 1 %d %s'%(p['id'],p['kind'])
if p['kind'] not in ('stats','dungeons'):text+=' %d %d'%(p['subject'],p['offset'])
if p['kind'] not in WHOLE:text+=' %d %d'%(p['subject'],p['offset'])
net.SendChatPacket(text)
self.deadline=now+4.0;self.nextSend=now+0.3
@@ -80,15 +81,15 @@ class Service(object):
elif op=='begin' and len(w)==9 and p['kind']!='cap':
if 'result' in p or w[3]!=p['kind'] or Number(w[4])!=p['subject'] or Number(w[5])!=p['offset']:
raise ValueError('mismatched response')
total=Number(w[6],128 if p['kind'] in ('stats','dungeons') else 256 if p['kind']=='calendar' else 4096)
total=Number(w[6],128 if p['kind'] in WHOLE else 256 if p['kind']=='calendar' else 4096)
if total<p['offset']:raise ValueError('invalid page')
p['result']=dict(kind=w[3],subject=p['subject'],offset=p['offset'],total=total,mode=Number(w[7],11),
time=Number(w[8]),rows=[])
elif op in ('row','stat','event') and 'result' in p:
elif op in ('row','stat','event','boss') and 'result' in p:
result=p['result'];rows=result['rows']
limit=result['total'] if p['kind'] in ('stats','dungeons') else min(8,result['total']-p['offset'])
limit=result['total'] if p['kind'] in WHOLE else min(8,result['total']-p['offset'])
if len(rows)>=limit or Number(w[3])!=p['offset']+len(rows):raise ValueError('invalid row order')
if op=='row' and len(w)==12 and p['kind'] not in ('stats','dungeons','calendar'):
if op=='row' and len(w)==12 and p['kind'] not in WHOLE+('calendar',):
values=[Number(v) for v in w[4:]]
if values[2] not in (0,1,2,3,4,5,10,11,20,21) or not values[0]:raise ValueError('invalid row')
rows.append(values)
@@ -97,12 +98,17 @@ class Service(object):
if not values[0] or values[1] not in (1,2) or values[2]>=values[3] or values[4]>255 or values[5]>3 or values[6]>1:raise ValueError('invalid calendar event')
if any(r[0]==values[0] for r in rows):raise ValueError('duplicate event')
rows.append(values)
elif op=='boss' and len(w)==13 and p['kind']=='bosses':
# VNUM MAP X Y CHANNEL ALIVE MAX NEXT PERIOD; X/Y are map-local and may be negative.
values=[Number(w[4]),Number(w[5]),Signed(w[6]),Signed(w[7])]+[Number(v) for v in w[8:]]
if not values[0] or not values[1] or values[4]>255 or values[6]>255 or values[5]>values[6] or not values[8]:raise ValueError('invalid boss')
rows.append(values)
elif op=='stat' and len(w)==6 and p['kind'] in ('stats','dungeons'):
if not w[4].replace('_','').isalpha() or len(w[4])>32 or any(r[0]==w[4] for r in rows):raise ValueError('invalid stat')
rows.append((w[4],Signed(w[5])))
else:raise ValueError('invalid row format')
elif op=='end' and len(w)==4 and 'result' in p:
result=p['result'];expected=result['total'] if p['kind'] in ('stats','dungeons') else min(8,result['total']-p['offset'])
result=p['result'];expected=result['total'] if p['kind'] in WHOLE else min(8,result['total']-p['offset'])
if Number(w[3])!=expected or len(result['rows'])!=expected:raise ValueError('incomplete reply')
if p['kind']=='dungeons':
values=dict(result['rows'])