class RamboStateStanding(State): name = "standing" def __init__(self, brain): super().__init__(brain) self.canAttackTimer = Timer() def on_enter(self): self.canAttackTimer.setTimer(2.0) meGroupId = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.groupid.GroupId) messaging.add( type=MessageType.EntityStanding, groupId=meGroupId.getId(), data=None ) def process(self, dt): self.canAttackTimer.advance(dt) self.tryAttack() def tryAttack(self): if self.canAttackTimer.timeIsUp(): self.brain.pop() self.brain.push("attacking")
class SceneLogo(SceneBase): def __init__(self, viewport, world): super().__init__(world=world, viewport=viewport) # teh logo coordinates = Coordinates(2, 5) textureLogo = PhenomenaTexture(phenomenaType=PhenomenaType.intro) self.renderableLogo = Renderable( texture=textureLogo, viewport=self.viewport, coordinates=coordinates, active=True, ) self.anyKeyFinishesScene = True self.timer = Timer(3) self.sceneFinished = False self.name = "Scene0 - Logo" def enter(self): self.addRenderable(self.renderableLogo) def sceneIsFinished(self): if self.timer.timeIsUp(): return True else: return False def advance(self, dt): self.timer.advance(dt)
class DragonStateChase(StateChase): def __init__(self, brain): super().__init__(brain) self.canSkillTimer = Timer() def on_enter(self): super().on_enter() self.canSkillTimer.setTimer(3.0) def process(self, dt): super().process(dt) self.canSkillTimer.advance(dt) def trySkill(self): if self.canSkillTimer.timeIsUp(): meRenderable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.graphics.renderable.Renderable) locCenter = meRenderable.getLocationCenter() messaging.add(type=MessageType.EmitParticleEffect, data={ 'location': locCenter, 'effectType': ParticleEffectType.dragonExplosion, 'damage': 50, 'byPlayer': False, 'direction': Direction.none, }) self.canSkillTimer.reset()
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 Defense(): def __init__(self): self.coordinates = None self.timer = Timer(1.0) self.isActive = False def advance(self, dt): self.timer.advance(dt) if self.timer.timeIsUp(): self.isActive = False
class Texture(object): def __init__(self, type, width=0, height=0, name=''): self.type = type self.width: int = width self.height: int = height self.active: bool = True self.name = name # color related self.overwriteColorTimer = Timer(0.25, active=False) self.overwriteColor = None def init(self): pass def draw(self, viewport): pass def advance(self, deltaTime: float): # reset overwrite color if self.overwriteColorTimer.timeIsUp(): self.overwriteColor = None self.overwriteColorTimer.stop() self.overwriteColorTimer.advance(deltaTime) def setOverwriteColorFor(self, time: float, color: Color): if self.overwriteColorTimer.isActive(): logger.debug("{} Color already active on new set color".format( self.name)) self.overwriteColor = color self.overwriteColorTimer.setTimer(time) self.overwriteColorTimer.reset() def advanceStep(self): pass def setActive(self, active: bool): self.active = active def isActive(self) -> bool: return self.active def setName(self, name: str): self.name = name def __repr__(self): return self.name
class DamageStat(object): def __init__(self): self.damage = 100 self.dmgTimer = Timer(1.0) def addDamage(self, damage): self.damage += damage def process(self, dt): self.dmgTimer.advance(dt) if self.dmgTimer.timeIsUp(): self.dmg -= 10 self.dmgTimer.reset() def getDamageStat(self): return self.damage
class StateAttack(State): name = "attack" def __init__(self, brain): State.__init__(self, brain) self.attackTimer = Timer(instant=True) def on_enter(self): meEnemy = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.enemy.Enemy) meGroupId = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.groupid.GroupId) self.attackTimer.setTimer(meEnemy.enemyInfo.attackTime) self.setTimer(meEnemy.enemyInfo.attackStateTime) messaging.add( type=MessageType.EntityAttack, groupId=meGroupId.getId(), data=None ) def process(self, dt): self.attackTimer.advance(dt) if self.attackTimer.timeIsUp(): logger.info(self.name + " I'm attacking, via offensiveattack!") self.attackTimer.reset() offensiveAttack = self.brain.owner.world.component_for_entity( self.brain.owner.entity, OffensiveAttack) offensiveAttack.attack() if self.timeIsUp(): # too long attacking. lets switch to chasing logger.info("{}: Too long attacking, switch to chasing".format(self.owner)) self.brain.pop() self.brain.push("chase")
class TextureMinimal(object): def __init__( self, char: str, colorArr, timeArr, movementX: int = 0, movementY: int = 0, ): self.width = 1 self.height = 1 self.char = char self.movementX = movementX self.movementY = movementY self.timeArr = timeArr self.colorArr = colorArr self.idx = 0 self.timer = Timer(self.timeArr[0]) def advance(self, dt): self.timer.advance(dt)
class SceneIntro(SceneBase): def __init__(self, viewport, world): super().__init__(world=world, viewport=viewport) self.textureEmiter = TextureEmiter(viewport=viewport, world=world) renderableCache.init(viewport=viewport) textureCopter = PhenomenaTexture( phenomenaType=PhenomenaType.roflcopter, name="Scene1 chopper") self.copterSpawn = Coordinates(13, -1 * textureCopter.height) renderableCopter = Renderable( texture=textureCopter, viewport=self.viewport, coordinates=self.copterSpawn, active=True, ) texturePlayer = CharacterTexture( characterAnimationType=CharacterAnimationType.standing, characterTextureType=CharacterTextureType.player, name='Scene1 Player') coordinates = Coordinates(24, 13) renderablePlayer = Renderable( texture=texturePlayer, viewport=self.viewport, coordinates=coordinates, active=True, ) textureEnemy = CharacterTexture( characterAnimationType=CharacterAnimationType.standing, characterTextureType=CharacterTextureType.player, name='Scene1 Enemy') coordinates = Coordinates(Config.columns, 13) renderableEnemy = Renderable( texture=textureEnemy, viewport=self.viewport, coordinates=coordinates, active=True, ) self.renderableCopter = renderableCopter self.renderablePlayer = renderablePlayer self.renderableEnemy = renderableEnemy self.myTimer = Timer(0.5) self.state = IntroSceneState.wait1 self.name = "Scene1 - Intro Animation" self.anyKeyFinishesScene = True self.init() def advance(self, dt): self.myTimer.advance(dt) self.handleState() def enter(self): self.addRenderable(self.renderableCopter) def sceneIsFinished(self) -> bool: if self.state is IntroSceneState.done: return True else: return False def handleState(self): # interactive, aka a hack, but it works if (self.state is IntroSceneState.wait1 or self.state is IntroSceneState.flydown or self.state is IntroSceneState.drop or self.state is IntroSceneState.flyup): c1, a1 = ColorPalette.getColorByColor(Color.blue) c2, a2 = ColorPalette.getColorByColor(Color.brightblue) self.viewport.addstr(5, 40, "N Key Rollover", c1, a1) self.viewport.addstr(6, 40, "Escape from Hong Kong", c2, a2) c3, a3 = ColorPalette.getColorByColor(Color.cyan) self.viewport.addstr(8, 40, "Moving: arrow keys, shift to strafe", c3, a3) self.viewport.addstr(9, 40, "Select attack: 1 2 3 4", c3, a3) self.viewport.addstr(10, 40, "Attack : space", c3, a3) self.viewport.addstr(11, 40, "Skills : q w e r", c3, a3) self.viewport.addstr(12, 40, "Heal, Port : f g", c3, a3) # state if self.state is IntroSceneState.wait1: # for next scene: Flydown if self.myTimer.timeIsUp(): self.state = IntroSceneState.flydown self.myTimer.setTimer(0.1) self.renderableCopter.setActive(True) logger.debug("Scene: Go to State: Flydown") elif self.state is IntroSceneState.flydown: if self.myTimer.timeIsUp(): self.myTimer.reset() self.renderableCopter.coordinates.y += 1 # for next scene: Drop if self.renderableCopter.coordinates.y == 8: self.myTimer.setTimer(0.1) logger.debug("Scene: Go to State: Drop") self.addRenderable(self.renderablePlayer) self.state = IntroSceneState.drop elif self.state is IntroSceneState.drop: # for next scene: Flyup if self.myTimer.timeIsUp(): self.myTimer.reset() self.renderableCopter.coordinates.y -= 1 if self.renderableCopter.coordinates.y == self.copterSpawn.y: self.myTimer.setTimer(0.1) self.addRenderable(self.renderableEnemy) self.renderableEnemy.texture.changeAnimation( CharacterAnimationType.walking, direction=Direction.left) self.state = IntroSceneState.spawnenemy self.isShowMap = True logger.info("Scene: Go to State: SpawnEnemy") elif self.state is IntroSceneState.spawnenemy: if self.myTimer.timeIsUp(): self.myTimer.reset() self.renderableEnemy.coordinates.x -= 1 self.renderableEnemy.advanceStep() if (self.renderableEnemy.coordinates.x == self.renderablePlayer.coordinates.x + 15): self.renderableEnemy.texture.changeAnimation( CharacterAnimationType.standing, direction=Direction.none) self.myTimer.setTimer(2.0) self.state = IntroSceneState.speakenemy self.textureEmiter.showSpeechBubble( 'The princess is in another castle...', time=3.0, parentRenderable=self.renderableEnemy ) elif self.state is IntroSceneState.speakenemy: if self.myTimer.timeIsUp(): self.state = IntroSceneState.leaveenemy self.renderableEnemy.texture.changeAnimation( CharacterAnimationType.walking, direction=Direction.right) self.myTimer.setTimer(0.1) elif self.state is IntroSceneState.leaveenemy: if self.myTimer.timeIsUp(): self.myTimer.reset() self.renderableEnemy.advanceStep() self.renderableEnemy.coordinates.x += 1 if (self.renderableEnemy.coordinates.x == Config.columns): self.state = IntroSceneState.done elif self.state is IntroSceneState.done: pass
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
class InputProcessor(esper.Processor): def __init__(self): super().__init__() self.movementTimer = Timer(1.0 / Config.movementKeysPerSec, instant=True) self.keyCache = [] def process(self, deltaTime): self.handleKeyboardInput() self.advance(deltaTime) def advance(self, deltaTime): self.movementTimer.advance(deltaTime) def handleKeyboardInput(self): playerEntity = EntityFinder.findPlayer(self.world) if playerEntity is None: return player = self.world.component_for_entity( playerEntity, system.gamelogic.player.Player) renderable = self.world.component_for_entity( playerEntity, system.graphics.renderable.Renderable) attackable = self.world.component_for_entity( playerEntity, system.gamelogic.attackable.Attackable) # return if we cannot handle key, but cache it first if attackable.isStunned or player.isAttacking: for message in messaging.getByType(MessageType.PlayerKeypress): # store a few movement commands if len(self.keyCache) >= 1: del (self.keyCache[0]) self.keyCache.append(message) return if not player.isAlive: return didMove = False for message in self.keyCache: # identical to bottom loop atm apm.tick(message.data['time']) didMoveTmp = self.handleKeyPress(message.data['key'], player, renderable, playerEntity) if didMoveTmp: didMove = True self.keyCache.clear() for message in messaging.getByType(MessageType.PlayerKeypress): apm.tick(message.data['time']) didMoveTmp = self.handleKeyPress(message.data['key'], player, renderable, playerEntity) if didMoveTmp: didMove = True # to allow diagonal movement, we allow multiple movement keys per input # cycle, without resetting the timer. if didMove: self.movementTimer.reset() def handleKeyPress(self, key, player, playerRenderable, playerEntity): didMove = False x = 0 y = 0 if self.movementTimer.timeIsUp(): dontChangeDirection = False if key == Screen.KEY_LEFT: if Config.xDoubleStep: x = -2 else: x = -1 didMove = True elif key == Screen.KEY_RIGHT: if Config.xDoubleStep: x = 2 else: x = 1 didMove = True if key == 393: # shift left dontChangeDirection = True if Config.xDoubleStep: x = -2 else: x = -1 didMove = True elif key == Screen.KEY_RIGHT: if Config.xDoubleStep: x = 2 else: x = 1 didMove = True if key == 402: # shift right dontChangeDirection = True if Config.xDoubleStep: x = 2 else: x = 1 didMove = True elif key == Screen.KEY_UP: y = -1 didMove = True elif key == Screen.KEY_DOWN: y = 1 didMove = True meGroupId = self.world.component_for_entity(playerEntity, system.groupid.GroupId) if didMove: directMessaging.add( groupId=meGroupId.getId(), type=DirectMessageType.movePlayer, data={ 'x': x, 'y': y, 'dontChangeDirection': dontChangeDirection, 'whenMoved': None, }, ) return didMove
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 Particle(object): def __init__( self, viewport =None, x :int =0, y :int =0, life :int =0, angle :float =0, speed :int =1, fadeout :bool =True, charType :int =0, byStep :bool =False, active :bool =False, damage :int =0, damageEveryStep :bool =False, byPlayer :bool =True, ): self.viewport = viewport self.movementTimer = Timer() self.init( x=x, y=y, life=life, angle=angle, speed=speed, fadeout=fadeout, byStep=byStep, charType=charType, active=active, damage=damage, damageEveryStep=damageEveryStep, byPlayer=byPlayer) def init( self, x :int =0, y :int =0, life :int =0, angle :float =0, speed :int =1, fadeout :bool =True, byStep :bool =False, charType :int =0, active :bool =False, damage :int =0, damageEveryStep :bool =False, byPlayer :bool =True, color =None, ): self.x = x self.y = y self.life = life self.originalLife = life self.angle = angle self.speed = speed self.fadeout = fadeout self.byStep = byStep self.charType = charType self.damage = damage self.damageEveryStep = damageEveryStep self.byPlayer = byPlayer self.angleInRadians = angle * math.pi / 180 self.velocity = { 'x': speed * math.cos(self.angleInRadians), 'y': -speed * math.sin(self.angleInRadians) } self.color, self.attr = ColorPalette.getColorByColor(Color.brightmagenta) if color is not None: self.color = color[0] self.attr = color[1] self.setChar() self.rx = 0.0 self.ry = 0.0 self.movementTimer.setTimer(self.speed) self.active = active def __repr__(self): return "Particle {}/{}".format( self.x, self.y ) def advance(self, dt): if self.active is False: return if self.byStep: return self.movementTimer.advance(dt) self.makeStep(dt) def fadeoutSetColor(self): if self.life > (self.originalLife / 2): self.attr = Screen.A_BOLD else: self.attr = Screen.A_NORMAL def setChar(self): if self.charType == 0: pass elif self.charType == 1: if self.life > ((self.originalLife / 3) * 2): self.char = 'O' elif self.life < ((self.originalLife / 3) * 1): self.char = '.' else: self.char = 'o' elif self.charType == 2: if self.life > ((self.originalLife / 3) * 2): self.char = '#' elif self.life < ((self.originalLife / 3) * 1): self.char = ':' else: self.char = '|' elif self.charType == 3: if self.life > ((self.originalLife / 3) * 2): self.char = 'O' elif self.life < ((self.originalLife / 3) * 1): self.char = '.' else: self.char = 'o' elif self.charType == 4: if self.life > ((self.originalLife / 3) * 2): self.char = '¦' elif self.life < ((self.originalLife / 3) * 1): self.char = '.' else: self.char = ':' elif self.charType == 5: self.char = '-' else: raise Exception("Invalid charType: {}".format(self.charType)) def makeStep(self, dt, adjustLife=True): if self.life <= 0: self.active = False return if self.fadeout: self.fadeoutSetColor() self.setChar() if self.speed > 0: xFloat = self.velocity['x'] * (dt * 100) + self.rx yFloat = self.velocity['y'] * (dt * 100) + self.ry xChange = int(round(xFloat)) yChange = int(round(yFloat)) # accumulate pos we could not handle yet changeRestX = xFloat - xChange - self.rx changeRestY = yFloat - yChange - self.ry self.rx += changeRestX self.ry += changeRestY # change pos self.x += xChange self.y += yChange if (self.damageEveryStep and self.damage > 0 and (xChange != 0 or yChange != 0)): # hitLocations = [Coordinates(self.x, self.y)] hitLocations = [] hitLocations.append(self) messaging.add( type=MessageType.AttackAt, data= { 'hitLocations': hitLocations, 'damage': self.damage, 'byPlayer': self.byPlayer, 'direction': Direction.none, 'knockback': False, 'stun': False, 'sourceRenderable': None, } ) if False: logging.info("Real change: X: {} Y: {}".format(xFloat, yFloat)) logging.info("Round change: X: {} Y: {}".format(xChange, yChange)) logging.info("Change Rest: X: {} Y: {}".format(changeRestX, changeRestY)) logging.info("New Rest: X: {} Y: {}".format(self.rx, self.ry)) logging.info("New Pos: X: {} Y: {}".format(self.x, self.y)) if adjustLife: self.life -= 1 def draw(self): self.viewport.addstr( self.y, self.x, self.char, self.color, self.attr) def isActive(self): return self.active def setActive(self, active): self.active = active
class SceneProcessor(esper.Processor): def __init__(self, viewport, sceneManager): super().__init__() self.viewport = viewport self.sceneManager = sceneManager self.xCenter = Config.columns / 2 - 5 self.state = State.start self.screenMoveTimer = Timer(0.1) self.lastKnownPlayerPos = None self.gameoverTimer = Timer(3.0, active=False) # show gameover for this long def setState(self, state): logging.debug("Set to state: {}".format(state)) self.state = state def process(self, dt): self.screenMoveTimer.advance(dt) self.gameoverTimer.advance(dt) for message in messaging.getByType(MessageType.Gameover): self.gameoverTimer.reset() self.gameoverTimer.start() self.setState(State.gameover) messaging.add(type=MessageType.EmitPhenomenaTexture, data={ 'phenomenaTextureType': PhenomenaType.gameover, 'location': Coordinates(10, 10), 'staticLocation': True, 'direction': Direction.right, 'physics': False, }) if self.state is State.gameover: for message in messaging.getByType(MessageType.PlayerKeypress): if self.gameoverTimer.timeIsUp(): messaging.add(type=MessageType.ClearRenderables, data={}) self.setState(State.start) elif self.state is State.start: self.sceneManager.restartScene() self.setState(State.brawl) for message in messaging.getByType(MessageType.EntityDying): # if no enemies are alive, we want to go to the next akt if self.numEnemiesAlive() == 0: self.screenMoveTimer.start() if self.state is not State.gameover: self.setState(State.pushToNextScene) break for message in messaging.getByType(MessageType.PlayerLocation): self.lastKnownPlayerPos = message.data if self.state is State.pushToNextScene: # if suddenly enemies appear, let the player free if self.numEnemiesVisible() > 0: self.setState(State.brawl) if self.state is State.brawl: if (self.numEnemiesVisible() == 0 and not self.enemiesLeftOfChar(self.lastKnownPlayerPos.x)): self.screenMoveTimer.start() self.setState(State.pushToEnemies) if self.state is State.pushToEnemies: if self.numEnemiesVisible() > 0: self.setState(State.brawl) playerScreenCoords = self.viewport.getScreenCoords(message.data) # adjust viewport on move if self.state is State.pushToNextScene: # screen follows player # player is left side of screen (screen pushes player right) if playerScreenCoords.x != 10: distance = int(playerScreenCoords.x - 10) if distance < 0: self.adjustViewport(-1) self.screenMoveTimer.reset() elif distance > 0: self.adjustViewport(1) self.screenMoveTimer.reset() elif self.state is State.pushToEnemies: # screen follows player # player is middle of the screen if playerScreenCoords.x != self.xCenter: distance = int(playerScreenCoords.x - self.xCenter) if distance < 0: self.adjustViewport(-1) self.screenMoveTimer.reset() elif distance > 0: self.adjustViewport(1) self.screenMoveTimer.reset() elif self.state is State.brawl: if not self.enemiesLeftOfChar(self.lastKnownPlayerPos.x): # player can move freely # coming close to left/right of the screen will move it if playerScreenCoords.x >= Config.moveBorderRight: distance = playerScreenCoords.x - Config.moveBorderRight self.adjustViewport(distance) if playerScreenCoords.x <= Config.moveBorderLeft: distance = playerScreenCoords.x - Config.moveBorderLeft self.adjustViewport(distance) # /adjust viewport on move # let the scene decide if we need more enemies self.sceneManager.currentScene.handlePosition( message.data, self.viewport.getx(), self.numEnemiesAlive()) for message in messaging.getByType(MessageType.PlayerKeypress): key = message.data['key'] self.sceneManager.handlePlayerKeyPress(key) if key == ord('k'): logger.info("Scene: Kill All Enemies") self.killAllEnemies() if key == ord('n'): logger.info("Scene: Go to next part") self.killAllEnemies() enemyCell = self.sceneManager.currentScene.getNextEnemy() playerEntity = EntityFinder.findPlayer(self.world) meGroupId = self.world.component_for_entity( playerEntity, system.groupid.GroupId) renderable = self.world.component_for_entity( playerEntity, system.graphics.renderable.Renderable) distX = enemyCell.spawnX - renderable.coordinates.x directMessaging.add( groupId=meGroupId.getId(), type=DirectMessageType.movePlayer, data={ 'x': distX, 'y': 0, 'dontChangeDirection': False, 'whenMoved': None, }, ) # move screen animation if self.screenMoveTimer.timeIsUp( ) and self.lastKnownPlayerPos is not None: playerScreenCoords = self.viewport.getScreenCoords( self.lastKnownPlayerPos) if self.state is State.pushToNextScene: # screen follows player # player is left side of screen (screen pushes player right) if playerScreenCoords.x != 10: distance = int(playerScreenCoords.x - 10) if distance < 0: self.adjustViewport(-1) self.screenMoveTimer.reset() elif distance > 0: self.adjustViewport(1) self.screenMoveTimer.reset() else: self.screenMoveTimer.stop() elif self.state is State.pushToEnemies: # screen follows player # player is middle of the screen if playerScreenCoords.x != self.xCenter: distance = int(playerScreenCoords.x - self.xCenter) if distance < 0: self.adjustViewport(-1) self.screenMoveTimer.reset() elif distance > 0: self.adjustViewport(1) self.screenMoveTimer.reset() else: self.screenMoveTimer.stop() self.sceneManager.advance(dt) def adjustViewport(self, xoff): viewportChanged = self.viewport.adjustViewport(xoff) if viewportChanged: messaging.add( type=MessageType.ScreenMove, data={ 'x': xoff, }, ) def killAllEnemies(self): for _, ai in self.world.get_component(system.gamelogic.ai.Ai): ai.brain.pop() ai.brain.push('dead') def numEnemiesAlive(self) -> int: count = 0 for _, ai in self.world.get_component(system.gamelogic.ai.Ai): if ai.brain.state.name != 'dead' and ai.brain.state.name != 'dying': count += 1 return count def numEnemiesVisible(self) -> int: count = 0 for _, (ai, renderable) in self.world.get_components( system.gamelogic.ai.Ai, system.graphics.renderable.Renderable): if (ai.brain.state.name != 'dead' and ai.brain.state.name != 'dying' and renderable.coordinates.x > self.viewport.getx() and renderable.coordinates.x < self.viewport.getRightX()): count += 1 return count def enemiesLeftOfChar(self, playerX): for _, (ai, renderable) in self.world.get_components( system.gamelogic.ai.Ai, system.graphics.renderable.Renderable): if (ai.brain.state.name != 'dead' and ai.brain.state.name != 'dying' and renderable.coordinates.x < playerX): return True return False
class StateAttack(State): name = "attack" def __init__(self, brain): State.__init__(self, brain) self.attackMoveTimer = Timer() # Timer(0.5, instant=False) # windup and cooldown def on_enter(self): meEnemy = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.enemy.Enemy) meGroupId = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.groupid.GroupId) messaging.add( type=MessageType.EntityAttack, groupId=meGroupId.getId(), data=None ) # self.attackTimer.setTimer(meEnemy.enemyInfo.attackTime) # self.setTimer(meEnemy.enemyInfo.attackTime) self.stepsTodo = 30 self.attackMoveTimer.init() self.attackMoveTimer.setTimer(0.1) self.setTimer(3.0) # even though we attack multiple times (each step) # only send EntityAttack once (change animation) messaging.add( type=MessageType.EntityAttack, groupId=meGroupId.getId(), data={} ) def process(self, dt): self.attackMoveTimer.advance(dt) # check if we got stunned meAttackable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.attackable.Attackable) if meAttackable.isStunned: self.brain.pop() self.brain.push("chase") # check if we should do one attack step if self.attackMoveTimer.timeIsUp(): logger.info("{}: I'm attacking, step {}".format(self.owner, self.stepsTodo)) if self.stepsTodo > 0: self.attackMoveTimer.reset() self.stepsTodo -= 1 meRenderable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, Renderable) meGroupId = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.groupid.GroupId) if self.stepsTodo % 2 == 0: offensiveAttack = self.brain.owner.world.component_for_entity( self.brain.owner.entity, OffensiveAttack) offensiveAttack.attack() if meRenderable.direction is Direction.left: x = -1 else: x = 1 directMessaging.add( groupId = meGroupId.getId(), type = DirectMessageType.moveEnemy, data = { 'x': x, 'y': 0, 'dontChangeDirection': False, 'updateTexture': False, 'force': True }, ) else: self.attackMoveTimer.stop() if self.timeIsUp(): # too long attacking. lets switch to chasing logger.info("{}: Too long attacking, switch to chasing".format( self.owner)) self.brain.pop() self.brain.push("chase")
class RamboStateAttacking(State): name = "attacking" def __init__(self, brain): super().__init__(brain) self.cooldownTimer = Timer() self.attackingTimer = Timer() def on_enter(self): self.cooldownTimer.setTimer(0.5) self.attackingTimer.setTimer(4.0) playerEntity = EntityFinder.findPlayer(self.brain.owner.world) if playerEntity is None: return meRenderable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.graphics.renderable.Renderable) meGroupId = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.groupid.GroupId) self.turnIfNecessary(playerEntity, meRenderable, meGroupId) # attack messaging.add(type=MessageType.EntityAttack, groupId=meGroupId.getId(), data=None) def turnIfNecessary(self, playerEntity, meRenderable, meGroupId): playerRenderable = self.brain.owner.world.component_for_entity( playerEntity, system.graphics.renderable.Renderable) x, y = AiHelper.getVectorToPlayer(source=meRenderable.coordinates, dest=playerRenderable.getLocation()) if x > 0: playerDir = Direction.right cx = 1 else: playerDir = Direction.left cx = -1 if playerDir is not meRenderable.getDirection(): directMessaging.add( groupId=meGroupId.getId(), type=DirectMessageType.moveEnemy, data={ 'x': cx, 'y': 0, 'dontChangeDirection': False, 'updateTexture': False, 'force': False, }, ) def process(self, dt): self.cooldownTimer.advance(dt) self.attackingTimer.advance(dt) self.tryShooting() def tryShooting(self): if self.attackingTimer.timeIsUp(): self.brain.pop() self.brain.push('standing') if self.cooldownTimer.timeIsUp(): meRenderable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.graphics.renderable.Renderable) locCenter = meRenderable.getAttackBaseLocation() messaging.add(type=MessageType.EmitParticleEffect, data={ 'location': locCenter, 'effectType': ParticleEffectType.bullet, 'damage': 50, 'byPlayer': False, 'direction': meRenderable.getDirection(), }) self.cooldownTimer.reset()
class StateWander(State): name = "wander" def __init__(self, brain): State.__init__(self, brain) meEnemy = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.enemy.Enemy) self.lastInputTimer = Timer(meEnemy.enemyInfo.wanderStepDelay, instant=True) self.destCoord = Coordinates() self.destIsPoint = False 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.wanderTimeRnd, 100 * meEnemy.enemyInfo.wanderTimeRnd) self.setTimer(meEnemy.enemyInfo.wanderTime + (stateTimeRnd / 100)) self.chooseDestination() def process(self, dt): meRenderable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.graphics.renderable.Renderable) meAttackable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.attackable.Attackable) meEnemy = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.gamelogic.enemy.Enemy) if meAttackable.isStunned: return self.lastInputTimer.advance(dt) if self.lastInputTimer.timeIsUp(): self.getInputWander() self.lastInputTimer.reset() if self.timeIsUp(): if (EntityFinder.numEnemiesInState(self.brain.owner.world, 'chase') < Config.maxEnemiesInStateChase): logger.info("{}: Too long wandering, chase again a bit".format( self.owner)) self.brain.pop() self.brain.push("chase") elif Utility.isIdentical(meRenderable.getLocation(), self.destCoord): # No reset of wander state atm, just a new location self.chooseDestination() else: # check if player is close for message in messaging.getByType(MessageType.PlayerLocation): distance = Utility.distance( message.data, meRenderable.getLocation()) if distance['sum'] < meEnemy.enemyInfo.wanderAttackDistance: logger.info("{}: Player is close, chasing".format(self.owner)) self.brain.pop() self.brain.push("chase") def getInputWander(self): meRenderable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.graphics.renderable.Renderable) meGroupId = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.groupid.GroupId) if not Config.allowEnemyMovement: return x, y = AiHelper.getVectorToPlayer( source=meRenderable.coordinates, dest=self.destCoord) directMessaging.add( groupId = meGroupId.getId(), type = DirectMessageType.moveEnemy, data = { 'x': x, 'y': y, 'dontChangeDirection': False, 'updateTexture': True, 'force': False, }, ) def chooseDestination(self): meRenderable = self.brain.owner.world.component_for_entity( self.brain.owner.entity, system.graphics.renderable.Renderable) # if true: go to a static point close to the current enemy position # if false: go to a point relative to the enemy # self.destIsPoint = random.choice([True, False]) # note that getLocation() will return a reference. we need to copy it here. playerEntity = EntityFinder.findPlayer(self.brain.owner.world) if not playerEntity: return playerRenderable = self.brain.owner.world.component_for_entity( playerEntity, system.graphics.renderable.Renderable) self.destCoord = AiHelper.pickDestAroundPlayer( playerRenderable, distanceX=meRenderable.texture.width, distanceY=meRenderable.texture.height) if Config.showEnemyWanderDest: messaging.add( type=MessageType.EmitTextureMinimal, data={ 'char': '.', 'timeout': self.timer, 'coordinate': self.destCoord, 'color': Color.grey } )
class DamageProcessor(esper.Processor): def __init__(self): super().__init__() self.dmgTimer = Timer(1.0) def process(self, dt): self.dmgTimer.advance(dt) if self.dmgTimer.timeIsUp(): damageStat.addDamage(-10) self.dmgTimer.reset() damageSumPlayer = 0 for msg in messaging.getByType(MessageType.AttackAt): for entity, (meAtk, groupId, renderable) in self.world.get_components( Attackable, GroupId, Renderable ): hitLocations = msg.data['hitLocations'] damage = msg.data['damage'] byPlayer = msg.data['byPlayer'] direction = msg.data['direction'] knockback = msg.data['knockback'] stun = msg.data['stun'] if 'sourceRenderable' in msg.data: sourceRenderable = msg.data['sourceRenderable'] else: sourceRenderable = None if renderable.isHitBy(hitLocations): directMessaging.add( groupId=groupId.id, type=DirectMessageType.receiveDamage, data={ 'damage': damage, 'byPlayer': byPlayer, 'direction': direction, 'knockback': knockback, 'stun': stun, 'sourceRenderable': sourceRenderable, 'destinationEntity': entity, 'hitLocations': hitLocations, } ) if byPlayer: damageSumPlayer += damage # check if we should announce our awesomeness if damageSumPlayer > Config.announceDamage: # find player for ent, (groupId, player) in self.world.get_components( GroupId, Player ): directMessaging.add( groupId = groupId.getId(), type = DirectMessageType.activateSpeechBubble, data = { 'text': 'Cowabunga!', 'time': 1.0, } ) damageStat.addDamage(damageSumPlayer)