Files
game-jam-2020/triple-dungeon/mobs.py
2020-04-26 02:26:18 -07:00

274 lines
9.1 KiB
Python

"""
mobs.py
Organizes all classes related to Mobs, Entities, Enemies, Players and Items.
"""
from typing import List, Tuple
import arcade
import random
import math
from config import Config, Enums, SpritePaths
from map import Dungeon
from sprites import PlayerAnimations
class MobHandler:
def __init__(self):
super().__init__()
self.enemy_list = []
self.avoid_list = []
self.dungeon = None
self.player = None
def setup(self, ghost, frogs, player, dungeon) -> list:
self.enemy_list = arcade.SpriteList()
self.dungeon = dungeon
self.player = player
self.avoid_list = arcade.SpriteList()
self.avoid_list.append(self.player)
for d in dungeon.getWalls():
self.avoid_list.append(d)
for count in range(ghost):
mob = Enemy(filename="resources/images/monsters/ghost/ghost1.png", dungeon=self.dungeon)
level = random.choice(self.dungeon.levelList)
mob.center_x, mob.center_y = level.random()
mob.target = self.player
mob.scale = 4
mob.monster_type = 'ghost'
mob.collisions = arcade.PhysicsEngineSimple(mob, self.avoid_list)
mob.level = level
self.enemy_list.append(mob)
self.avoid_list.append(mob)
for count in range(frogs):
mob = Enemy(filename="resources/images/monsters/frog/frog1.png", dungeon=self.dungeon)
level = random.choice(self.dungeon.levelList)
mob.center_x, mob.center_y = level.random()
mob.target = self.player
mob.scale = 4
mob.monster_type = 'frog'
mob.collisions = arcade.PhysicsEngineSimple(mob, self.avoid_list)
mob.level = level
self.enemy_list.append(mob)
self.avoid_list.append(mob)
return self.enemy_list
def render(self) -> None:
self.player.draw()
self.enemy_list.draw()
def update(self) -> None:
#update player
self.player.collisions.update()
self.player.update_animation()
# Enemy activation and update
for enemy in reversed(self.enemy_list):
distance = self.get_distance(enemy)
enemy.collisions.update()
if distance < 100 :
self.player.health -= (2 - self.player.armor)
if (distance < 300):
enemy.speed = Config.MONSTER_MOVEMENT_SPEED
try:
path = enemy.get_path(enemy.target.position)
enemy.tick(path)
except Exception:
import traceback
traceback.print_exc()
else:
left, right, bottom, top = arcade.get_viewport()
if (
enemy.bottom > bottom and
enemy.top < bottom + Config.SCREEN_HEIGHT and
enemy.right < left + Config.SCREEN_WIDTH and
enemy.left > left
):
enemy.speed = 5
ran = random.randint(0,1000)
if ran > 950:
print(ran)
try:
path = enemy.get_path(enemy.level.random())
enemy.tick(path)
except Exception:
import traceback
traceback.print_exc()
def get_distance(self, enemy) -> int:
start_x = enemy.center_x
start_y = enemy.center_y
end_x = self.player.center_x
end_y = self.player.center_y
distance = math.sqrt(math.pow(start_x - end_x, 2) + math.pow(start_y - end_y, 2))
return distance
@staticmethod
def draw_path(path: List[Tuple[int, int]]) -> None:
"""
Draws a line between positions in a list of tuple, also known as the path.
:param path: A list of tuple positions defining a path that can be traversed.
"""
if len(path) > 2:
path = map(lambda point: ((point[0]) * Config.TILE_SIZE, (point[1]) * Config.TILE_SIZE), path)
path = list(path)
#print(path)
for pos1, pos2 in zip(path, path[1:]):
arcade.draw_line(*pos1, *pos2, color=arcade.color.RED)
class Mob(arcade.Sprite):
"""
Represents a Mob. No defined behaviour, it has no intelligence.
"""
def __init__(self, dungeon: Dungeon, max_health=200, max_armor=0, *args, **kwargs) -> None:
# Set up parent class
super(Mob, self).__init__(*args, **kwargs)
self.max_health, self.max_armor = max_health, max_armor
self.health, self.armor = max_health, max_armor
self.idle_textures = []
self.walking_textures = []
self.up_textures = []
self.down_textures = []
self.cur_texture = 0
self.collisions = None
self.dungeon = dungeon
self.target = None
self.level = None
class Player(Mob):
"""
Represents a Player.
While this is a instance, there should only be one in the world at any given time.
"""
def __init__(self, *args, **kwargs) -> None:
super(Player, self).__init__(*args, **kwargs)
self.animations = PlayerAnimations(SpritePaths.KNIGHT)
# Used for mapping directions to animations
self.map = {
Enums.IDLE: self.animations.idles,
Enums.UP: self.animations.up,
Enums.DOWN: self.animations.down,
Enums.RIGHT: self.animations.right,
Enums.LEFT: self.animations.left
}
self.refreshIndex = 0
self.prev = Enums.IDLE
self.texture = next(self.map[self.prev])
self.cur_recipe = None
self.speed = 14
def heal(self):
self.health+=Config.HEAL_AMOUNT
if self.health > self.max_health:
self.health = self.max_health
def harden(self):
self.armor+=Config.ARMOR_AMOUNT
def hurry(self):
self.speed+=Config.SPEED_AMOUNT
def update_animation(self, delta_time: float = 1 / 60) -> None:
"""
Updates animations for the Player.
:param delta_time: No idea.
"""
# Increase the refresh index according
self.refreshIndex = (self.refreshIndex + 1) % Config.RUN_UPDATES_PER_FRAME
# Logic to determine what direction we're in.
if self.change_x == 0 and self.change_y == 0:
cur = Enums.IDLE
elif self.change_y > 0: # Up
cur = Enums.UP
elif self.change_y < 0: # Down
cur = Enums.DOWN
elif self.change_x > 0: # Left
cur = Enums.RIGHT
elif self.change_x < 0: # Right
cur = Enums.LEFT
else: # Idle
cur = Enums.IDLE
# If we're in a new direction or the refresh index has reset
if self.prev is not cur or self.refreshIndex == 0:
self.texture = next(self.map[cur])
self.prev = cur
def tick(self):
"""
While Player objects do not have any AI (they are controlled by the user),
the tick function can keep track of statistics that progress over time, like
regenerating health/armor or status effects like poison.
"""
class Enemy(Mob):
"""
Represents an Enemy Mob.
Will take basic offensive actions against Player objects.
"""
def __init__(self, *args, **kwargs) -> None:
super(Enemy, self).__init__(*args, **kwargs)
self.monster_type = ''
def nearestPosition(self) -> Tuple[int, int]:
"""
Returns the nearest absolute dungeon tile the Mob is placed on.
:return: A tuple containing the Mob's dungeon tile position.
"""
return (round(self.center_x / Config.TILE_SIZE),
round(self.center_y / Config.TILE_SIZE))
def tick(self, path: Tuple[int, int] = None) -> None:
"""
A on_update function, the Mob should decide it's next actions here.
"""
near_pos = self.nearestPosition()
curpos, nextpos = near_pos, path[1]
# print(curpos, nextpos)
if nextpos[0] > curpos[0]:
self.change_x = self.speed
elif nextpos[0] < curpos[0]:
self.change_x = -self.speed
else:
self.change_x = 0
if nextpos[1] > curpos[1]:
self.change_y = self.speed
elif nextpos[1] < curpos[1]:
self.change_y = -self.speed
else:
self.change_y = 0
def get_path(self, end: Tuple[int, int] = None) -> List[Tuple[int, int]]:
"""
Returns the path to get to the Mob's target in absolute integer positions.
:param end: A the endpoint tuple. Must be a valid position within the matrix.
:return:
"""
start, end = self.nearestPosition(), (round(end[0] / Config.TILE_SIZE), round(end[1] / Config.TILE_SIZE))
start, end = self.dungeon.grid.node(*start), self.dungeon.grid.node(*end)
paths, runs = self.dungeon.finder.find_path(start, end, self.dungeon.grid)
self.dungeon.grid.cleanup()
return paths