From cd0333cbffe4c20d699680f93f9718b5e91077aa Mon Sep 17 00:00:00 2001 From: Vasilis Valatsos Date: Wed, 27 Sep 2023 20:03:37 +0200 Subject: [PATCH] Reenabled Player and all animations, upgrades, attacks and spells --- game/{ui => configs}/__init__.py | 0 game/configs/game/__init__.py | 0 game/configs/game/spell_config.py | 10 +- game/configs/game/weapon_config.py | 17 +- game/configs/system/__init__.py | 0 game/configs/system/window_config.py | 64 +++---- game/effects/magic_effects.py | 74 ++++---- game/effects/particle_effects.py | 79 ++++---- game/effects/weapon_effects.py | 28 ++- game/entities/__init__.py | 0 game/entities/components/__init__.py | 0 game/entities/components/_input.py | 138 ++++++++++++++ game/entities/components/animaton.py | 69 +++++++ game/entities/components/combat.py | 59 ++++++ game/entities/components/movement.py | 41 +++++ game/entities/components/stats.py | 45 +++++ game/entities/entity.py | 9 + game/entities/observer.py | 65 +++++++ game/entities/player.py | 67 +++++++ game/interface/__init__.py | 0 game/{ui => interface}/ui.py | 96 ++++++---- .../ui_config.py => interface/ui_settings.py} | 8 +- game/{ui => interface}/upgrade.py | 115 ++++++------ game/level/__init__.py | 0 game/level/camera.py | 42 +++++ game/level/level.py | 120 +++++++++++++ game/level/terrain.py | 19 ++ game/main.py | 47 +++++ game/utils/__init__.py | 0 old-old/Game/level.py | 45 +++-- old-old/Game/main.py | 39 ++-- old/Game/main.py | 19 +- old/Game/objects/camera.py | 21 +-- old/Game/objects/enemy.py | 60 ++++--- old/Game/objects/entity.py | 53 +++--- old/Game/objects/level.py | 168 ++++++++---------- old/Game/objects/player.py | 114 ++++++------ 37 files changed, 1262 insertions(+), 469 deletions(-) rename game/{ui => configs}/__init__.py (100%) create mode 100644 game/configs/game/__init__.py create mode 100644 game/configs/system/__init__.py create mode 100644 game/entities/__init__.py create mode 100644 game/entities/components/__init__.py create mode 100644 game/entities/components/_input.py create mode 100644 game/entities/components/animaton.py create mode 100644 game/entities/components/combat.py create mode 100644 game/entities/components/movement.py create mode 100644 game/entities/components/stats.py create mode 100644 game/entities/entity.py create mode 100644 game/entities/observer.py create mode 100644 game/entities/player.py create mode 100644 game/interface/__init__.py rename game/{ui => interface}/ui.py (56%) rename game/{ui/ui_config.py => interface/ui_settings.py} (72%) rename game/{ui => interface}/upgrade.py (68%) create mode 100644 game/level/__init__.py create mode 100644 game/level/camera.py create mode 100644 game/level/level.py create mode 100644 game/level/terrain.py create mode 100644 game/main.py create mode 100644 game/utils/__init__.py diff --git a/game/ui/__init__.py b/game/configs/__init__.py similarity index 100% rename from game/ui/__init__.py rename to game/configs/__init__.py diff --git a/game/configs/game/__init__.py b/game/configs/game/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game/configs/game/spell_config.py b/game/configs/game/spell_config.py index f2cdf0c..0a4d8df 100644 --- a/game/configs/game/spell_config.py +++ b/game/configs/game/spell_config.py @@ -1,3 +1,9 @@ +import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +asset_path = os.path.join( + script_dir, '../../..', 'assets') + magic_data = { - 'flame': {'strength': 5,'cost': 20,'graphic':'../Graphics/particles/flame/fire.png'}, - 'heal' : {'strength': 20,'cost': 10,'graphic':'../Graphics/particles/heal/heal.png'}} + 'flame': {'strength': 5, 'cost': 20, 'graphic': f"{asset_path}/graphics/particles/flame/fire.png"}, + 'heal': {'strength': 20, 'cost': 10, 'graphic': f"{asset_path}/graphics/particles/heal/heal.png"}} diff --git a/game/configs/game/weapon_config.py b/game/configs/game/weapon_config.py index d1a789f..ab4df40 100644 --- a/game/configs/game/weapon_config.py +++ b/game/configs/game/weapon_config.py @@ -1,6 +1,13 @@ +import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +asset_path = os.path.join( + script_dir, '../../..', 'assets') + weapon_data = { - 'sword': {'cooldown': 100, 'damage': 15,'graphic':'../Graphics/weapons/sword/full.png'}, - 'lance': {'cooldown': 400, 'damage': 30,'graphic':'../Graphics/weapons/lance/full.png'}, - 'axe': {'cooldown': 300, 'damage': 20, 'graphic':'../Graphics/weapons/axe/full.png'}, - 'rapier':{'cooldown': 50, 'damage': 8, 'graphic':'../Graphics/weapons/rapier/full.png'}, - 'sai':{'cooldown': 80, 'damage': 10, 'graphic':'../Graphics/weapons/sai/full.png'}} + 'sword': {'cooldown': 100, 'damage': 15, 'graphic': f"{asset_path}/graphics/weapons/sword/full.png"}, + 'lance': {'cooldown': 400, 'damage': 30, 'graphic': f"{asset_path}/graphics/weapons/lance/full.png"}, + 'axe': {'cooldown': 300, 'damage': 20, 'graphic': f"{asset_path}/graphics/weapons/axe/full.png"}, + 'rapier': {'cooldown': 50, 'damage': 8, 'graphic': f"{asset_path}/graphics/weapons/rapier/full.png"}, + 'sai': {'cooldown': 80, 'damage': 10, 'graphic': f"{asset_path}/graphics/weapons/sai/full.png"} +} diff --git a/game/configs/system/__init__.py b/game/configs/system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game/configs/system/window_config.py b/game/configs/system/window_config.py index 3e2245d..f403747 100644 --- a/game/configs/system/window_config.py +++ b/game/configs/system/window_config.py @@ -1,23 +1,15 @@ # game setup -WIDTH = 1280 -HEIGHT = 720 -FPS = 60 +WIDTH = 1280 +HEIGHT = 720 +FPS = 60 TILESIZE = 64 HITBOX_OFFSET = { - 'player': (-6, -26), - 'camera': (-50, -50), - 'object': (0, -40), - 'grass': (0, -10), - 'invisible': (0, 0) - } - -# ui -BAR_HEIGHT = 20 -HEALTH_BAR_WIDTH = 200 -ENERGY_BAR_WIDTH = 140 -ITEM_BOX_SIZE = 80 -UI_FONT = '../Graphics/graphics/font/joystix.ttf' -UI_FONT_SIZE = 18 + 'player': (-6, -26), + 'camera': (-50, -50), + 'object': (0, -40), + 'grass': (0, -10), + 'invisible': (0, 0) +} # general colors WATER_COLOR = '#71ddee' @@ -36,22 +28,22 @@ BAR_COLOR = '#EEEEEE' BAR_COLOR_SELECTED = '#111111' UPGRADE_BG_COLOR_SELECTED = '#EEEEEE' -# weapons -weapon_data = { - 'sword': {'cooldown': 100, 'damage': 15,'graphic':'../Graphics/graphics/weapons/sword/full.png'}, - 'lance': {'cooldown': 400, 'damage': 30,'graphic':'../Graphics/graphics/weapons/lance/full.png'}, - 'axe': {'cooldown': 300, 'damage': 20, 'graphic':'../Graphics/graphics/weapons/axe/full.png'}, - 'rapier':{'cooldown': 50, 'damage': 8, 'graphic':'../Graphics/graphics/weapons/rapier/full.png'}, - 'sai':{'cooldown': 80, 'damage': 10, 'graphic':'../Graphics/graphics/weapons/sai/full.png'}} - -# magic -magic_data = { - 'flame': {'strength': 5,'cost': 20,'graphic':'../Graphics/graphics/particles/flame/fire.png'}, - 'heal' : {'strength': 20,'cost': 10,'graphic':'../Graphics/graphics/particles/heal/heal.png'}} - -# enemy -monster_data = { - 'squid': {'health': 100,'exp':100,'damage':20,'attack_type': 'slash', 'attack_sound':'../Graphics/audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 80, 'notice_radius': 360}, - 'raccoon': {'health': 300,'exp':250,'damage':40,'attack_type': 'claw', 'attack_sound':'../Graphics/audio/attack/claw.wav','speed': 2, 'resistance': 3, 'attack_radius': 120, 'notice_radius': 400}, - 'spirit': {'health': 100,'exp':110,'damage':8,'attack_type': 'thunder', 'attack_sound':'../Graphics/audio/attack/fireball.wav', 'speed': 4, 'resistance': 3, 'attack_radius': 60, 'notice_radius': 350}, - 'bamboo': {'health': 70,'exp':120,'damage':6,'attack_type': 'leaf_attack', 'attack_sound':'../Graphics/audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 50, 'notice_radius': 300}} +# # weapons +# weapon_data = { +# 'sword': {'cooldown': 100, 'damage': 15,'graphic':'../Graphics/graphics/weapons/sword/full.png'}, +# 'lance': {'cooldown': 400, 'damage': 30,'graphic':'../Graphics/graphics/weapons/lance/full.png'}, +# 'axe': {'cooldown': 300, 'damage': 20, 'graphic':'../Graphics/graphics/weapons/axe/full.png'}, +# 'rapier':{'cooldown': 50, 'damage': 8, 'graphic':'../Graphics/graphics/weapons/rapier/full.png'}, +# 'sai':{'cooldown': 80, 'damage': 10, 'graphic':'../Graphics/graphics/weapons/sai/full.png'}} +# +# # magic +# magic_data = { +# 'flame': {'strength': 5,'cost': 20,'graphic':'../Graphics/graphics/particles/flame/fire.png'}, +# 'heal' : {'strength': 20,'cost': 10,'graphic':'../Graphics/graphics/particles/heal/heal.png'}} +# +# # enemy +# monster_data = { +# 'squid': {'health': 100,'exp':100,'damage':20,'attack_type': 'slash', 'attack_sound':'../Graphics/audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 80, 'notice_radius': 360}, +# 'raccoon': {'health': 300,'exp':250,'damage':40,'attack_type': 'claw', 'attack_sound':'../Graphics/audio/attack/claw.wav','speed': 2, 'resistance': 3, 'attack_radius': 120, 'notice_radius': 400}, +# 'spirit': {'health': 100,'exp':110,'damage':8,'attack_type': 'thunder', 'attack_sound':'../Graphics/audio/attack/fireball.wav', 'speed': 4, 'resistance': 3, 'attack_radius': 60, 'notice_radius': 350}, +# 'bamboo': {'health': 70,'exp':120,'damage':6,'attack_type': 'leaf_attack', 'attack_sound':'../Graphics/audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 50, 'notice_radius': 300}} diff --git a/game/effects/magic_effects.py b/game/effects/magic_effects.py index 2b19cb8..63d2803 100644 --- a/game/effects/magic_effects.py +++ b/game/effects/magic_effects.py @@ -1,49 +1,61 @@ import pygame -from utils.settings import * from random import randint +from configs.system.window_config import TILESIZE + +from configs.game.spell_config import * + + class MagicPlayer: def __init__(self, animation_player): self.animation_player = animation_player - + # Sound Setup - self.sounds = { - 'heal': pygame.mixer.Sound('../Graphics/audio/heal.wav'), - 'flame': pygame.mixer.Sound('../Graphics/audio/flame.wav') - } - + # self.sounds = { + # 'heal': pygame.mixer.Sound('../Graphics/audio/heal.wav'), + # 'flame': pygame.mixer.Sound('../Graphics/audio/flame.wav') + # } + def heal(self, player, strength, cost, groups): - if player.energy >= cost: - self.sounds['heal'].play() - player.health += strength - player.energy -= cost - if player.health >= player.stats['health']: - player.health = player.stats['health'] - self.animation_player.generate_particles('aura', player.rect.center, groups) - self.animation_player.generate_particles('heal', player.rect.center + pygame.math.Vector2(0, -50), groups) - + if player.stats.energy >= cost: + # self.sounds['heal'].play() + player.stats.health += strength + player.stats.energy -= cost + if player.stats.health >= player.stats.stats['health']: + player.stats.health = player.stats.stats['health'] + self.animation_player.generate_particles( + 'aura', player.rect.center, groups) + self.animation_player.generate_particles( + 'heal', player.rect.center + pygame.math.Vector2(0, -50), groups) + def flame(self, player, cost, groups): - if player.energy >= cost: - player.energy -= cost - self.sounds['flame'].play() - + if player.stats.energy >= cost: + player.stats.energy -= cost + # self.sounds['flame'].play() + if player.status.split('_')[0] == 'right': - direction = pygame.math.Vector2(1,0) + direction = pygame.math.Vector2(1, 0) elif player.status.split('_')[0] == 'left': - direction = pygame.math.Vector2(-1,0) + direction = pygame.math.Vector2(-1, 0) elif player.status.split('_')[0] == 'up': - direction = pygame.math.Vector2(0,-1) + direction = pygame.math.Vector2(0, -1) else: - direction = pygame.math.Vector2(0,1) - + direction = pygame.math.Vector2(0, 1) + for i in range(1, 6): if direction.x: offset_x = direction.x * i * TILESIZE - x = player.rect.centerx + offset_x + randint(-TILESIZE // 3, TILESIZE //3) - y = player.rect.centery + randint(-TILESIZE // 3, TILESIZE //3) - self.animation_player.generate_particles('flame', (x,y), groups) + x = player.rect.centerx + offset_x + \ + randint(-TILESIZE // 3, TILESIZE // 3) + y = player.rect.centery + \ + randint(-TILESIZE // 3, TILESIZE // 3) + self.animation_player.generate_particles( + 'flame', (x, y), groups) else: offset_y = direction.y * i * TILESIZE - x = player.rect.centerx + randint(-TILESIZE // 3, TILESIZE //3) - y = player.rect.centery + offset_y + randint(-TILESIZE // 3, TILESIZE //3) - self.animation_player.generate_particles('flame', (x,y), groups) + x = player.rect.centerx + \ + randint(-TILESIZE // 3, TILESIZE // 3) + y = player.rect.centery + offset_y + \ + randint(-TILESIZE // 3, TILESIZE // 3) + self.animation_player.generate_particles( + 'flame', (x, y), groups) diff --git a/game/effects/particle_effects.py b/game/effects/particle_effects.py index d33b318..4cc8fcf 100644 --- a/game/effects/particle_effects.py +++ b/game/effects/particle_effects.py @@ -1,44 +1,58 @@ +import os import pygame -from utils.support import import_folder + +from utils.resource_loader import import_folder from random import choice + class AnimationPlayer: def __init__(self): + + script_dir = os.path.dirname(os.path.abspath(__file__)) + asset_path = os.path.join( + script_dir, '../..', 'assets') + self.frames = { # magic - 'flame': import_folder('../Graphics/graphics/particles/flame/frames'), - 'aura': import_folder('../Graphics/graphics/particles/aura'), - 'heal': import_folder('../Graphics/graphics/particles/heal/frames'), + 'flame': import_folder(f'{asset_path}/graphics/particles/flame/frames'), + 'aura': import_folder(f'{asset_path}/graphics/particles/aura'), + 'heal': import_folder(f'{asset_path}/graphics/particles/heal/frames'), - # attacks - 'claw': import_folder('../Graphics/graphics/particles/claw'), - 'slash': import_folder('../Graphics/graphics/particles/slash'), - 'sparkle': import_folder('../Graphics/graphics/particles/sparkle'), - 'leaf_attack': import_folder('../Graphics/graphics/particles/leaf_attack'), - 'thunder': import_folder('../Graphics/graphics/particles/thunder'), + # attacks + 'claw': import_folder(f'{asset_path}/graphics/particles/claw'), + 'slash': import_folder(f'{asset_path}/graphics/particles/slash'), + 'sparkle': import_folder(f'{asset_path}/graphics/particles/sparkle'), + 'leaf_attack': import_folder(f'{asset_path}/graphics/particles/leaf_attack'), + 'thunder': import_folder(f'{asset_path}/graphics/particles/thunder'), # monster deaths - 'squid': import_folder('../Graphics/graphics/particles/smoke_orange'), - 'raccoon': import_folder('../Graphics/graphics/particles/raccoon'), - 'spirit': import_folder('../Graphics/graphics/particles/nova'), - 'bamboo': import_folder('../Graphics/graphics/particles/bamboo'), + 'squid': import_folder(f'{asset_path}/graphics/particles/smoke_orange'), + 'raccoon': import_folder(f'{asset_path}/graphics/particles/raccoon'), + 'spirit': import_folder(f'{asset_path}/graphics/particles/nova'), + 'bamboo': import_folder(f'{asset_path}/graphics/particles/bamboo'), - # leafs + # leafs 'leaf': ( - import_folder('../Graphics/graphics/particles/leaf1'), - import_folder('../Graphics/graphics/particles/leaf2'), - import_folder('../Graphics/graphics/particles/leaf3'), - import_folder('../Graphics/graphics/particles/leaf4'), - import_folder('../Graphics/graphics/particles/leaf5'), - import_folder('../Graphics/graphics/particles/leaf6'), - self.reflect_images(import_folder('../Graphics/graphics/particles/leaf1')), - self.reflect_images(import_folder('../Graphics/graphics/particles/leaf2')), - self.reflect_images(import_folder('../Graphics/graphics/particles/leaf3')), - self.reflect_images(import_folder('../Graphics/graphics/particles/leaf4')), - self.reflect_images(import_folder('../Graphics/graphics/particles/leaf5')), - self.reflect_images(import_folder('../Graphics/graphics/particles/leaf6')) + import_folder(f'{asset_path}/graphics/particles/leaf1'), + import_folder(f'{asset_path}/graphics/particles/leaf2'), + import_folder(f'{asset_path}/graphics/particles/leaf3'), + import_folder(f'{asset_path}/graphics/particles/leaf4'), + import_folder(f'{asset_path}/graphics/particles/leaf5'), + import_folder(f'{asset_path}/graphics/particles/leaf6'), + self.reflect_images(import_folder( + f'{asset_path}/graphics/particles/leaf1')), + self.reflect_images(import_folder( + f'{asset_path}/graphics/particles/leaf2')), + self.reflect_images(import_folder( + f'{asset_path}/graphics/particles/leaf3')), + self.reflect_images(import_folder( + f'{asset_path}/graphics/particles/leaf4')), + self.reflect_images(import_folder( + f'{asset_path}/graphics/particles/leaf5')), + self.reflect_images(import_folder( + f'{asset_path}/graphics/particles/leaf6')) ) - } + } def reflect_images(self, frames): new_frames = [] @@ -49,12 +63,13 @@ class AnimationPlayer: def create_grass_particles(self, position, groups): animation_frames = choice(self.frames['leaf']) - ParticleEffect(position, animation_frames,groups) + ParticleEffect(position, animation_frames, groups) def generate_particles(self, animation_type, position, groups): animation_frames = self.frames[animation_type] ParticleEffect(position, animation_frames, groups) + class ParticleEffect(pygame.sprite.Sprite): def __init__(self, position, animation_frames, groups): super().__init__(groups) @@ -62,15 +77,15 @@ class ParticleEffect(pygame.sprite.Sprite): self.animation_speed = 0.15 self.frames = animation_frames self.image = self.frames[self.frame_index] - self.rect = self.image.get_rect(center = position) + self.rect = self.image.get_rect(center=position) self.sprite_type = 'magic' - + def animate(self): self.frame_index += self.animation_speed if self.frame_index >= len(self.frames): self.kill() else: self.image = self.frames[int(self.frame_index)] - + def update(self): self.animate() diff --git a/game/effects/weapon_effects.py b/game/effects/weapon_effects.py index 1762803..893e011 100644 --- a/game/effects/weapon_effects.py +++ b/game/effects/weapon_effects.py @@ -1,23 +1,33 @@ +import os import pygame + class Weapon(pygame.sprite.Sprite): - + def __init__(self, player, groups): super().__init__(groups) - + + script_dir = os.path.dirname(os.path.abspath(__file__)) + asset_path = os.path.join( + script_dir, '../..', 'assets') + self.sprite_type = 'weapon' direction = player.status.split('_')[0] - + # Graphic - full_path = f"../Graphics/graphics/weapons/{player.weapon}/{direction}.png" + full_path = f"{asset_path}/graphics/weapons/{player._input.combat.weapon}/{direction}.png" self.image = pygame.image.load(full_path).convert_alpha() - + # Sprite Placement if direction == 'right': - self.rect = self.image.get_rect(midleft = player.rect.midright + pygame.math.Vector2(0, 16)) + self.rect = self.image.get_rect( + midleft=player.rect.midright + pygame.math.Vector2(0, 16)) elif direction == 'left': - self.rect = self.image.get_rect(midright = player.rect.midleft + pygame.math.Vector2(0, 16)) + self.rect = self.image.get_rect( + midright=player.rect.midleft + pygame.math.Vector2(0, 16)) elif direction == 'down': - self.rect = self.image.get_rect(midtop = player.rect.midbottom + pygame.math.Vector2(-10, 0)) + self.rect = self.image.get_rect( + midtop=player.rect.midbottom + pygame.math.Vector2(-10, 0)) else: - self.rect = self.image.get_rect(midbottom = player.rect.midtop + pygame.math.Vector2(-10, 0)) + self.rect = self.image.get_rect( + midbottom=player.rect.midtop + pygame.math.Vector2(-10, 0)) diff --git a/game/entities/__init__.py b/game/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game/entities/components/__init__.py b/game/entities/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game/entities/components/_input.py b/game/entities/components/_input.py new file mode 100644 index 0000000..be387c5 --- /dev/null +++ b/game/entities/components/_input.py @@ -0,0 +1,138 @@ +import pygame +from random import randint + +from configs.game.spell_config import magic_data +from configs.game.weapon_config import weapon_data + +from .movement import MovementHandler +from .combat import CombatHandler + + +class InputHandler: + + def __init__(self, sprite_type, status): + self.status = status + + # Setup Movement + self.movement = MovementHandler(sprite_type) + self.move_cooldown = 15 + self.can_move = True + self.move_time = None + + # Setup Combat + self.combat = CombatHandler() + self.attacking = False + self.attack_cooldown = 400 + self.attack_time = None + self.can_move = True + + # Setup Special Actions + self.can_rotate_weapon = True + self.weapon_rotation_time = None + self.rotate_attack_cooldown = 600 + + self.can_swap_magic = True + self.magic_swap_time = None + + def check_input(self, speed, hitbox, obstacle_sprites, rect, player): + + if not self.attacking and self.can_move: + keys = pygame.key.get_pressed() + button = randint(0, 7) + + self.move_time = pygame.time.get_ticks() + + # Movement Input + if button == 0: # keys[pygame.K_w]: + self.movement.direction.y = -1 + self.status = 'up' + self.can_move = False + elif button == 1: # keys[pygame.K_s]: + self.movement.direction.y = 1 + self.status = 'down' + self.can_move = False + else: + self.movement.direction.y = 0 + + if button == 2: # keys[pygame.K_a]: + self.movement.direction.x = -1 + self.status = 'left' + self.can_move = False + elif button == 3: # keys[pygame.K_d]: + self.movement.direction.x = 1 + self.status = 'right' + self.can_move = False + else: + self.movement.direction.x = 0 + + self.movement.move(speed, hitbox, obstacle_sprites, rect) + + # Combat Input + if button == 4 and not self.attacking: # keys[pygame.K_e] + self.attacking = True + self.attack_time = pygame.time.get_ticks() + self.combat.create_attack_sprite(player) + # self.weapon_attack_sound.play() + + # Magic Input + if button == 5: # keys[pygame.K_q]: + self.attacking = True + self.attack_time = pygame.time.get_ticks() + + self.combat.magic = list(magic_data.keys())[ + self.combat.magic_index] + + strength = list(magic_data.values())[ + self.combat.magic_index]['strength'] + player.stats.magic + + cost = list(magic_data.values())[ + self.combat.magic_index]['cost'] + print(self.combat.magic) + self.combat.create_magic_sprite( + player, self.combat.magic, strength, cost) + + # Rotating Weapons + if button == 6 and self.can_rotate_weapon: # keys[pygame.K_LSHIFT] + self.can_rotate_weapon = False + self.weapon_rotation_time = pygame.time.get_ticks() + if self.combat.weapon_index < len(list(weapon_data.keys())) - 1: + self.combat.weapon_index += 1 + else: + self.combat.weapon_index = 0 + + self.combat.weapon = list(weapon_data.keys())[ + self.combat.weapon_index] + + # Swap Spells + if button == 7 and self.can_swap_magic: # keys[pygame.K_LCTRL] : + self.can_swap_magic = False + self.magic_swap_time = pygame.time.get_ticks() + if self.combat.magic_index < len(list(magic_data.keys())) - 1: + self.combat.magic_index += 1 + else: + self.combat.magic_index = 0 + + def cooldowns(self, vulnerable): + current_time = pygame.time.get_ticks() + + if self.attacking: + if current_time - self.attack_time > self.attack_cooldown + weapon_data[self.combat.weapon]['cooldown']: + self.attacking = False + if self.combat.current_attack: + self.combat.delete_attack_sprite() + + if not self.can_rotate_weapon: + if current_time - self.weapon_rotation_time > self.rotate_attack_cooldown: + self.can_rotate_weapon = True + + if not self.can_swap_magic: + if current_time - self.magic_swap_time > self.rotate_attack_cooldown: + self.can_swap_magic = True + + if not vulnerable: + if current_time - self.hurt_time >= self.invulnerability_duration: + vulnerable = True + + if not self.can_move: + if current_time - self.move_time >= self.move_cooldown: + self.can_move = True diff --git a/game/entities/components/animaton.py b/game/entities/components/animaton.py new file mode 100644 index 0000000..0fa0cff --- /dev/null +++ b/game/entities/components/animaton.py @@ -0,0 +1,69 @@ +import os +import pygame +from math import sin + +from utils.resource_loader import import_folder +from configs.system.window_config import HITBOX_OFFSET + + +class AnimationHandler: + + def __init__(self): + self.frame_index = 0 + self.animation_speed = 0.15 + + def import_assets(self, sprite_type, position, name=None): + script_dir = os.path.dirname(os.path.abspath(__file__)) + asset_path = os.path.join( + script_dir, '../../..', 'assets', 'graphics') + if sprite_type == 'player': + + character_path = f"{asset_path}/player" + + # Import Graphic Assets + self.image = pygame.image.load( + f"{character_path}/down/down_0.png").convert_alpha() + self.rect = self.image.get_rect(topleft=position) + self.hitbox = self.rect.inflate(HITBOX_OFFSET[sprite_type]) + self.animations = { + 'up': [], 'down': [], 'left': [], 'right': [], + 'up_idle': [], 'down_idle': [], 'left_idle': [], 'right_idle': [], + 'up_attack': [], 'down_attack': [], 'left_attack': [], 'right_attack': [] + } + for animation in self.animations.keys(): + full_path = f"{character_path}/{animation}" + self.animations[animation] = import_folder(full_path) + + elif sprite_type == 'enemy': + + character_path = f"{asset_path}/monsters/{name}" + self.animations = {'idle': [], 'move': [], 'attack': []} + main_path = f"{asset_path}/monsters/{name}" + + for animation in self.animations.keys(): + self.animations[animation] = import_folder( + f"{main_path}/{animation}") + + def animate(self, status, vulnerable): + animation = self.animations[status] + + self.frame_index += self.animation_speed + if self.frame_index >= len(animation): + self.frame_index = 0 + + # Set the image + self.image = animation[int(self.frame_index)] + self.rect = self.image.get_rect(center=self.hitbox.center) + + if not vulnerable: + alpha = self.wave_value() + self.image.set_alpha(alpha) + else: + self.image.set_alpha(255) + + def wave_value(self): + value = sin(pygame.time.get_ticks()) + if value >= 0: + return 255 + else: + return 0 diff --git a/game/entities/components/combat.py b/game/entities/components/combat.py new file mode 100644 index 0000000..0f99653 --- /dev/null +++ b/game/entities/components/combat.py @@ -0,0 +1,59 @@ +import os +import pygame + +from effects.weapon_effects import Weapon +from effects.magic_effects import MagicPlayer +from effects.particle_effects import AnimationPlayer + +from configs.game.weapon_config import weapon_data +from configs.game.spell_config import magic_data + + +class CombatHandler: + + def __init__(self): + + # Setup Combat + self.animation_player = AnimationPlayer() + self.magic_player = MagicPlayer(self.animation_player) + self.current_attack = None + + # Spell and Weapon Rotation + self.weapon_index = 0 + self.weapon = list(weapon_data.keys())[self.weapon_index] + + self.magic_index = 0 + self.magic = list(magic_data.keys())[self.magic_index] + + # Damage Timer + self.vulnerable = True + self.hurt_time = None + self.invulnerability_duration = 300 + + # Import Sounds +# script_dir = os.path.dirname(os.path.abspath(__file__)) +# asset_path = os.path.join( +# script_dir, '../../..', 'assets', 'audio') +# +# self.weapon_attack_sound = pygame.mixer.Sound( +# f"{asset_path}/sword.wav") +# self.weapon_attack_sound.set_volume(0.2) + + def create_attack_sprite(self, player): + self.current_attack = Weapon( + player, [player.visible_sprites, player.attack_sprites]) + + def delete_attack_sprite(self): + if self.current_attack: + self.current_attack.kill() + self.current_attack = None + + def create_magic_sprite(self, player, style, strength, cost): + print(style) + if style == 'heal': + self.magic_player.heal(player, strength, cost, [ + player.visible_sprites]) + + if style == 'flame': + self.magic_player.flame( + player, cost, [player.visible_sprites, player.attack_sprites]) diff --git a/game/entities/components/movement.py b/game/entities/components/movement.py new file mode 100644 index 0000000..a2b52dc --- /dev/null +++ b/game/entities/components/movement.py @@ -0,0 +1,41 @@ +import pygame + + +class MovementHandler: + + def __init__(self, sprite_type): + + self.direction = pygame.math.Vector2() + + def move(self, speed, hitbox, obstacle_sprites, rect): + if self.direction.magnitude() != 0: + self.direction = self.direction.normalize() + + hitbox.x += self.direction.x * speed + self.collision('horizontal', hitbox, obstacle_sprites) + hitbox.y += self.direction.y * speed + self.collision('vertical', hitbox, obstacle_sprites) + rect.center = hitbox.center + + def collision(self, direction, hitbox, obstacle_sprites): + if direction == 'horizontal': + for sprite in obstacle_sprites: + # The following works for static obstacles only + if sprite.hitbox.colliderect(hitbox): + # Moving Right + if self.direction.x > 0: + hitbox.right = sprite.hitbox.left + # Moving Left + if self.direction.x < 0: + hitbox.left = sprite.hitbox.right + + if direction == 'vertical': + for sprite in obstacle_sprites: + # The following works for static obstacles only + if sprite.hitbox.colliderect(hitbox): + # Moving Down + if self.direction.y > 0: + hitbox.bottom = sprite.hitbox.top + # Moving Up + if self.direction.y < 0: + hitbox.top = sprite.hitbox.bottom diff --git a/game/entities/components/stats.py b/game/entities/components/stats.py new file mode 100644 index 0000000..c383017 --- /dev/null +++ b/game/entities/components/stats.py @@ -0,0 +1,45 @@ +class StatsHandler: + + def __init__(self): + self.stats = { + 'health': 100, + 'energy': 60, + 'attack': 10, + 'magic': 4, + 'speed': 5 + } + + self.max_stats = { + 'health': 300, + 'energy': 150, + 'attack': 20, + 'magic': 10, + 'speed': 10 + } + + self.upgrade_costs = { + 'health': 100, + 'energy': 100, + 'attack': 100, + 'magic': 100, + 'speed': 100 + } + + self.health = self.stats['health'] + self.energy = self.stats['energy'] + self.attack = self.stats['attack'] + self.magic = self.stats['magic'] + self.speed = self.stats['speed'] + self.exp = 10000 + + def energy_recovery(self): + if self.energy < self.stats['energy']: + self.energy += 0.01 * self.stats['magic'] + else: + self.energy = self.stats['energy'] + + def get_value_by_index(self, index): + return list(self.stats.values())[index] + + def get_cost_by_index(self, index): + return list(self.upgrade_costs.values())[index] diff --git a/game/entities/entity.py b/game/entities/entity.py new file mode 100644 index 0000000..e0e8d29 --- /dev/null +++ b/game/entities/entity.py @@ -0,0 +1,9 @@ +import pygame +from math import sin +import random + + +class Entity(): + + def __init__(self, groups, position, sprite_type): + super().__init__(groups) diff --git a/game/entities/observer.py b/game/entities/observer.py new file mode 100644 index 0000000..7b3a70e --- /dev/null +++ b/game/entities/observer.py @@ -0,0 +1,65 @@ +import os +import pygame + +from configs.system.window_config import HITBOX_OFFSET + + +class Observer(pygame.sprite.Sprite): + + def __init__(self, position, groups): + super().__init__(groups) + + self.sprite_type = 'camera' + + script_dir = os.path.dirname(os.path.abspath(__file__)) + asset_path = os.path.join( + script_dir, '../..', 'assets') + + self.image = pygame.image.load( + f"{asset_path}/graphics/observer.png").convert_alpha() + self.rect = self.image.get_rect(topleft=position) + self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type]) + + # Stats + self.exp = -1 # This prints OBSERVER in the UI + self.speed = 10 # Speed for moving around + + # Movement + self.direction = pygame.math.Vector2() + + def input(self): + keys = pygame.key.get_pressed() + + # Movement Input + if keys[pygame.K_w]: + self.direction.y = -1 + self.status = 'up' + self.can_move = False + elif keys[pygame.K_s]: + self.direction.y = 1 + self.status = 'down' + self.can_move = False + else: + self.direction.y = 0 + + if keys[pygame.K_a]: + self.direction.x = -1 + self.status = 'left' + self.can_move = False + elif keys[pygame.K_d]: + self.direction.x = 1 + self.status = 'right' + self.can_move = False + else: + self.direction.x = 0 + + def move(self, speed): + if self.direction.magnitude() != 0: + self.direction = self.direction.normalize() + self.hitbox.x += self.direction.x * speed + self.hitbox.y += self.direction.y * speed + self.rect.center = self.hitbox.center + + def update(self): + self.input() + self.move(self.speed) diff --git a/game/entities/player.py b/game/entities/player.py new file mode 100644 index 0000000..f660eb0 --- /dev/null +++ b/game/entities/player.py @@ -0,0 +1,67 @@ +import pygame + +from .components.combat import CombatHandler +from .components.stats import StatsHandler +from .components._input import InputHandler +from .components.animaton import AnimationHandler + + +class Player(pygame.sprite.Sprite): + + def __init__(self, position, groups, obstacle_sprites, visible_sprites, attack_sprites): + super().__init__(groups) + + # Setup Sprites + self.sprite_type = 'player' + self.visible_sprites = visible_sprites + self.attack_sprites = attack_sprites + self.obstacle_sprites = obstacle_sprites + self.status = 'down' + + # Setup Inputs + self._input = InputHandler( + self.sprite_type, self.status) + + # Setup Graphics + self.animation = AnimationHandler() + self.animation.import_assets(self.sprite_type, position) + self.animate = self.animation.animate + self.image = self.animation.image + self.animate(self.status, self._input.combat.vulnerable) + self.rect = self.animation.rect + + # Setup Stats + self.stats = StatsHandler() + + def get_status(self): + if self._input.movement.direction.x == 0 and self._input.movement.direction.y == 0: + if not 'idle' in self.status and not 'attack' in self.status: + self.status += '_idle' + + if self._input.attacking: + self._input.movement.direction.x = 0 + self._input.movement.direction.y = 0 + if not 'attack' in self.status: + if 'idle' in self.status: + self.status = self.status.replace('idle', 'attack') + else: + self.status += '_attack' + else: + if 'attack' in self.status: + self.status = self.status.replace('_attack', '') + + def update(self): + # Refresh objects based on input + self._input.check_input( + self.stats.stats['speed'], self.animation.hitbox, self.obstacle_sprites, self.animation.rect, self) + self.status = self._input.status + + # Animate + self.get_status() + self.animation.animate(self.status, self._input.combat.vulnerable) + self.image = self.animation.image + self.rect = self.animation.rect + + # Cooldowns and Regen + self.stats.energy_recovery() + self._input.cooldowns(self._input.combat.vulnerable) diff --git a/game/interface/__init__.py b/game/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game/ui/ui.py b/game/interface/ui.py similarity index 56% rename from game/ui/ui.py rename to game/interface/ui.py index 7c922e0..d504c5b 100644 --- a/game/ui/ui.py +++ b/game/interface/ui.py @@ -1,98 +1,116 @@ import pygame -from utils.settings import * + +from configs.game.weapon_config import * +from configs.game.spell_config import * + +from .ui_settings import * + class UI: def __init__(self): - + + script_dir = os.path.dirname(os.path.abspath(__file__)) + asset_path = os.path.join( + script_dir, '../..', 'assets') + # General info self.display_surface = pygame.display.get_surface() self.font = pygame.font.Font(UI_FONT, UI_FONT_SIZE) - + # Bar setup - self.health_bar_rect = pygame.Rect(10, 10, HEALTH_BAR_WIDTH, BAR_HEIGHT) - self.energy_bar_rect = pygame.Rect(10, 34, ENERGY_BAR_WIDTH, BAR_HEIGHT) - + self.health_bar_rect = pygame.Rect( + 10, 10, HEALTH_BAR_WIDTH, BAR_HEIGHT) + self.energy_bar_rect = pygame.Rect( + 10, 34, ENERGY_BAR_WIDTH, BAR_HEIGHT) + # Convert weapon dictionary self.weapon_graphics = [] for weapon in weapon_data.values(): path = weapon['graphic'] weapon = pygame.image.load(path).convert_alpha() self.weapon_graphics.append(weapon) - + # Convert weapon dictionary self.magic_graphics = [] for spell in magic_data.values(): path = spell['graphic'] spell = pygame.image.load(path).convert_alpha() self.magic_graphics.append(spell) - - + def show_bar(self, current_amount, max_amount, bg_rect, color): - + # Draw background pygame.draw.rect(self.display_surface, UI_BG_COLOR, bg_rect) - + # Convert stat amount to pixels ratio = current_amount / max_amount current_width = bg_rect.width * ratio current_rect = bg_rect.copy() current_rect.width = current_width - + # Draw stat bar pygame.draw.rect(self.display_surface, color, current_rect) pygame.draw.rect(self.display_surface, UI_BORDER_COLOR, bg_rect, 4) - + def show_exp(self, exp): if exp >= 0: - text_surf = self.font.render(f"EXP: {str(int(exp))}", False, TEXT_COLOR) + text_surf = self.font.render( + f"EXP: {str(int(exp))}", False, TEXT_COLOR) x = self.display_surface.get_size()[0] - 20 y = self.display_surface.get_size()[1] - 20 - text_rect = text_surf.get_rect(bottomright = (x,y)) - - pygame.draw.rect(self.display_surface, UI_BG_COLOR, text_rect.inflate(10, 10)) + text_rect = text_surf.get_rect(bottomright=(x, y)) + + pygame.draw.rect(self.display_surface, UI_BG_COLOR, + text_rect.inflate(10, 10)) self.display_surface.blit(text_surf, text_rect) - pygame.draw.rect(self.display_surface, UI_BORDER_COLOR, text_rect.inflate(10, 10), 4) + pygame.draw.rect(self.display_surface, + UI_BORDER_COLOR, text_rect.inflate(10, 10), 4) else: text_surf = self.font.render(f"OBSERVER", False, TEXT_COLOR) x = self.display_surface.get_size()[0] - 20 y = self.display_surface.get_size()[1] - 20 - text_rect = text_surf.get_rect(bottomright = (x,y)) - - pygame.draw.rect(self.display_surface, UI_BG_COLOR, text_rect.inflate(10, 10)) + text_rect = text_surf.get_rect(bottomright=(x, y)) + + pygame.draw.rect(self.display_surface, UI_BG_COLOR, + text_rect.inflate(10, 10)) self.display_surface.blit(text_surf, text_rect) - pygame.draw.rect(self.display_surface, UI_BORDER_COLOR, text_rect.inflate(10, 10), 4) - - + pygame.draw.rect(self.display_surface, + UI_BORDER_COLOR, text_rect.inflate(10, 10), 4) + def selection_box(self, left, top, has_rotated): bg_rect = pygame.Rect(left, top, ITEM_BOX_SIZE, ITEM_BOX_SIZE) pygame.draw.rect(self.display_surface, UI_BG_COLOR, bg_rect) if not has_rotated: - pygame.draw.rect(self.display_surface, UI_BORDER_COLOR_ACTIVE, bg_rect, 4) + pygame.draw.rect(self.display_surface, + UI_BORDER_COLOR_ACTIVE, bg_rect, 4) else: pygame.draw.rect(self.display_surface, UI_BORDER_COLOR, bg_rect, 4) return bg_rect - + def weapon_overlay(self, weapon_index, has_rotated): bg_rect = self.selection_box(10, 630, has_rotated) weapon_surf = self.weapon_graphics[weapon_index] - weapon_rect = weapon_surf.get_rect(center = bg_rect.center) - + weapon_rect = weapon_surf.get_rect(center=bg_rect.center) + self.display_surface.blit(weapon_surf, weapon_rect) - + def magic_overlay(self, magic_index, has_swaped): - bg_rect = self.selection_box(100, 630, has_swaped) + bg_rect = self.selection_box(100, 630, has_swaped) magic_surf = self.magic_graphics[magic_index] - magic_rect = magic_surf.get_rect(center = bg_rect.center) - + magic_rect = magic_surf.get_rect(center=bg_rect.center) + self.display_surface.blit(magic_surf, magic_rect) - + def display(self, player): if player.sprite_type == 'player': - self.show_bar(player.health, player.stats['health'], self.health_bar_rect, HEALTH_COLOR) - self.show_bar(player.energy, player.stats['energy'], self.energy_bar_rect, ENERGY_COLOR) - self.show_exp(player.exp) - self.weapon_overlay(player.weapon_index, player.can_rotate_weapon) - self.magic_overlay(player.magic_index, player.can_swap_magic) + self.show_bar( + player.stats.health, player.stats.stats['health'], self.health_bar_rect, HEALTH_COLOR) + self.show_bar( + player.stats.energy, player.stats.stats['energy'], self.energy_bar_rect, ENERGY_COLOR) + self.show_exp(player.stats.exp) + self.weapon_overlay(player._input.combat.weapon_index, + player._input.can_rotate_weapon) + self.magic_overlay(player._input.combat.magic_index, + player._input.can_swap_magic) if player.sprite_type == 'camera': self.show_exp(player.exp) - diff --git a/game/ui/ui_config.py b/game/interface/ui_settings.py similarity index 72% rename from game/ui/ui_config.py rename to game/interface/ui_settings.py index 47437a5..cfa39fe 100644 --- a/game/ui/ui_config.py +++ b/game/interface/ui_settings.py @@ -1,9 +1,15 @@ +import os + +script_dir = os.path.dirname(os.path.abspath(__file__)) +asset_path = os.path.join( + script_dir, '../..', 'assets') + # ui BAR_HEIGHT = 20 HEALTH_BAR_WIDTH = 200 ENERGY_BAR_WIDTH = 140 ITEM_BOX_SIZE = 80 -UI_FONT = '../Graphics/graphics/font/joystix.ttf' +UI_FONT = f"{asset_path}/font/joystix.ttf" UI_FONT_SIZE = 18 # general colors diff --git a/game/ui/upgrade.py b/game/interface/upgrade.py similarity index 68% rename from game/ui/upgrade.py rename to game/interface/upgrade.py index b950ac3..9cfb46f 100644 --- a/game/ui/upgrade.py +++ b/game/interface/upgrade.py @@ -1,31 +1,34 @@ import pygame -from utils.settings import * + +from .ui_settings import * + class Upgrade: - + def __init__(self, player): - + # General setup self.display_surface = pygame.display.get_surface() self.player = player - self.attribute_num = len(player.stats) - self.attribute_names = list(player.stats.keys()) - self.max_values = list(player.max_stats.values()) + self.attribute_num = len(player.stats.stats) + self.attribute_names = list(player.stats.stats.keys()) + self.max_values = list(player.stats.max_stats.values()) self.font = pygame.font.Font(UI_FONT, UI_FONT_SIZE) - + # Defining upgrade boxes self.height = self.display_surface.get_size()[1] * 0.8 - self.width = self.display_surface.get_size()[0] // (self.attribute_num + 1) + self.width = self.display_surface.get_size()[ + 0] // (self.attribute_num + 1) self.create_boxes() - + # Selection System self.selection_index = 0 self.selection_time = None self.can_move = True - + def input(self): keys = pygame.key.get_pressed() - + if self.can_move: if keys[pygame.K_d]: self.selection_index += 1 @@ -39,98 +42,99 @@ class Upgrade: self.selection_index = self.attribute_num - 1 self.can_move = False self.selection_time = pygame.time.get_ticks() - + if keys[pygame.K_SPACE]: self.can_move = False self.selection_time = pygame.time.get_ticks() self.box_list[self.selection_index].trigger(self.player) - + def selection_cooldown(self): if not self.can_move: current_time = pygame.time.get_ticks() if current_time - self.selection_time >= 150: self.can_move = True - + def create_boxes(self): self.box_list = [] - + for box, index in enumerate(range(self.attribute_num)): - + # Horizontal position full_width = self.display_surface.get_size()[0] increment = full_width // self.attribute_num left = (box * increment) + (increment - self.width) // 2 - + # Vertical position top = self.display_surface.get_size()[1] * 0.1 - + box = Box(left, top, self.width, self.height, index, self.font) self.box_list.append(box) - - + def display(self): - + self.input() self.selection_cooldown() - + for index, box in enumerate(self.box_list): # Get attributes name = self.attribute_names[index] - value = self.player.get_value_by_index(index) + value = self.player.stats.get_value_by_index(index) max_value = self.max_values[index] - cost = self.player.get_cost_by_index(index) - box.display(self.display_surface, self.selection_index, name, value, max_value, cost) - - + cost = self.player.stats.get_cost_by_index(index) + box.display(self.display_surface, self.selection_index, + name, value, max_value, cost) + + class Box: - def __init__(self, l, t, w, h, index, font): - self.rect = pygame.Rect(l, t, w, h) + def __init__(self, left, top, width, height, index, font): + self.rect = pygame.Rect(left, top, width, height) self.index = index self.font = font - + def display_names(self, surface, name, cost, selected): color = TEXT_COLOR_SELECTED if selected else TEXT_COLOR - + # Title title_surf = self.font.render(name, False, color) - title_rect = title_surf.get_rect(midtop = self.rect.midtop + pygame.math.Vector2(0, 20)) + title_rect = title_surf.get_rect( + midtop=self.rect.midtop + pygame.math.Vector2(0, 20)) # Cost cost_surf = self.font.render(f'Cost: {int(cost)}', False, color) - cost_rect = cost_surf.get_rect(midbottom = self.rect.midbottom +- pygame.math.Vector2(0, 20)) - + cost_rect = cost_surf.get_rect( + midbottom=self.rect.midbottom + - pygame.math.Vector2(0, 20)) + # Draw surface.blit(title_surf, title_rect) surface.blit(cost_surf, cost_rect) - + def display_bar(self, surface, value, max_value, selected): - + # Line setup top = self.rect.midtop + pygame.math.Vector2(0, 60) - bottom = self.rect.midbottom - pygame.math.Vector2(0,60) + bottom = self.rect.midbottom - pygame.math.Vector2(0, 60) color = BAR_COLOR_SELECTED if selected else BAR_COLOR - + # Bar setup full_height = bottom[1] - top[1] relative_number = (value / max_value) * full_height - value_rect = pygame.Rect(top[0] - 15, bottom[1] - relative_number, 30, 10) - + value_rect = pygame.Rect( + top[0] - 15, bottom[1] - relative_number, 30, 10) + # Draw elements pygame.draw.line(surface, color, top, bottom, 5) pygame.draw.rect(surface, color, value_rect) - + def trigger(self, player): - upgrade_attribute = list(player.stats.keys())[self.index] - - if player.exp >= player.upgrade_costs[upgrade_attribute] and player.stats[upgrade_attribute] < player.max_stats[upgrade_attribute]: - player.exp -= player.upgrade_costs[upgrade_attribute] - player.stats[upgrade_attribute] *= 1.2 - player.upgrade_costs[upgrade_attribute] *= 1.4 - - if player.stats[upgrade_attribute] > player.max_stats[upgrade_attribute]: - player.stats[upgrade_attribute] = player.max_stats[upgrade_attribute] - - - + upgrade_attribute = list(player.stats.stats.keys())[self.index] + + if player.stats.exp >= player.stats.upgrade_costs[upgrade_attribute] and player.stats.stats[upgrade_attribute] < player.stats.max_stats[upgrade_attribute]: + player.stats.exp -= player.stats.upgrade_costs[upgrade_attribute] + player.stats.stats[upgrade_attribute] *= 1.2 + player.stats.upgrade_costs[upgrade_attribute] *= 1.4 + + if player.stats.stats[upgrade_attribute] > player.stats.max_stats[upgrade_attribute]: + player.stats.stats[upgrade_attribute] = player.stats.max_stats[upgrade_attribute] + def display(self, surface, selection_num, name, value, max_value, cost): if self.index == selection_num: pygame.draw.rect(surface, UPGRADE_BG_COLOR_SELECTED, self.rect) @@ -138,6 +142,7 @@ class Box: else: pygame.draw.rect(surface, UI_BG_COLOR, self.rect) pygame.draw.rect(surface, UI_BORDER_COLOR, self.rect, 4) - + self.display_names(surface, name, cost, self.index == selection_num) - self.display_bar(surface, value, max_value, self.index == selection_num) + self.display_bar(surface, value, max_value, + self.index == selection_num) diff --git a/game/level/__init__.py b/game/level/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game/level/camera.py b/game/level/camera.py new file mode 100644 index 0000000..d991a62 --- /dev/null +++ b/game/level/camera.py @@ -0,0 +1,42 @@ +import os +import pygame + + +class Camera(pygame.sprite.Group): + + def __init__(self): + super().__init__() + + # General Setup + self.display_surface = pygame.display.get_surface() + self.half_width = self.display_surface.get_size()[0] // 2 + self.half_height = self.display_surface.get_size()[1] // 2 + self.offset = pygame.math.Vector2(100, 200) + + # Creating the floor + script_dir = os.path.dirname(os.path.abspath(__file__)) + image_path = os.path.join( + script_dir, '../..', 'assets', 'graphics', 'tilemap', 'ground.png') + + self.floor_surf = pygame.image.load(image_path).convert() + self.floor_rect = self.floor_surf.get_rect(topleft=(0, 0)) + + def custom_draw(self, player): + self.sprite_type = player.sprite_type + # Getting the offset + self.offset.x = player.rect.centerx - self.half_width + self.offset.y = player.rect.centery - self.half_height + + # Drawing the floor + floor_offset_pos = self.floor_rect.topleft - self.offset + self.display_surface.blit(self.floor_surf, floor_offset_pos) + + for sprite in sorted(self.sprites(), key=lambda sprite: sprite.rect.centery): + offset_pos = sprite.rect.topleft - self.offset + self.display_surface.blit(sprite.image, offset_pos) + + def enemy_update(self, player): + enemy_sprites = [sprite for sprite in self.sprites() if hasattr( + sprite, 'sprite_type') and sprite.sprite_type == 'enemy'] + for enemy in enemy_sprites: + enemy.enemy_update(player) diff --git a/game/level/level.py b/game/level/level.py new file mode 100644 index 0000000..be975e7 --- /dev/null +++ b/game/level/level.py @@ -0,0 +1,120 @@ +import os +import pygame + +from random import choice, randint + +from configs.game.spell_config import magic_data +from configs.game.weapon_config import weapon_data +from configs.game.monster_config import monster_data +from configs.system.window_config import TILESIZE + +from utils.debug import debug +from utils.resource_loader import import_csv_layout, import_folder + +from interface.ui import UI +from interface.upgrade import Upgrade + +from effects.magic_effects import MagicPlayer +from effects.particle_effects import AnimationPlayer +from effects.weapon_effects import Weapon + +from entities.observer import Observer +from entities.player import Player + +from .terrain import Tile +from .camera import Camera + + +class Level: + + def __init__(self): + + # General Settings + self.game_paused = False + + # Get display surface + self.display_surface = pygame.display.get_surface() + + # Sprite Group setup + self.visible_sprites = Camera() + self.obstacle_sprites = pygame.sprite.Group() + self.attack_sprites = pygame.sprite.Group() + self.attackable_sprites = pygame.sprite.Group() + + # Sprite setup and entity generation + self.create_map() + + # UI setup + self.ui = UI() + self.upgrade = Upgrade(self.player) + + def create_map(self): + script_dir = os.path.dirname(os.path.abspath(__file__)) + asset_path = os.path.join( + script_dir, '../..', 'assets') + layouts = { + 'boundary': import_csv_layout(f"{asset_path}/map/FloorBlocks.csv"), + 'grass': import_csv_layout(f"{asset_path}/map/Grass.csv"), + 'objects': import_csv_layout(f"{asset_path}/map/Objects.csv"), + 'entities': import_csv_layout(f"{asset_path}/map/Entities.csv") + } + + graphics = { + 'grass': import_folder(f"{asset_path}/graphics/grass"), + 'objects': import_folder(f"{asset_path}/graphics/objects") + } + + for style, layout in layouts.items(): + for row_index, row in enumerate(layout): + for col_index, col in enumerate(row): + if col != '-1': + x = col_index * TILESIZE + y = row_index * TILESIZE + if style == 'boundary': + Tile((x, y), [self.obstacle_sprites], 'invisible') + + if style == 'grass': + random_grass_image = choice(graphics['grass']) + Tile((x, y), [self.visible_sprites, self.obstacle_sprites, + self.attackable_sprites], 'grass', random_grass_image) + + if style == 'objects': + surf = graphics['objects'][int(col)] + Tile((x, y), [self.visible_sprites, + self.obstacle_sprites], 'object', surf) + + if style == 'entities': + # The numbers represent their IDs in .csv files generated from TILED. + if col == '395': + self.observer = Observer( + (x, y), [self.visible_sprites]) + + elif col == '394': + # Player Generation + self.player = Player( + (x, y), [self.visible_sprites], self.obstacle_sprites, self.visible_sprites, self.attack_sprites) + + else: + pass + # monster generation + + def toggle_menu(self): + self.game_paused = not self.game_paused + + def run(self): + # Draw the game + self.visible_sprites.custom_draw(self.player) + self.ui.display(self.player) + debug(self.player.status) + if not self.game_paused: + # Update the game + # self.player.distance_direction_to_player = self.get_state() + self.visible_sprites.update() + # self.visible_sprites.enemy_update(self.player) + # self.player_attack_logic() + else: + if self.visible_sprites.sprite_type == 'player': + self.upgrade.display() + + if self.player.stats.health <= 0: + self.__init__() diff --git a/game/level/terrain.py b/game/level/terrain.py new file mode 100644 index 0000000..9de2c1f --- /dev/null +++ b/game/level/terrain.py @@ -0,0 +1,19 @@ +import pygame + +from configs.system.window_config import * + + +class Tile(pygame.sprite.Sprite): + + def __init__(self, position, groups, sprite_type, surface=pygame.Surface((TILESIZE, TILESIZE))): + super().__init__(groups) + + self.sprite_type = sprite_type + self.image = surface + if sprite_type == 'object': + # Offset + self.rect = self.image.get_rect( + topleft=(position[0], position[1] - TILESIZE)) + else: + self.rect = self.image.get_rect(topleft=position) + self.hitbox = self.rect.inflate(HITBOX_OFFSET[sprite_type]) diff --git a/game/main.py b/game/main.py new file mode 100644 index 0000000..83fd427 --- /dev/null +++ b/game/main.py @@ -0,0 +1,47 @@ +import pygame +import sys + +from configs.system.window_config import WIDTH, HEIGHT, WATER_COLOR, FPS +from utils.debug import debug + +from level.level import Level + + +class Game: + + def __init__(self): + + pygame.init() + + self.screen = pygame.display.set_mode((WIDTH, HEIGHT)) + pygame.display.set_caption('Pneuma') + self.clock = pygame.time.Clock() + + self.level = Level() + + # # Sound + # main_sound = pygame.mixer.Sound('../assets/audio/main.ogg') + # main_sound.set_volume(0.4) + # main_sound.play(loops=-1) + + def run(self): + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_m: + self.level.toggle_menu() + + self.screen.fill(WATER_COLOR) + self.level.run() + pygame.display.update() + self.clock.tick(FPS) + + +if __name__ == '__main__': + + game = Game() + while True: + game.run() diff --git a/game/utils/__init__.py b/game/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/old-old/Game/level.py b/old-old/Game/level.py index d8699ed..3c137f5 100644 --- a/old-old/Game/level.py +++ b/old-old/Game/level.py @@ -16,16 +16,17 @@ from terrain.tiles import Tile from view.observer import Observer from view.camera import Camera + class Level: - + def __init__(self): - + # General Settings self.game_paused = False - + # Get display surface self.display_surface = pygame.display.get_surface() - + # Sprite Group setup self.visible_sprites = Camera() self.obstacle_sprites = pygame.sprite.Group() @@ -58,31 +59,35 @@ class Level: x = col_index * TILESIZE y = row_index * TILESIZE if style == 'boundary': - Tile((x,y), [self.obstacle_sprites], 'invisible') + Tile((x, y), [self.obstacle_sprites], 'invisible') if style == 'grass': random_grass_image = choice(graphics['grass']) - Tile((x,y), [self.visible_sprites, self.obstacle_sprites, self.attackable_sprites], 'grass', random_grass_image) + Tile((x, y), [self.visible_sprites, self.obstacle_sprites, + self.attackable_sprites], 'grass', random_grass_image) if style == 'objects': surf = graphics['objects'][int(col)] - Tile((x,y), [self.visible_sprites, self.obstacle_sprites], 'object', surf) + Tile((x, y), [self.visible_sprites, + self.obstacle_sprites], 'object', surf) if style == 'entities': # The numbers represent their IDs in the map .csv files generated from TILED. if col == '395': - self.observer = Observer((x,y), [self.visible_sprites]) + self.observer = Observer( + (x, y), [self.visible_sprites]) elif col == '394': pass - #player generation + # player generation else: pass - #monster generation + # monster generation def create_attack_sprite(self): - self.current_attack = Weapon(self.player, [self.visible_sprites, self.attack_sprites]) + self.current_attack = Weapon( + self.player, [self.visible_sprites, self.attack_sprites]) def delete_attack_sprite(self): if self.current_attack: @@ -91,13 +96,27 @@ class Level: def create_magic_sprite(self, style, strength, cost): if style == 'heal': - self.magic_player.heal(self.player, strength, cost, [self.visible_sprites]) + self.magic_player.heal(self.player, strength, cost, [ + self.visible_sprites]) if style == 'flame': - self.magic_player.flame(self.player, cost, [self.visible_sprites, self.attack_sprites]) + self.magic_player.flame( + self.player, cost, [self.visible_sprites, self.attack_sprites]) def run(self): # Draw the game self.visible_sprites.custom_draw(self.observer) self.ui.display(self.observer) + if self.game_paused: + if self.visible_sprites.sprite_type == 'player': + self.upgrade.display() + pass + else: + # Update the game + self.player.distance_direction_to_player = self.get_state() + self.visible_sprites.update() + self.visible_sprites.enemy_update(self.player) + self.player_attack_logic() + if self.player.health <= 0: + self.__init__() diff --git a/old-old/Game/main.py b/old-old/Game/main.py index f6d1f24..3487415 100644 --- a/old-old/Game/main.py +++ b/old-old/Game/main.py @@ -6,16 +6,17 @@ from utils.debug import debug from level import Level + class Game: def __init__(self): - + pygame.init() - - self.screen = pygame.display.set_mode((WIDTH,HEIGHT)) + + self.screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption('Pneuma') self.clock = pygame.time.Clock() - + self.level = Level() # # Sound @@ -24,26 +25,22 @@ class Game: # main_sound.play(loops = -1) def run(self): - for event in pygame.event.get(): - if event.type == pygame.QUIT: - pygame.quit() - sys.exit() - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_m: - self.level.toggle_menu() - - self.screen.fill(WATER_COLOR) - self.level.run() - pygame.display.update() - self.clock.tick(FPS) + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_m: + self.level.toggle_menu() + + self.screen.fill(WATER_COLOR) + self.level.run() + pygame.display.update() + self.clock.tick(FPS) + if __name__ == '__main__': - game = Game() while True: game.run() - - - - diff --git a/old/Game/main.py b/old/Game/main.py index b804057..954c2fb 100644 --- a/old/Game/main.py +++ b/old/Game/main.py @@ -6,22 +6,24 @@ from utils.debug import debug from objects.level import Level + class Game: def __init__(self): - + pygame.init() - - self.screen = pygame.display.set_mode((WIDTH,HEIGHT)) + + self.screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption('Pneuma') self.clock = pygame.time.Clock() - + self.level = Level() # Sound main_sound = pygame.mixer.Sound('../Graphics/audio/main.ogg') main_sound.set_volume(0.4) - main_sound.play(loops = -1) + main_sound.play(loops=-1) + def run(self): for event in pygame.event.get(): @@ -31,19 +33,16 @@ class Game: if event.type == pygame.KEYDOWN: if event.key == pygame.K_m: self.level.toggle_menu() - + self.screen.fill(WATER_COLOR) self.level.run() pygame.display.update() self.clock.tick(FPS) + if __name__ == '__main__': game = Game() figure_file = 'rl/plots/pneuma.png' while True: game.run() - - - - diff --git a/old/Game/objects/camera.py b/old/Game/objects/camera.py index 320c4ed..1c3a658 100644 --- a/old/Game/objects/camera.py +++ b/old/Game/objects/camera.py @@ -4,22 +4,24 @@ import random from utils.settings import * from utils.support import import_folder + class Camera(pygame.sprite.Sprite): def __init__(self, position, groups): super().__init__(groups) - + self.sprite_type = 'camera' - - self.image = pygame.image.load('../Graphics/graphics/camera.png').convert_alpha() - self.rect = self.image.get_rect(topleft = position) + + self.image = pygame.image.load( + '../Graphics/graphics/camera.png').convert_alpha() + self.rect = self.image.get_rect(topleft=position) self.hitbox = self.rect.inflate(HITBOX_OFFSET[self.sprite_type]) # Stats - self.exp = -1 # This prints OBSERVER in the UI - self.speed = 10 # Speed for moving around - - #Movement + self.exp = -1 # This prints OBSERVER in the UI + self.speed = 10 # Speed for moving around + + # Movement self.direction = pygame.math.Vector2() def input(self): @@ -47,14 +49,13 @@ class Camera(pygame.sprite.Sprite): self.can_move = False else: self.direction.x = 0 - + def move(self, speed): if self.direction.magnitude() != 0: self.direction = self.direction.normalize() self.hitbox.x += self.direction.x * speed self.hitbox.y += self.direction.y * speed self.rect.center = self.hitbox.center - def update(self): self.input() diff --git a/old/Game/objects/enemy.py b/old/Game/objects/enemy.py index 09711bb..e29aebc 100644 --- a/old/Game/objects/enemy.py +++ b/old/Game/objects/enemy.py @@ -5,23 +5,24 @@ from utils.support import import_folder from objects.entity import Entity + class Enemy(Entity): - + def __init__(self, monster_name, position, groups, obstacle_sprites, damage_player, trigger_death_particles, add_exp, is_AI, state): super().__init__(groups, is_AI, state) - + # General setup self.sprite_type = 'enemy' - + # Graphics setup self.import_graphics(monster_name) self.status = 'idle' self.image = self.animations[self.status][self.frame_index] - + # Movement - self.rect = self.image.get_rect(topleft = position) + self.rect = self.image.get_rect(topleft=position) self.hitbox = self.rect.inflate(0, -10) - + # Stats self.monster_name = monster_name monster_info = monster_data[self.monster_name] @@ -33,7 +34,7 @@ class Enemy(Entity): self.attack_radius = monster_info['attack_radius'] self.notice_radius = monster_info['notice_radius'] self.attack_type = monster_info['attack_type'] - + # Sounds self.attack_sound = pygame.mixer.Sound(monster_info['attack_sound']) self.death_sound = pygame.mixer.Sound('../Graphics/audio/death.wav') @@ -41,7 +42,7 @@ class Enemy(Entity): self.death_sound.set_volume(0.2) self.hit_sound.set_volume(0.2) self.attack_sound.set_volume(0.2) - + # Player Interaction self.can_attack = True self.attack_time = None @@ -49,35 +50,36 @@ class Enemy(Entity): self.damage_player = damage_player self.trigger_death_particles = trigger_death_particles self.add_exp = add_exp - + # Invincibility times self.vulnerable = True self.hit_time = None self.invincibility_duration = 300 self.obstacle_sprites = obstacle_sprites - + def import_graphics(self, name): - self.animations = {'idle':[], 'move':[], 'attack':[]} + self.animations = {'idle': [], 'move': [], 'attack': []} main_path = f"../Graphics/graphics/monsters/{name}" for animation in self.animations.keys(): - self.animations[animation] = import_folder(f"{main_path}/{animation}") + self.animations[animation] = import_folder( + f"{main_path}/{animation}") - def get_player_distance_direction(self,player): + def get_player_distance_direction(self, player): enemy_vector = pygame.math.Vector2(self.rect.center) player_vector = pygame.math.Vector2(player.rect.center) - + distance = (player_vector - enemy_vector).magnitude() if distance > 0: direction = (player_vector - enemy_vector).normalize() else: direction = pygame.math.Vector2() - + return (distance, direction) def get_status(self, player): distance = self.get_player_distance_direction(player)[0] - + if distance <= self.attack_radius and self.can_attack: if self.status != 'attack': self.frame_index = 0 @@ -85,8 +87,8 @@ class Enemy(Entity): elif distance <= self.notice_radius: self.status = 'move' else: - self.status = 'idle' - + self.status = 'idle' + def actions(self, player): if self.status == 'attack': self.attack_sound.play() @@ -99,22 +101,22 @@ class Enemy(Entity): def animate(self): animation = self.animations[self.status] - + self.frame_index += self.animation_speed if self.frame_index >= len(animation): - if self.status =='attack': + if self.status == 'attack': self.can_attack = False self.frame_index = 0 - + self.image = animation[int(self.frame_index)] - self.rect = self.image.get_rect(center = self.hitbox.center) - + self.rect = self.image.get_rect(center=self.hitbox.center) + if not self.vulnerable: alpha = self.wave_value() self.image.set_alpha(alpha) else: self.image.set_alpha(255) - + def cooldowns(self): current_time = pygame.time.get_ticks() @@ -125,7 +127,7 @@ class Enemy(Entity): if not self.vulnerable: if current_time - self.hit_time >= self.invincibility_duration: self.vulnerable = True - + def get_damage(self, player, attack_type): if self.vulnerable: self.hit_sound.play() @@ -136,25 +138,25 @@ class Enemy(Entity): self.health -= player.get_full_magic_damage() self.hit_time = pygame.time.get_ticks() self.vulnerable = False - + def check_death(self): if self.health <= 0: self.trigger_death_particles(self.rect.center, self.monster_name) self.add_exp(self.exp) self.death_sound.play() self.kill() - + def hit_reaction(self): if not self.vulnerable: self.direction *= -self.resistance - + def update(self): self.hit_reaction() self.move(self.speed) self.animate() self.cooldowns() self.check_death() - + def enemy_update(self, player): self.get_status(player) self.actions(player) diff --git a/old/Game/objects/entity.py b/old/Game/objects/entity.py index b37da36..6091191 100644 --- a/old/Game/objects/entity.py +++ b/old/Game/objects/entity.py @@ -4,20 +4,21 @@ import random from utils.settings import * + class Entity(pygame.sprite.Sprite): - def __init__(self, groups, is_AI, state = None): + def __init__(self, groups, is_AI, state=None): super().__init__(groups) - + # Animation self.frame_index = 0 self.animation_speed = 0.15 - + # Movement self.direction = pygame.math.Vector2() self.move_cooldown = 150 self.can_move = True self.move_time = None - + # AI Setup if is_AI: self.possible_actions = { @@ -31,14 +32,15 @@ class Entity(pygame.sprite.Sprite): 7: ('swap_magic', None, None) } self.state = state - self.distance_direction_to_player = [float('inf'), 0, 0, None, None, None, None, None, None, None, None, None]*5 + self.distance_direction_to_player = [ + float('inf'), 0, 0, None, None, None, None, None, None, None, None, None]*5 + + # self.agent = Agent(self.possible_actions, self.distance_direction_to_player, self.stats, self.exp, None, None) - #self.agent = Agent(self.possible_actions, self.distance_direction_to_player, self.stats, self.exp, None, None) - def move(self, speed): if self.direction.magnitude() != 0: self.direction = self.direction.normalize() - + self.hitbox.x += self.direction.x * speed self.collision('horizontal') self.hitbox.y += self.direction.y * speed @@ -56,7 +58,7 @@ class Entity(pygame.sprite.Sprite): # Moving Left if self.direction.x < 0: self.hitbox.left = sprite.hitbox.right - + if direction == 'vertical': for sprite in self.obstacle_sprites: # The following works for static obstacles only @@ -66,51 +68,52 @@ class Entity(pygame.sprite.Sprite): self.hitbox.bottom = sprite.hitbox.top # Moving Up if self.direction.y < 0: - self.hitbox.top = sprite.hitbox.bottom + self.hitbox.top = sprite.hitbox.bottom def input(self): if not self.attacking and self.can_move: keys = pygame.key.get_pressed() button = random.randint(0, 5) - + self.move_time = pygame.time.get_ticks() - + # Movement Input - if button == 0: #keys[pygame.K_w]: + if button == 0: # keys[pygame.K_w]: self.direction.y = -1 self.status = 'up' self.can_move = False - elif button == 1: #keys[pygame.K_s]: + elif button == 1: # keys[pygame.K_s]: self.direction.y = 1 self.status = 'down' self.can_move = False else: self.direction.y = 0 - if button == 2: #keys[pygame.K_a]: + if button == 2: # keys[pygame.K_a]: self.direction.x = -1 self.status = 'left' self.can_move = False - elif button == 3: #keys[pygame.K_d]: + elif button == 3: # keys[pygame.K_d]: self.direction.x = 1 self.status = 'right' self.can_move = False else: self.direction.x = 0 - + # Combat Input - if button == 4: #keys[pygame.K_e]: + if button == 4: # keys[pygame.K_e]: self.attacking = True self.attack_time = pygame.time.get_ticks() self.create_attack_sprite() self.weapon_attack_sound.play() - + # Magic Input - if button == 5: #keys[pygame.K_q]: + if button == 5: # keys[pygame.K_q]: self.attacking = True self.attack_time = pygame.time.get_ticks() style = list(magic_data.keys())[self.magic_index] - strength = list(magic_data.values())[self.magic_index]['strength'] + self.stats['magic'] + strength = list(magic_data.values())[ + self.magic_index]['strength'] + self.stats['magic'] cost = list(magic_data.values())[self.magic_index]['cost'] self.create_magic_sprite(style, strength, cost) @@ -122,9 +125,9 @@ class Entity(pygame.sprite.Sprite): self.weapon_index += 1 else: self.weapon_index = 0 - + self.weapon = list(weapon_data.keys())[self.weapon_index] - + # Swap Spells if keys[pygame.K_LCTRL] and self.can_swap_magic: self.can_swap_magic = False @@ -133,9 +136,9 @@ class Entity(pygame.sprite.Sprite): self.magic_index += 1 else: self.magic_index = 0 - + self.magic = list(magic_data.keys())[self.magic_index] - + def wave_value(self): value = sin(pygame.time.get_ticks()) if value >= 0: diff --git a/old/Game/objects/level.py b/old/Game/objects/level.py index 52b58b9..a7cb002 100644 --- a/old/Game/objects/level.py +++ b/old/Game/objects/level.py @@ -19,50 +19,48 @@ from objects.camera import Camera class Level: - + def __init__(self): - + # General Settings self.game_paused = False - # Get the display surface self.display_surface = pygame.display.get_surface() - + # Sprite Group setup self.visible_sprites = YSortCameraGroup() self.obstacle_sprites = pygame.sprite.Group() self.attack_sprites = pygame.sprite.Group() self.attackable_sprites = pygame.sprite.Group() - + # Combat Sprite setup self.current_attack = None - + # Sprite setup self.create_map() # UI setup self.ui = UI() self.upgrade = Upgrade(self.player) - + # Particle setup self.animation_player = AnimationPlayer() self.magic_player = MagicPlayer(self.animation_player) - + def create_map(self): layouts = { - 'boundary': import_csv_layout('../Graphics/map/map_FloorBlocks.csv'), - 'grass': import_csv_layout('../Graphics/map/map_Grass.csv'), - 'objects': import_csv_layout('../Graphics/map/map_Objects.csv'), - 'entities': import_csv_layout('../Graphics/map/map_Entities.csv') + 'boundary': import_csv_layout('../Graphics/map/map_FloorBlocks.csv'), + 'grass': import_csv_layout('../Graphics/map/map_Grass.csv'), + 'objects': import_csv_layout('../Graphics/map/map_Objects.csv'), + 'entities': import_csv_layout('../Graphics/map/map_Entities.csv') } - + graphics = { - 'grass': import_folder('../Graphics/graphics/grass'), - 'objects': import_folder('../Graphics/graphics/objects') + 'grass': import_folder('../Graphics/graphics/grass'), + 'objects': import_folder('../Graphics/graphics/objects') } - - + for style, layout in layouts.items(): for row_index, row in enumerate(layout): for col_index, col in enumerate(row): @@ -70,24 +68,28 @@ class Level: x = col_index * TILESIZE y = row_index * TILESIZE if style == 'boundary': - Tile((x,y), [self.obstacle_sprites], 'invisible') - + Tile((x, y), [self.obstacle_sprites], 'invisible') + if style == 'grass': random_grass_image = choice(graphics['grass']) - Tile((x,y), [self.visible_sprites, self.obstacle_sprites, self.attackable_sprites], 'grass', random_grass_image) - + Tile((x, y), [self.visible_sprites, self.obstacle_sprites, + self.attackable_sprites], 'grass', random_grass_image) + if style == 'objects': surf = graphics['objects'][int(col)] - Tile((x,y), [self.visible_sprites, self.obstacle_sprites], 'object', surf) - + Tile((x, y), [self.visible_sprites, + self.obstacle_sprites], 'object', surf) + # The numbers represent their IDs in the map .csv files generated from TILED. if style == 'entities': if col == '394': - self.player = Player((x,y), [self.visible_sprites], self.obstacle_sprites, self.create_attack_sprite, self.delete_attack_sprite, self.create_magic_sprite, is_AI = True, state = self.get_state) - - elif col =='395': - self.camera = Camera((x,y), [self.visible_sprites]) - + self.player = Player((x, y), [self.visible_sprites], self.obstacle_sprites, self.create_attack_sprite, + self.delete_attack_sprite, self.create_magic_sprite, is_AI=True, state=self.get_state) + + elif col == '395': + self.camera = Camera( + (x, y), [self.visible_sprites]) + else: if col == '390': monster_name = 'bamboo' @@ -97,48 +99,57 @@ class Level: monster_name = 'raccoon' else: monster_name = 'squid' - - Enemy(monster_name, (x,y), [self.visible_sprites, self.attackable_sprites], self.obstacle_sprites, self.damage_player, self.trigger_death_particles, self.add_exp, is_AI = False, state = None) - + + Enemy(monster_name, (x, y), [self.visible_sprites, self.attackable_sprites], self.obstacle_sprites, + self.damage_player, self.trigger_death_particles, self.add_exp, is_AI=False, state=None) + def create_attack_sprite(self): - self.current_attack = Weapon(self.player, [self.visible_sprites, self.attack_sprites]) - + self.current_attack = Weapon( + self.player, [self.visible_sprites, self.attack_sprites]) + def delete_attack_sprite(self): if self.current_attack: self.current_attack.kill() self.current_attack = None - + def create_magic_sprite(self, style, strength, cost): if style == 'heal': - self.magic_player.heal(self.player, strength, cost, [self.visible_sprites]) - + self.magic_player.heal(self.player, strength, cost, [ + self.visible_sprites]) + if style == 'flame': - self.magic_player.flame(self.player, cost, [self.visible_sprites, self.attack_sprites]) - + self.magic_player.flame( + self.player, cost, [self.visible_sprites, self.attack_sprites]) + def player_attack_logic(self): if self.attack_sprites: for attack_sprite in self.attack_sprites: - collision_sprites = pygame.sprite.spritecollide(attack_sprite,self.attackable_sprites,False) + collision_sprites = pygame.sprite.spritecollide( + attack_sprite, self.attackable_sprites, False) if collision_sprites: for target_sprite in collision_sprites: if target_sprite.sprite_type == 'grass': pos = target_sprite.rect.center - offset = pygame.math.Vector2(0,75) - for leaf in range(randint(3,6)): - self.animation_player.create_grass_particles(position = pos - offset, groups = [self.visible_sprites]) + offset = pygame.math.Vector2(0, 75) + for leaf in range(randint(3, 6)): + self.animation_player.create_grass_particles( + position=pos - offset, groups=[self.visible_sprites]) target_sprite.kill() else: - target_sprite.get_damage(self.player,attack_sprite.sprite_type) - - + target_sprite.get_damage( + self.player, attack_sprite.sprite_type) + def get_state(self): state = [] - - enemy_sprites = [sprite for sprite in self.visible_sprites if hasattr(sprite, 'sprite_type') and sprite.sprite_type == 'enemy'] + + enemy_sprites = [sprite for sprite in self.visible_sprites if hasattr( + sprite, 'sprite_type') and sprite.sprite_type == 'enemy'] for enemy in enemy_sprites: - distance, direction = enemy.get_player_distance_direction(self.player) - state.append([(distance, direction.x, direction.y, enemy.monster_name, enemy.health, enemy.exp, enemy.speed, enemy.attack_damage, enemy.resistance, enemy.attack_radius, enemy.notice_radius, enemy.attack_type)]) - + distance, direction = enemy.get_player_distance_direction( + self.player) + state.append([(distance, direction.x, direction.y, enemy.monster_name, enemy.health, enemy.exp, enemy.speed, + enemy.attack_damage, enemy.resistance, enemy.attack_radius, enemy.notice_radius, enemy.attack_type)]) + # Sort by distance state = sorted(state, key=lambda x: x[0]) @@ -147,13 +158,14 @@ class Level: # If there are fewer than 5 enemies, pad the state with placeholder values while len(state) < 5: - state.append((float('inf'), 0, 0, None, None, None, None, None, None, None, None, None)) + state.append((float('inf'), 0, 0, None, None, None, + None, None, None, None, None, None)) # Flatten the state to be a single list of numbers and strings state = [item for sublist in state for item in sublist] return state - + def damage_player(self, amount, attack_type): if self.player.vulnerable: self.player.health -= amount @@ -161,17 +173,19 @@ class Level: self.player.health = 0 self.player.vulnerable = False self.player.hurt_time = pygame.time.get_ticks() - self.animation_player.generate_particles(attack_type, self.player.rect.center, [self.visible_sprites]) - + self.animation_player.generate_particles( + attack_type, self.player.rect.center, [self.visible_sprites]) + def trigger_death_particles(self, position, particle_type): - self.animation_player.generate_particles(particle_type, position, [self.visible_sprites]) - + self.animation_player.generate_particles( + particle_type, position, [self.visible_sprites]) + def add_exp(self, amount): self.player.exp += amount - + def toggle_menu(self): self.game_paused = not self.game_paused - + def run(self): # Draw the game self.visible_sprites.custom_draw(self.camera) @@ -186,42 +200,6 @@ class Level: self.visible_sprites.update() self.visible_sprites.enemy_update(self.player) self.player_attack_logic() - + if self.player.health <= 0: self.__init__() - - - -class YSortCameraGroup(pygame.sprite.Group): - - def __init__(self): - super().__init__() - - # General Setup - self.display_surface = pygame.display.get_surface() - self.half_width = self.display_surface.get_size()[0] // 2 - self.half_height = self.display_surface.get_size()[1] // 2 - self.offset = pygame.math.Vector2(100, 200) - - # Creating the floor - self.floor_surf = pygame.image.load('../Graphics/graphics/tilemap/ground.png').convert() - self.floor_rect = self.floor_surf.get_rect(topleft = (0,0)) - - def custom_draw(self, player): - self.sprite_type = player.sprite_type - #Getting the offset - self.offset.x = player.rect.centerx - self.half_width - self.offset.y = player.rect.centery - self.half_height - - # Drawing the floor - floor_offset_pos = self.floor_rect.topleft - self.offset - self.display_surface.blit(self.floor_surf, floor_offset_pos) - - for sprite in sorted(self.sprites(), key = lambda sprite: sprite.rect.centery): - offset_pos = sprite.rect.topleft - self.offset - self.display_surface.blit(sprite.image, offset_pos) - - def enemy_update(self, player): - enemy_sprites = [sprite for sprite in self.sprites() if hasattr(sprite, 'sprite_type') and sprite.sprite_type == 'enemy'] - for enemy in enemy_sprites: - enemy.enemy_update(player) diff --git a/old/Game/objects/player.py b/old/Game/objects/player.py index 29dd1e7..b7eec3f 100644 --- a/old/Game/objects/player.py +++ b/old/Game/objects/player.py @@ -10,16 +10,18 @@ from objects.entity import Entity from rl.agent import Agent from rl.rl_settings import * + class Player(Entity): def __init__(self, position, groups, obstacle_sprites, create_attack_sprite, delete_attack_sprite, create_magic_sprite, is_AI, state): super().__init__(groups, is_AI, state) - - self.image = pygame.image.load('../Graphics/graphics/player/down/down_0.png').convert_alpha() - self.rect = self.image.get_rect(topleft = position) + + self.image = pygame.image.load( + '../Graphics/graphics/player/down/down_0.png').convert_alpha() + self.rect = self.image.get_rect(topleft=position) self.hitbox = self.rect.inflate(HITBOX_OFFSET['player']) self.sprite_type = 'player' - + # Graphics Setup self.import_player_assets() self.status = 'down' @@ -32,86 +34,88 @@ class Player(Entity): # Weapons self.create_attack_sprite = create_attack_sprite self.delete_attack_sprite = delete_attack_sprite - + # Magic self.create_magic_sprite = create_magic_sprite - + # Weapon rotation self.weapon_index = 0 self.weapon = list(weapon_data.keys())[self.weapon_index] self.can_rotate_weapon = True self.weapon_rotation_time = None self.rotate_attack_cooldown = 600 - + # Magic rotation self.magic_index = 0 self.magic = list(magic_data.keys())[self.magic_index] self.can_swap_magic = True self.magic_swap_time = None - + # Stats self.stats = { - 'health': 100, - 'energy': 60, - 'attack': 10, - 'magic': 4, - 'speed': 5 - } + 'health': 100, + 'energy': 60, + 'attack': 10, + 'magic': 4, + 'speed': 5 + } self.max_stats = { - 'health': 300, - 'energy': 150, - 'attack': 20, - 'magic': 10, - 'speed': 10 + 'health': 300, + 'energy': 150, + 'attack': 20, + 'magic': 10, + 'speed': 10 } self.upgrade_costs = { - 'health': 100, - 'energy': 100, - 'attack': 100, - 'magic': 100, - 'speed': 100 + 'health': 100, + 'energy': 100, + 'attack': 100, + 'magic': 100, + 'speed': 100 } - + # AI setup self.is_AI = is_AI if self.is_AI: - self.agent = Agent(self.possible_actions, input_dims = (list(self.stats.values())+ self.distance_direction_to_player), batch_size = batch_size, alpha = alpha, n_epochs = n_epochs) - + self.agent = Agent(self.possible_actions, input_dims=(list(self.stats.values( + )) + self.distance_direction_to_player), batch_size=batch_size, alpha=alpha, n_epochs=n_epochs) + self.health = self.stats['health'] self.energy = self.stats['energy'] self.exp = 0 self.speed = self.stats['speed'] - + # Damage timer self.vulnerable = True self.hurt_time = None self.invulnerability_duration = 300 self.obstacle_sprites = obstacle_sprites - + # Import Sounds - self.weapon_attack_sound = pygame.mixer.Sound('../Graphics/audio/sword.wav') + self.weapon_attack_sound = pygame.mixer.Sound( + '../Graphics/audio/sword.wav') self.weapon_attack_sound.set_volume(0.2) def import_player_assets(self): character_path = '../Graphics/graphics/player' - + self.animations = { - 'up': [], 'down': [], 'left': [], 'right': [], - 'up_idle': [], 'down_idle': [], 'left_idle': [], 'right_idle': [], - 'up_attack': [], 'down_attack': [], 'left_attack': [], 'right_attack': [] + 'up': [], 'down': [], 'left': [], 'right': [], + 'up_idle': [], 'down_idle': [], 'left_idle': [], 'right_idle': [], + 'up_attack': [], 'down_attack': [], 'left_attack': [], 'right_attack': [] } for animation in self.animations.keys(): full_path = f"{character_path}/{animation}" self.animations[animation] = import_folder(full_path) - + def get_status(self): - + # Idle Status if self.direction.x == 0 and self.direction.y == 0: if not 'idle' in self.status and not 'attack' in self.status: self.status += '_idle' - + if self.attacking: self.direction.x = 0 self.direction.y = 0 @@ -123,44 +127,44 @@ class Player(Entity): else: if 'attack' in self.status: self.status = self.status.replace('_attack', '') - + def get_full_weapon_damage(self): base_damage = self.stats['attack'] weapon_damage = weapon_data[self.weapon]['damage'] - + return (base_damage + weapon_damage) - + def get_full_magic_damage(self): base_damage = self.stats['magic'] spell_damage = magic_data[self.magic]['strength'] return (base_damage + spell_damage) - + def get_value_by_index(self, index): return list(self.stats.values())[index] - + def get_cost_by_index(self, index): return list(self.upgrade_costs.values())[index] - + def cooldowns(self): current_time = pygame.time.get_ticks() - + if self.attacking: if current_time - self.attack_time > self.attack_cooldown + weapon_data[self.weapon]['cooldown']: self.attacking = False self.delete_attack_sprite() - + if not self.can_rotate_weapon: if current_time - self.weapon_rotation_time > self.rotate_attack_cooldown: self.can_rotate_weapon = True - + if not self.can_swap_magic: if current_time - self.magic_swap_time > self.rotate_attack_cooldown: self.can_swap_magic = True - + if not self.vulnerable: if current_time - self.hurt_time >= self.invulnerability_duration: self.vulnerable = True - + if not self.can_move: if current_time - self.move_time >= self.move_cooldown: self.can_move = True @@ -170,24 +174,22 @@ class Player(Entity): self.energy += 0.01 * self.stats['magic'] else: self.energy = self.stats['energy'] - - + def animate(self): animation = self.animations[self.status] self.frame_index += self.animation_speed if self.frame_index >= len(animation): self.frame_index = 0 - + # Set the image self.image = animation[int(self.frame_index)] - self.rect = self.image.get_rect(center = self.hitbox.center) - + self.rect = self.image.get_rect(center=self.hitbox.center) + if not self.vulnerable: alpha = self.wave_value() self.image.set_alpha(alpha) else: self.image.set_alpha(255) - def update(self): self.input() @@ -197,5 +199,5 @@ class Player(Entity): self.move(self.stats['speed']) self.energy_recovery() self.distance_direction_to_player = self.state() - #if self.is_AI: - # self.agent.act(self.distance_direction_to_player) + # if self.is_AI: + # self.agent.act(self.distance_direction_to_player)