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:
co-authored by
Claude Opus 5.5
parent
13a462b6fc
commit
ffe097ec57
@@ -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'])
|
||||
|
||||
Reference in New Issue
Block a user