171 lines
6.9 KiB
Python
171 lines
6.9 KiB
Python
import random as ra
|
|
import json as j
|
|
from os.path import dirname,join
|
|
with open(join(dirname(__file__),'data.json')) as d:
|
|
data=j.load(d)
|
|
classtable=data['classes']
|
|
from . import decos
|
|
|
|
@decos.target()
|
|
@decos.guardcheck
|
|
def attack(self,other,*_):
|
|
admg,ddmg=_dealdmg(self,other)
|
|
return admg,ddmg,f"{self.name} dealt {admg} to {other.name}" + (f", received {ddmg} counter damage" if ddmg else "")
|
|
|
|
def allattack(self,*_):
|
|
# We'll start at this code and attack 6 units
|
|
start=(self.code//6+1)%2*6
|
|
units=self.board.getunits(*list(range(start,start+6)),codes=True)
|
|
ascore=dscore=0
|
|
for other in units:
|
|
# That would be an embarrassing bug.
|
|
if other is None: continue
|
|
if 'guard' in other.status:
|
|
other.status['guard']-=0.4
|
|
if other.status['guard']<=0: other.status.pop('guard')
|
|
admg,ddmg=_dealdmg(self,other)
|
|
ascore+=admg
|
|
dscore+=ddmg
|
|
return round(ascore*2/3),dscore,f"{self.name} dealt a total of {ascore} damage (earning {round(ascore*2/3)} points) to enemy units" + (f", and received a total of {dscore} damage in return" if dscore else "")
|
|
|
|
def _dealdmg(self,other,melee=None):
|
|
sinfo=classtable[self.cls]
|
|
oinfo=classtable[other.cls]
|
|
if melee is None: amelee='amelee' in sinfo['attrs']
|
|
else: amelee=melee
|
|
counter=amelee & ('counter' in oinfo['attrs'])
|
|
admg=_calcdmg(self,other,'magic' in sinfo['attrs'],self.cls!='musket')
|
|
if counter: ddmg=_calcdmg(other,self,'magic' in oinfo['attrs'],True)
|
|
else: ddmg=0
|
|
if not (type(admg)==bool and admg==False): # We didn't pop a shield, proceed with popping charge
|
|
if 'cancel' in sinfo['attrs']: other.status.pop('charging',None) # I'll need to mess with this later to make it handle super charge.
|
|
other.cur-=admg
|
|
self.cur-=ddmg
|
|
if other.cur<=0: o=other.die(); admg+=o[0]; ddmg+=o[1]
|
|
if self.cur<=0: s=self.die(); admg+=s[1]; ddmg+=s[0]
|
|
return admg,ddmg
|
|
|
|
def _calcdmg(self,other,magic=False,capped=True,counter=False):
|
|
# This only deals damage one direction. It's like a one-liner.
|
|
# Well but if it's magic damage, and protection status...
|
|
if 'protected' in other.status: other.status.pop('protected'); return 0
|
|
if not magic:
|
|
dmg=round((1.15**(self.atk-other.dfn)) \
|
|
*((self.cur*1000)**0.5)*0.1 \
|
|
*(0.6 if 'defend' in other.status else 1) \
|
|
* (0.5 if counter else 1))
|
|
else:
|
|
dmg=round((1.15**(self.atk*0.2+self.int-other.dfn*0.2-other.int)) \
|
|
*((self.cur*1000)**0.5)*0.1 \
|
|
*(0.6 if 'defend' in other.status else 1) \
|
|
* (0.5 if counter else 1))
|
|
if capped: return min(dmg,other.cur,self.cur)
|
|
else: return min(dmg,other.cur)
|
|
|
|
def guard(self,*_):
|
|
try: self.status['guard']+=self.int*0.2
|
|
except KeyError: self.status['guard']=self.int*0.2
|
|
self.status['defend']=True
|
|
return 0,0,f"{self.name} gains {self.int*0.2:.3} guard value"
|
|
|
|
@decos.target(ranged=True)
|
|
@decos.guardcheck
|
|
def assassinate(self,other,*_):
|
|
other.status.pop('protected',None)
|
|
chance=self.int*(self.cur/other.cur)*0.07
|
|
roll=ra.random()
|
|
if roll<chance:
|
|
o=other.die()
|
|
return 50+o[0],0+o[1],f"{self.name} assassinates {other.name} at {round(chance*100)}% chance!"
|
|
return 0,0,f"{self.name} fails to assassinate {other.name} at {round(chance*100)}% chance!"
|
|
|
|
@decos.target(ally=True)
|
|
def shield(self,other,*_):
|
|
# Actual real simple like
|
|
other.status['protected']=True
|
|
return 0,0,f"{self.name} shields {other.name}"
|
|
|
|
def guardcancel(self,*_):
|
|
start=(self.code//6+1)%2*6
|
|
for other in self.board.getunits(*list(range(start,start+6)),codes=True):
|
|
other.status.pop('guard',None); other.status.pop('defend',None)
|
|
return 0,0,f"{self.name} breaks all enemy guard"
|
|
|
|
@decos.target(ally=True)
|
|
def heal(self,other,*_):
|
|
healval=round(min(self.cur*0.03*self.int,other.max-other.cur))
|
|
other.cur+=healval
|
|
return 0,0,f"{self.name} heals {other.name} for {healval}"
|
|
|
|
def regenerate(self,*_):
|
|
heal=round(min(max(self.max*0.05,self.cur*0.2),self.max-self.cur))
|
|
self.cur+=heal
|
|
return 0,0,f"{self.name} heals itself for {heal}"
|
|
|
|
def ratingup(self,*_):
|
|
# Actual real final simple like ver1.0.1
|
|
# HAHAHAHAHA FORGOT TO MAKE THIS RETURN INTS INSTEAD OF FLOATS
|
|
return (round(75+15*self.int),0,f"{self.name} boosts score by {round(75+15*self.int)}")
|
|
|
|
@decos.target(ally=True)
|
|
def buff(self,other,*_):
|
|
buffnum=self.cur//100+1
|
|
targnum=buffnum//2 # This is actually +1 but ssh, we already have that one allocated.
|
|
start=self.code//6%2*6 # This actually evaluates correctly without the parens
|
|
targs=list(filter(lambda x:x.code!=other.code, self.board.getunits(*list(range(start,start+6)),codes=True)))
|
|
targs=ra.sample(targs,targnum)
|
|
targs.append(other)
|
|
out=[]
|
|
for n in targs:
|
|
out.extend([(n,m) for m in ['atk','def','int','spd'] if m not in n.status])
|
|
out2=[]
|
|
if len(out)<buffnum:
|
|
for n in targs: out2.extend([(n,m) for m in ['atk','def','int','spd'] if m in n.status])
|
|
out2=ra.sample(out2,buffnum-len(out))
|
|
out.extend(out2)
|
|
else: out=ra.sample(out,buffnum)
|
|
# We must always assume there are buffs that could get replaced, so we need to check if they should be.
|
|
for n in out:
|
|
# These are the stats to be buffed, paired with the unit on which they should be.
|
|
try: n[0].status[n[1]]=max(n[0].status[n[1]],0.1*self.int)
|
|
except KeyError: n[0].status[n[1]]=0.1*self.int
|
|
return 0,0,f"{self.name} buffs {', '.join([t.name for t in targs])}"
|
|
|
|
def debuff(self,*_):
|
|
targnum=self.cur//250+1
|
|
start=(self.code//6+1)%2*6 # This doesn't evaluate correctly without the parens; but that's all it needs.
|
|
targs=self.board.getunits(*list(range(start,start+6)),codes=True)
|
|
weights=[sum([True for n in ['atk','def','int','spd'] if n in i.status]) for i in targs]
|
|
# I'll probably move this out to utils later
|
|
# I have no idea what the above line is about. Maybe it's because it was so badly written. It's because of some straight bullshit. I will move it to utils now and do it properly.
|
|
# Didn't move it to utils but I did it properly. Fucking hell python, you're supposed to be good at this stuff.
|
|
out=_weighted_sample(targs,weights,targnum)
|
|
#out=[]
|
|
#for _ in range(targnum):
|
|
# out.append(weights.pop(ra.choices(list(range(len(weights))),weights)[0]))
|
|
# This should be our target list
|
|
for n in out: print(f'debuffing {n}'); n.status.pop('atk',None); n.status.pop('def',None); n.status.pop('int',None); n.status.pop('spd',None)
|
|
return 0,0,f"{self.name} clears buffs from {', '.join([t.name for t in out])}"
|
|
|
|
def _weighted_sample(items,weights,count=1,cum=False):
|
|
# Weights are not assumed to be cumulative by default.
|
|
if not len(items)==len(weights): raise ValueError('Length of weights should be the same as length of items.')
|
|
if cum:
|
|
for i in range(len(weights)):
|
|
weights[i]-=sum(weights[:i]) # Change from cumulative to arbitrary
|
|
out=[]
|
|
for _ in range(count):
|
|
pick=int(ra.random()*sum(weights))
|
|
i=0
|
|
while pick>weights[i]: pick-=weights[i];i+=1;
|
|
out.append(items[i])
|
|
items.pop(i); weights.pop(i) # Update list for next run
|
|
return out
|
|
|
|
def rest(self,*_):
|
|
# Real simple like
|
|
# Not so simple like
|
|
heal=round(max(self.max*0.015,min(self.cur*0.07,self.max-self.cur)))
|
|
self.cur+=heal
|
|
return 0,0,f"{self.name} rests and recovers {heal} troops"
|