mirror of
https://github.com/n0remac/game-jam-2020.git
synced 2025-12-06 01:13:12 -06:00
274 lines
9.1 KiB
Python
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
|