forge/blade/entity/player.py (140 lines of code) (raw):
import numpy as np
from forge.blade.systems import ai
from forge.blade.lib.enums import Material, Neon
from pdb import set_trace as T
class Stat:
def __init__(self, val, maxVal):
self._val = val
self._max = maxVal
def increment(self, amt=1):
self._val = min(self.max, self.val + amt)
def decrement(self, amt=1):
self._val = max(0, self.val - amt)
def center(self):
return (self.val - self.max/2.0) / self.max
@property
def val(self):
return self._val
@property
def max(self):
return self._max
def packet(self):
return {'val': self.val, 'max': self.max}
class Player:
public = set(
'pos lastPos R C food water health entID annID name colorInd color timeAlive kill attackMap damage freeze immune'.split())
def __init__(self, entID, color, config):
self._config = config
self._R, self._C = config.R, config.C
self._pos = config.SPAWN()
self._lastPos = self.pos
self._food = Stat(config.FOOD, config.FOOD)
self._water = Stat(config.WATER, config.WATER)
self._health = Stat(config.HEALTH, config.HEALTH)
self._entID = entID
self._name = 'Neural_' + str(self._entID)
self._timeAlive = 0
self._damage = None
self._freeze = 0
self._immune = True
self._kill = False
self._annID, self._color = color
self._colorInd = self._annID
self._attackMap = np.zeros((7, 7, 3)).tolist()
self._index = 1
self._immuneTicks = 15
self.move = None
self.attack = None
def __getattribute__(self, name):
if name in Player.public:
return getattr(self, '_' + name)
return super().__getattribute__(name)
def __setattr__(self, name, value):
if name in Player.public:
raise AttributeError('Property \"' + name + '\" is read only: agents cannot modify their server-side data')
return super().__setattr__(name, value)
def packet(self):
data = {}
for key in Player.public:
val = getattr(self, key)
data[key] = val
if hasattr(val, 'packet'):
data[key] = val.packet()
if self.attack is not None:
data['attack'] = {
'style': self.attack.action.__name__,
'target': self.attack.args.entID}
return data
#PCs interact with the world only through stimuli
#to prevent cheating
def decide(self, packets):
action, args = self.cpu.decide(self, packets)
return action, args
def forage(self, world):
r, c = self._pos
isForest = type(world.env.tiles[r, c].mat) in [Material.FOREST.value]
if isForest and world.env.harvest(r, c):
self.food.increment(5)
isWater = Material.WATER.value in ai.adjacentMats(world.env, self._pos)
if isWater:
self.water.increment(5)
def lavaKill(self, world):
r, c = self._pos
if type(world.env.tiles[r, c].mat) == Material.LAVA.value:
self._kill = True
return self._kill
def updateStats(self):
if (self._food.val > self._food.max//2 and
self._water.val > self._water.max//2):
self._health.increment()
self._water.decrement()
self._food.decrement()
if self._food.val <= 0:
self._health.decrement()
if self._water.val <= 0:
self._health.decrement()
def updateCounts(self, world):
r, c = self._pos
world.env.tiles[r, c].counts[self._colorInd] += 1
def mapAttack(self):
if self.attack is not None:
attack = self.attack
name = attack.action.__name__
if name == 'Melee':
attackInd = 0
elif name == 'Range':
attackInd = 1
elif name == 'Mage':
attackInd = 2
rt, ct = attack.args.pos
rs, cs = self._pos
dr = rt - rs
dc = ct - cs
if abs(dr)<=3 and abs(dc)<=3:
self._attackMap[3+dr][3+dc][attackInd] += 1
def step(self, world):
if not self.alive: return
self._freeze = max(0, self._freeze-1)
self.updateCounts(world)
if self.lavaKill(world): return
self.forage(world)
self.updateStats()
self._damage = None
self._timeAlive += 1
self.updateImmune()
def act(self, world, actions, arguments, val):
if not self.alive: return
self.mapAttack()
self.val = val
self._lastPos = self._pos
for action, args in zip(actions, arguments):
action.call(world, self, *args)
@property
def alive(self):
return self._health.val > 0
def updateImmune(self):
if self._timeAlive >= self._immuneTicks:
self._immune = False
#Note: does not stack damage, but still applies to health
def applyDamage(self, damage):
if self.immune:
return
self._damage = damage
self._health.decrement(damage)