rance/actions.py

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"