whatever
This commit is contained in:
parent
2ac4e42645
commit
78e9f4de0e
176
codegen.py
176
codegen.py
|
@ -1,176 +0,0 @@
|
|||
import encode, decode, shared
|
||||
import tables
|
||||
import os
|
||||
|
||||
conf={}
|
||||
confp=os.path.expanduser('~/.config/swat.cfg') # This won't work on Windows. You'll need to tweak this to run it on Windows. Not my problem.
|
||||
|
||||
|
||||
defaults={'classes':[(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(0,0),(0,0),(1,3),(1,3),(1,3)],'guns':[(1,3),(1,3),(1,3),(1,3),(1,3),(0,0),(0,0),(1,3)],'armour':[(1,3),(1,3),(1,3),(1,3)],'traits':[(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3)],'specs':[(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3)],'talents':[(12,12),(1,3),(1,3),(1,3),(1,3),(1,3),(1,3)],'medals':{}}
|
||||
def add_user(name):
|
||||
namehash=encode.hash_name(name) # Store the hash so that if they request a different name that happens to have the same hash, it can just be stored together.
|
||||
if namehash in conf: conf[namehash]['names'].add(name)
|
||||
else: conf[namehash]=defaults.copy()|{'names':{name}}
|
||||
save_conf()
|
||||
global active_profile
|
||||
active_profile=namehash
|
||||
|
||||
def parse_conf_line(line):
|
||||
a=line.split(' ')
|
||||
out=[]
|
||||
for n in a:
|
||||
b=n.split('/')
|
||||
out.append((int(b[0]),int(b[1])))
|
||||
return out
|
||||
|
||||
def load_conf():
|
||||
if not os.path.exists(confp): return
|
||||
a=open(confp).read()
|
||||
for n in a.split('\n\n'):
|
||||
lines=n.split('\n')
|
||||
lines=list(filter(lambda x:x,lines))
|
||||
if not lines: continue
|
||||
header=lines[0].split(' ')
|
||||
nhash=int(header[0]); names=set(header[1:]) # Makes deduping way easier
|
||||
medals={}
|
||||
if ':' in lines[-1]: # Since this isn't used in any other line, it's safe
|
||||
medals={int(n.split(':')[0]):int(n.split(':')[1]) for n in lines.pop(-1).split(' ')}
|
||||
stuff={k:parse_conf_line(v) for k,v in zip(tables.displays_str[:-1],lines[1:])}
|
||||
conf[nhash]={'names':names}|stuff|{'medals':medals}
|
||||
update_all_talents()
|
||||
global active_profile
|
||||
active_profile=nhash
|
||||
|
||||
def unparse_conf_line(line):
|
||||
return ' '.join([str(n[0])+'/'+str(n[1]) for n in line])
|
||||
|
||||
def save_conf():
|
||||
write=''
|
||||
for k,v in conf.items():
|
||||
write+=' '.join([str(k)]+list(v['names']))+'\n'
|
||||
for n in map(lambda x:unparse_conf_line(v[x]),tables.displays_str[:-1]):
|
||||
write+=n+'\n'
|
||||
write+=' '.join(f'{h}:{m}' for h,m in v['medals'].items())+'\n'
|
||||
write+='\n'
|
||||
write=write[:-2]
|
||||
open(confp,'w').write(write)
|
||||
|
||||
def update_all_talents():
|
||||
for profile in conf:
|
||||
cl=dict(enumerate(conf[profile]['classes']))
|
||||
t=defaults['talents'].copy()
|
||||
conf[profile]['talents']=t
|
||||
for n in range(1,7):
|
||||
t[n]=update_talent(n,cl)
|
||||
|
||||
def update_talents(clas,profile=None):
|
||||
if profile is None: profile=active_profile
|
||||
cl=dict(enumerate(conf[profile]['classes']))
|
||||
for talent in tables.clas[clas][1:]: # Don't care about courage
|
||||
conf[profile]['talents'][talent]=update_talent(talent,cl)
|
||||
|
||||
def update_talent(talent,classes):
|
||||
a=enumerate(tables.clas); b=filter(lambda x:talent in x[1],a); c=list(map(lambda x:x[0],b)); d=filter(lambda x:x[0] in c,classes.items()); rel_classes=list(map(lambda x:x[1],d))
|
||||
ranks,caps=[],[]
|
||||
for n in rel_classes: ranks.append(n[0]); caps.append(n[1])
|
||||
ranks,caps=sorted(ranks),sorted(caps)
|
||||
lon=len(ranks)//2+len(ranks)%2
|
||||
return (ranks[-lon],caps[-lon])
|
||||
|
||||
def read_code(code,name=None,save=None):
|
||||
if name is None: name=active_profile; save2=True
|
||||
else: save2=False # Don't save spoofed codes unless explicitly told
|
||||
if save is None: save=save2
|
||||
data=list(decode.decode(code,name))
|
||||
check,validator=data.pop(-1),data.pop(-1)
|
||||
talent=data.pop(5)
|
||||
hero=data[0:6]
|
||||
show=list(map(lambda x:x[0][x[1]],zip(tables.displays,hero)))
|
||||
rank,cap=data[6:8]
|
||||
cap=tables.cap[cap]
|
||||
if rank==12: cap=12
|
||||
data=data[8:]
|
||||
if rank<9: show[-1]='Hidden' # Don't spoil the talent
|
||||
print(show)
|
||||
print(str(rank)+'/'+str(cap))
|
||||
print(data) # Extract medals and save them
|
||||
medals=data[1:-1]
|
||||
if not check: print('No name given, code validation not performed')
|
||||
else:
|
||||
if check!=validator: print('code did not validate:',validator,check); return
|
||||
else: # Do not save medals unless validation passed
|
||||
if any(medals): conf[active_profile]['medals'][shared.muxhero(hero[0],hero[3],hero[4])]=shared.muxmedals(*medals)
|
||||
print('code validated')
|
||||
for thing in zip(tables.displays_str[:-1],hero):
|
||||
saved=conf[active_profile][thing[0]][thing[1]]
|
||||
saved=(max(saved[0],rank),max(saved[1],cap))
|
||||
conf[active_profile][thing[0]][thing[1]]=saved
|
||||
update_talents(hero[0])
|
||||
if not save: return
|
||||
save_conf()
|
||||
|
||||
def mass_add():
|
||||
a=input('>')
|
||||
while a:
|
||||
try: read_code(a,false)
|
||||
except: pass
|
||||
a=input('>')
|
||||
save_conf()
|
||||
|
||||
def generate(officer=None,name=None):
|
||||
pro=conf[active_profile]
|
||||
if name is None: name=active_profile
|
||||
if officer is None: officer=input('> ')
|
||||
pieces=officer.split('/')
|
||||
c=pieces[0].lower()
|
||||
if 'mav' in c:
|
||||
gun=c[0]
|
||||
pieces[0]='mav'
|
||||
elif 'wm' in c:
|
||||
gun=c[0:2]
|
||||
pieces[0]='wm'
|
||||
else: gun=''
|
||||
out=[]
|
||||
if len(pieces)==6: rank=cap=int(pieces.pop(-1))
|
||||
else: rank,cap=12,12
|
||||
try:
|
||||
for n in zip(pieces,tables.displays_nogun,tables.displays_nogun_str):
|
||||
ind=lookup_piece(n[0],n[1],n[2])
|
||||
rank,cap=min(rank,pro[n[2]][ind][0]),min(cap,pro[n[2]][ind][1])
|
||||
if 0 in [rank,cap]: print(f"{n[0]} is locked, go unlock it first.")
|
||||
out.append(ind)
|
||||
if gun: out.insert(1,lookup_piece(gun,tables.guns,'guns',True))
|
||||
else: out.insert(1,tables.gunmaps_wrapped[out[0]])
|
||||
except LookupException as ex: print(ex); return
|
||||
rank,cap=min(rank,pro['guns'][out[1]][0]),min(cap,pro['guns'][out[1]][1]) # Derp2, forgot to actually cap on gun
|
||||
if 0 in [rank,cap]: return # Already warned about locks earlier.
|
||||
cap=(cap-(cap<10)+(cap>10))//3 # Derp
|
||||
try: out[-1]=tables.clas[out[0]].index(out[-1])
|
||||
except ValueError: print(f"{tables.classes[out[0]]} can't get talent {tables.talent[out[-1]]}, options are {list(map(lambda x:tables.talent[x],tables.clas[out[0]]))}"); return
|
||||
muxed=shared.muxhero(out[0],out[3],out[4])
|
||||
meds=[]
|
||||
if muxed in pro['medals']: meds=shared.muxmedals(pro['medals'][muxed])
|
||||
return encode.encode(name,*out,rank,cap,0,*meds)
|
||||
|
||||
def lookup_piece(piece,table,name,partial=False):
|
||||
if len(piece)<3: p=piece.upper()
|
||||
else: p=piece.capitalize() # Bloody Americans
|
||||
if not partial:
|
||||
try: return table.index(p)
|
||||
except ValueError: raise LookupException(f"Couldn't resolve {piece} from {name}")
|
||||
res=list(filter(lambda x:p in x,table))
|
||||
if len(res)!=1: raise LookupException(f"Couldn't resolve {piece} with partial match from {name} (found {len(res)} possibilities)") # Ambiguous lookups not allowed
|
||||
return table.index(res[0])
|
||||
class LookupException(Exception): pass
|
||||
|
||||
def ranks():
|
||||
for n,m in zip(tables.displays_str,tables.displays):
|
||||
a=zip(m,conf[active_profile][n])
|
||||
for o in a:
|
||||
print(f'{o[0]}: {o[1][0]}/{o[1][1]}',end=', ')
|
||||
print()
|
||||
|
||||
load_conf()
|
||||
|
||||
|
||||
if __name__=="__main__": print("Please open an interpreter and use 'import codegen', this script doesn't serve as an entrypoint on its own yet.")
|
55
decode.py
55
decode.py
|
@ -1,55 +0,0 @@
|
|||
import shared
|
||||
import tables
|
||||
import encode
|
||||
from functools import reduce
|
||||
|
||||
def decode(code,name=''): # This is going to be a huge function
|
||||
code=shared.delimit(code) # It didn't end up being that huge
|
||||
code=shared.scramble(code,True)
|
||||
code,validator=shared.combine(code)
|
||||
xp=int(code[-1]); seed=int(code[0])
|
||||
code=code[1:-1]
|
||||
data=unzip(code)
|
||||
xp+=data.pop(0)
|
||||
rank,cap,cob,lsa,rem,clas,armour,trait,spec,talent=data
|
||||
cob,lsa=bool(cob),bool(lsa)
|
||||
rank,cap,key,moh,pcc=unadjust_rank(rank,cap)
|
||||
if seed%2: seed-=1; clas+=10
|
||||
if clas==6: clas=[8,9,6][armour]; armour=3
|
||||
check=''
|
||||
oldclas=clas # Whoops this all needs to be done before globalising talent
|
||||
gun=tables.gunmaps[clas]
|
||||
clas-=(clas>8)+min(4,max(0,clas-10)) # WM and mav adjustments
|
||||
gtalent=tables.clas[clas][talent] # Globalise it lol
|
||||
# And do it *before* validation because it needs gtalent passed in now
|
||||
if name:
|
||||
check=encode.validate_data(name, oldclas,armour,trait,spec,gtalent, rank,cap,xp, key,moh,pcc,cob,lsa,rem)
|
||||
rank+=1 # This has to come after extracting medals
|
||||
return clas,gun,armour,trait,spec,talent,gtalent,rank,cap,xp,key,moh,pcc,cob,lsa,rem,seed,validator,check
|
||||
|
||||
############################
|
||||
# SUPPORTING FUNCTIONS BELOW
|
||||
############################
|
||||
|
||||
def unzip(code):
|
||||
code=int(code)
|
||||
out=[]
|
||||
weight=reduce(lambda x,y:x*y,tables.weights)
|
||||
for n in list(zip(tables.weights,['xp//10','rank','cap','cob','lsa','rem','clas','armour','trait','spec','talent']))[::-1]:
|
||||
shared.trace(code,weight,n,out)
|
||||
out.append(code//weight)
|
||||
code%=weight
|
||||
weight//=n[0]
|
||||
out[-1]*=10 # This is XP
|
||||
return out[::-1]
|
||||
|
||||
def unadjust_rank(rank,cap):
|
||||
key,moh,pcc=False,False,False
|
||||
if rank>8:
|
||||
pcc=bool((cap+1)%2)
|
||||
cap=2+(cap+1)//2
|
||||
if rank>11: # Actually need to check this on the way out
|
||||
key=rank>12
|
||||
moh=bool((rank-11)%2)
|
||||
rank=11
|
||||
return rank,cap,key,moh,pcc
|
76
encode.py
76
encode.py
|
@ -1,76 +0,0 @@
|
|||
import shared
|
||||
import random
|
||||
import tables
|
||||
|
||||
def encode(name,clas,gun,armour,trait,spec,talent,rank=-1,cap=-1,xp=0, key=False,moh=False,pcc=False,cob=False,lsa=False,rem=False,seed=-1):
|
||||
if rank==-1: # actually means gun wasn't given, so shift them
|
||||
gun,armour,trait,spec,talent,rank=-1,gun,armour,trait,spec,talent
|
||||
gtalent=tables.clas[clas][talent]
|
||||
if clas>9: clas+=5 # Shift tech/Alice up to their positions - always
|
||||
# ... And do it before modifying clas below.
|
||||
if gun!=-1:
|
||||
#encode gun into class
|
||||
if clas==9: clas+=gun+1 # Because 9 outside is Mav, and 9 inside is GLWM
|
||||
if clas==8: clas+=gun-5
|
||||
rank-=1 # This actually DOES affect part 2 for some fucking reason
|
||||
cap=max(cap,(rank)//3+(rank>9)) #Sanity-check
|
||||
code1=encode_data(clas,armour,trait,spec,talent,rank,cap,xp, key,moh,pcc,cob,lsa,rem,seed)
|
||||
code2=validate_data(name,clas,armour,trait,spec,gtalent,rank,cap,xp, key,moh,pcc,cob,lsa,rem)
|
||||
shared.trace(code1,code2)
|
||||
code=shared.combine(code1,code2)
|
||||
shared.trace(code)
|
||||
return shared.delimit(shared.scramble(code),'-')
|
||||
|
||||
def encode_data(clas,armour,trait,spec,talent,rank,cap=-1,xp=0, key=False,moh=False,pcc=False,cob=False,lsa=False,rem=False,seed=-1):
|
||||
if clas==6: armour=2 # Borg is heavy borg
|
||||
if clas==8: clas=6; armour=0 # LR WM is light borg
|
||||
if clas==9: clas=6; armour=1 # GL WM is med borg
|
||||
seed,clas=rand(seed,clas)
|
||||
rank,cap=adjust_rank(rank,cap,key,moh,pcc)
|
||||
code1=0
|
||||
weight=1
|
||||
for n in list(zip(tables.weights,[xp//10,rank,cap,cob,lsa,rem,clas,armour,trait,spec,talent])):
|
||||
shared.trace(code1,weight,n[0],n[1])
|
||||
weight*=n[0]
|
||||
code1+=n[1]*weight
|
||||
return str(seed)+shared.fill0s(code1,9)+str(xp%10)
|
||||
|
||||
def validate_data(name,clas,armour,trait,spec,talent,rank,cap=0,xp=0, key=False,moh=False,pcc=False,cob=False,lsa=False,rem=False):
|
||||
# gtalent is now looked up outside, before class modification - because I've adjusted that table to be more useful in general.
|
||||
if isinstance(name,str): name=hash_name(name)
|
||||
fac1=[171,142,175,157,167,150,149,151,153,165]
|
||||
fac2=[169,170,166,173,158,177,161,180,186,159] # I don't understand these, but I hope they work.
|
||||
code2=name*(spec+1) +(trait+4)*(trait+6) +(rank+1)*(xp+1) +(talent+1)*43*fac1[name%10] -(clas+1)*(241+fac2[name%10]) -(rem+1)*50 +(key+1)*4 +(moh+1)*9 +(pcc+1)*19 +(cob+1)*39 +(lsa+1)*79 +(armour+1)*159
|
||||
code2+=100*(cap+1)*(code2%1000) # To be honest I don't understand any of this. It's just random nonsense.
|
||||
while code2>99999:
|
||||
code2=code2%100000+code2//100000
|
||||
return shared.fill0s(code2,5)
|
||||
|
||||
############################
|
||||
# SUPPORTING FUNCTIONS BELOW
|
||||
############################
|
||||
|
||||
def getval(char):
|
||||
vals='_9483726150rstlmeaiuonycdpjkhgxwfvqzb(-[.!' # szszss'
|
||||
#vals='_5698472031aeioyusptndchbrxvzjmlkwgfq-!.([' # meebs'
|
||||
if char in vals: return vals.index(char)+1
|
||||
return 43*len(char.encode('utf-8')) # Apparently multibyte chars do this
|
||||
|
||||
def hash_name(name):
|
||||
parsed=name.lower().replace(')','(').replace(']','[')
|
||||
checksum=0
|
||||
for i in range(len(parsed)):
|
||||
checksum+=getval(parsed[i])*((i+1)%3+1)
|
||||
return checksum
|
||||
|
||||
def adjust_rank(rank,cap=-1, key=False,moh=False,pcc=False):
|
||||
rank+=key*2+moh # Invalid if rank <11, but whatever
|
||||
if rank>8: #Can't have PCC below r10, and there are only two possible cap states after that
|
||||
cap=(cap==4)*2+pcc+1 # I don't know why pcc is encoded like this, I would have thought cap would behave normally if not pcc, but nope.
|
||||
return rank,cap
|
||||
|
||||
def rand(seed,clas):
|
||||
if seed>-1: n=seed
|
||||
else: n=int(random.random()*5)*2
|
||||
if clas>9: return n+1,clas-10
|
||||
return n,clas
|
38
shared.py
38
shared.py
|
@ -1,38 +0,0 @@
|
|||
tracing=False
|
||||
def trace(*args,**kwargs):
|
||||
if tracing: print(*args,**kwargs)
|
||||
|
||||
def delimit(code,spacer=''):
|
||||
if not spacer:
|
||||
if len(code)==16: return code
|
||||
if len(code)!=19: raise Exception('code length should be 19 for despacing, got',len(code),'from',code)
|
||||
return code[0:4]+code[5:9]+code[10:14]+code[15:19]
|
||||
if spacer:
|
||||
if len(code)==19: return code
|
||||
if len(code)!=16: raise Exception('code length should be 16 for spacing, got',len(code),'from',code)
|
||||
return code[0:4]+spacer+code[4:8]+spacer+code[8:12]+spacer+code[12:16]
|
||||
|
||||
def scramble(code,unscramble=False):
|
||||
n=int(code[0])
|
||||
if unscramble: n=15-n # Because the 1st digit is left alone this needs to be +1
|
||||
return code[0]+code[16-n:16]+code[1:16-n]
|
||||
|
||||
def combine(code,namehash=''):
|
||||
if namehash: return code[0:2]+namehash[4:5]+code[2:6]+namehash[3:4]+code[6:8]+namehash[1:3]+code[8:11]+namehash[0:1]
|
||||
charcode=code[0:2]+code[3:7]+code[8:10]+code[12:15]
|
||||
namehash=code[15]+code[10:12]+code[7]+code[2]
|
||||
return charcode,namehash
|
||||
|
||||
def fill0s(num,zeroes=0):
|
||||
if not zeroes: return str(int(num))
|
||||
n=str(num)
|
||||
if len(n)>zeroes: raise Exception(f"can't pad {n} ({len(n)}) with {zeroes} 0s")
|
||||
return '0'*(zeroes-len(n))+n
|
||||
|
||||
def muxhero(clas,trait,spec):
|
||||
return clas+trait*12+spec*12*16 # It's actually stored backwards like this, spec is in the most significant bits. Whatever, it doesn't need to be reversable.
|
||||
|
||||
def muxmedals(key,moh=-1,pcc=-1,cob=-1,lsa=-1,rem=-1):
|
||||
print(key,moh,pcc,cob,lsa,rem)
|
||||
if moh!=-1: return key+moh*2+pcc*4+cob*8+lsa*16+rem*32
|
||||
return bool(key&1),bool(key&2),bool(key&4),bool(key&8),bool(key&16),key//32
|
34
tables.py
34
tables.py
|
@ -1,34 +0,0 @@
|
|||
#Data tables: Mostly used by the codegen itself
|
||||
clas=[
|
||||
[0,1,2,5], # Sniper: Crg, wire, run, tinker
|
||||
[0,1,4,6], # Medic: Crg, wire, tough, hack
|
||||
[0,1,2,6], # Tact: Crg, wire, run, hack
|
||||
[0,1,3,6], # Psy: Crg, wire, spot, hack
|
||||
[0,2,4,5], # HO: Crg, run, tough, tinker
|
||||
[0,2,4,3], # Demo: Crg, run, tough, spot
|
||||
[0,3,2,6], # Borg: Crg, spot, run, hack
|
||||
[0,3,5,4], # Pyro: Crg, spot, tinker, tough
|
||||
[0,4,5,3], # WM (LR, GL): Crg, tough, tinker, spot
|
||||
[0,2,6,5], # Mav (AR, SR, CG, RL, F): Crg, run, hack, tinker
|
||||
[0,1,5,6], # Tech: Crg, wire, tinker, hack
|
||||
[0,1,3,4] # Alice: Crg, wire, spot, tough
|
||||
]
|
||||
weights=[1,50,15,5,2,2,4,8,3,18,9]
|
||||
|
||||
#Display tables: Mostly used by the rank code manager
|
||||
classes=['Sniper','Medic','Tact','Psy','HO','Demo','Borg','Pyro','WM','Mav','Tech','Alice']
|
||||
guns=['AR','SR','CG','RL','F','LR','GL','PF']
|
||||
gunmaps=[1,0,0,0,2,3,2,4,5,6,0,1,2,3,4,1,7] # This is unwrapped mappings so every value matters
|
||||
gunmaps_wrapped=[1,0,0,0,2,3,2,4,-1,-1,1,7] # Wrapped mappings return -1 for classes that have gun choices. Hopefully you'll never use it with them.
|
||||
armour='LMHA'
|
||||
trait=['SK', 'Gift', 'Surv', 'Goon', 'Acro', 'SL', 'Healer', 'FC', 'CR', 'RR', 'Gadg', 'Prowl', 'Gizer', 'PR', 'Engi', 'Reck']
|
||||
spec=['Weap', 'PA', 'Cells', 'Cyber', 'Tri', 'Chem', 'Leader', 'Robo', 'Esp']
|
||||
talent=['Crg','Wire','Run','Spot','Tough','Tinker','Hack']
|
||||
#Convenience
|
||||
displays=[classes,guns,armour,trait,spec,talent]
|
||||
displays_str=['classes','guns','armour','traits','specs','talents']
|
||||
displays_nogun=[classes,armour,trait,spec,talent]
|
||||
displays_nogun_str=['classes','armour','traits','specs','talents']
|
||||
rank=range(12)
|
||||
xp=range(2500)
|
||||
cap=[3, 6, 9, 10, 11]
|
Loading…
Reference in New Issue
Block a user