class Player(): def __init__(self): self.name = 'Player' self.points = 0 self.isPlayer = True self.isAttacking = False self.isAlive = True self.attackTimer = Timer(0.0) def advance(self, deltaTime: float): self.attackTimer.advance(deltaTime) if self.attackTimer.timeIsUp(): self.isAttacking = False self.attackTimer.setActive(False) def setAttacking(self, attackTime: float): self.isAttacking = True self.attackTimer.setTimer(attackTime) self.attackTimer.start() def setAlive(self, alive): self.isAlive = alive def __repr__(self): return "Player"
class StateChase(State): name = "chase" def __init__(self, brain): State.__init__(self, brain) meEnemy = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.enemy.Enemy) # basically move speed self.lastInputTimer = Timer(meEnemy.enemyInfo.chaseStepDelay, instant=True) # try attacking when timer is finished self.canAttackTimer = Timer() # we need to know player location, or we could just handle on every new # PlayerLocation message self.lastKnownPlayerPosition = None def on_enter(self): meEnemy = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.enemy.Enemy) stateTimeRnd = random.randrange(-100 * meEnemy.enemyInfo.chaseTimeRnd, 100 * meEnemy.enemyInfo.chaseTimeRnd) self.setTimer(meEnemy.enemyInfo.chaseTime + (stateTimeRnd / 100)) self.canAttackTimer.setTimer(meEnemy.enemyInfo.enemyCanAttackPeriod) self.canAttackTimer.reset() if not Config.allowEnemyAttacking: self.canAttackTimer.setActive(False) def tryAttacking(self): if self.canAttackTimer.timeIsUp(): logger.debug("{}: Check if i can attack player".format(self.name)) if self.canAttackPlayer(): if (EntityFinder.numEnemiesInState(self.brain.owner.world, 'attack') < Config.maxEnemiesInStateAttacking): self.brain.pop() self.brain.push("attackwindup") self.canAttackTimer.reset() def tryMoving(self): # only move if we can not hit him (even on cooldown) # this is quiet... taxing. and not really necessary? # if not self.canAttackPlayer(): if True: # movement speed, and direction if self.lastInputTimer.timeIsUp(): self.getInputChase() self.lastInputTimer.reset() def trySkill(self): # stickfigure has no skills pass def process(self, dt): meAttackable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.attackable.Attackable) if meAttackable.isStunned: return self.lastInputTimer.advance(dt) self.canAttackTimer.advance(dt) # update player position if new location self.checkForNewPlayerPosition() self.tryAttacking() # note that if we want to attack, as identified a few lines above, # we will be in state attackWindup, and not reach here self.trySkill() self.tryMoving() # switch to wander if exhausted if self.timeIsUp(): logger.debug("{}: Too long chasing, switching to wander".format( self.owner)) self.brain.pop() self.brain.push("wander") def checkForNewPlayerPosition(self): # check if there are any new player position messages for message in messaging.getByType(MessageType.PlayerLocation): self.lastKnownPlayerPosition = message.data def canAttackPlayer(self): if self.lastKnownPlayerPosition is None: # we may not yet have received a location. # find it directly via player entity # this is every time we go into chase state playerEntity = EntityFinder.findPlayer(self.brain.owner.world) # player not spawned if playerEntity is not None: playerRenderable = self.brain.owner.world.component_for_entity( playerEntity, system.graphics.renderable.Renderable) self.lastKnownPlayerPosition = playerRenderable.getLocationAndSize( ) canAttack = AiHelper.canAttackPlayer(self.brain.owner, self.lastKnownPlayerPosition) return canAttack def getInputChase(self): meGroupId = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.groupid.GroupId) meRenderable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.graphics.renderable.Renderable) if not Config.allowEnemyMovement: return moveX, moveY, dontChangeDirection = AiHelper.getAttackVectorToPlayer( self.owner, meRenderable) # only move if we really move a character if moveX != 0 or moveY != 0: directMessaging.add( groupId=meGroupId.getId(), type=DirectMessageType.moveEnemy, data={ 'x': moveX, 'y': moveY, 'dontChangeDirection': dontChangeDirection, 'updateTexture': True, 'force': False, }, )
class Attackable(): def __init__(self, initialHealth=100, stunTime=0.75, stunCount=3, stunTimeFrame=3, knockdownChance=0.0, knockbackChance=0.0): self.health = initialHealth self.initialHealth = initialHealth self.knockdownChance = knockdownChance self.knockbackChance = knockbackChance self.maxStunCount = stunCount self.stunTimeFrame = stunTimeFrame self.stunTime = stunTime self.isStunned = False self.stunTimer = Timer(0.0) self.stunTimer.setActive(False) self.stunnedQueue = collections.deque(maxlen=5) # after message GameRestart, all Renderables are deleted - but not until # the next advance(). Upon restarting the map, if the user presses a key, # checkHeal() in AttackableProcessor would emit yet another stray # GameOver message. This is the fix. self.isActive = True def setActive(self, active): self.isActive = active def isStunnable(self): if self.maxStunCount == 0: return False timeRef = system.singletons.gametime.getGameTime() - self.stunTimeFrame stunCount = 0 for stunned in self.stunnedQueue: if stunned['time'] > timeRef: stunCount += 1 if stunCount <= self.maxStunCount: logging.info( "Stun check: Can be stunned Cnt: {} max: {} time: {}".format( stunCount, self.maxStunCount, timeRef)) return True else: logging.info( "Stun check: Can not be stunned Cnt: {} max: {} time: {}". format(stunCount, self.maxStunCount, timeRef)) return False def addStun(self, stunTime): self.stunnedQueue.append({ 'time': system.singletons.gametime.getGameTime(), 'stunTime': stunTime }) def resetHealth(self): self.health = self.initialHealth def adjustHealth(self, health: int): self.health += health def getHealth(self): return self.health def getHealthPercentage(self): p = self.health / self.initialHealth return p def advance(self, dt): self.stunTimer.advance(dt) def setHealth(self, health): self.health = health self.initialHealth = health def setStunTime(self, stunTime): self.stunTime = stunTime def setStunTimeFrame(self, stunTimeFrame): self.stunTimeFrame = stunTimeFrame def setMaxStunCount(self, maxStunCount): self.maxStunCount = maxStunCount def setKnockdownChance(self, knockdownChance): self.knockdownChance = knockdownChance def setKnockbackChance(self, knockbackChance): self.knockbackChance = knockbackChance