Transfered RL part

This commit is contained in:
Vasilis Valatsos 2023-06-14 14:15:05 +02:00
parent 0789d461a5
commit a1c5500da2
324 changed files with 1927 additions and 0 deletions

3
.gitignore vendored
View file

@ -158,3 +158,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
# Mac bs
.DS_store

98
Game/UI/ui.py Normal file
View file

@ -0,0 +1,98 @@
import pygame
from utils.settings import *
class UI:
def __init__(self):
# 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)
# 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)
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))
self.display_surface.blit(text_surf, text_rect)
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))
self.display_surface.blit(text_surf, text_rect)
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)
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)
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)
magic_surf = self.magic_graphics[magic_index]
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)
if player.sprite_type == 'camera':
self.show_exp(player.exp)

143
Game/UI/upgrade.py Normal file
View file

@ -0,0 +1,143 @@
import pygame
from utils.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.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.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
if self.selection_index == self.attribute_num:
self.selection_index = 0
self.can_move = False
self.selection_time = pygame.time.get_ticks()
elif keys[pygame.K_a]:
self.selection_index -= 1
if self.selection_index == -1:
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)
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)
class Box:
def __init__(self, l, t, w, h, index, font):
self.rect = pygame.Rect(l, t, w, h)
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))
# 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))
# 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)
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)
# 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]
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)
pygame.draw.rect(surface, UI_BORDER_COLOR, self.rect, 4)
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)

0
Game/__init__.py Normal file
View file

0
Game/effects/__init__.py Normal file
View file

49
Game/effects/magic.py Normal file
View file

@ -0,0 +1,49 @@
import pygame
from utils.settings import *
from random import randint
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')
}
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)
def flame(self, player, cost, groups):
if player.energy >= cost:
player.energy -= cost
self.sounds['flame'].play()
if player.status.split('_')[0] == 'right':
direction = pygame.math.Vector2(1,0)
elif player.status.split('_')[0] == 'left':
direction = pygame.math.Vector2(-1,0)
elif player.status.split('_')[0] == 'up':
direction = pygame.math.Vector2(0,-1)
else:
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)
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)

76
Game/effects/particles.py Normal file
View file

@ -0,0 +1,76 @@
import pygame
from utils.support import import_folder
from random import choice
class AnimationPlayer:
def __init__(self):
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'),
# 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'),
# 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'),
# 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'))
)
}
def reflect_images(self, frames):
new_frames = []
for frame in frames:
flipped_frame = pygame.transform.flip(frame, True, False)
new_frames.append(flipped_frame)
return new_frames
def create_grass_particles(self, position, groups):
animation_frames = choice(self.frames['leaf'])
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)
self.frame_index = 0
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.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()

23
Game/effects/weapon.py Normal file
View file

@ -0,0 +1,23 @@
import pygame
class Weapon(pygame.sprite.Sprite):
def __init__(self, player, groups):
super().__init__(groups)
self.sprite_type = 'weapon'
direction = player.status.split('_')[0]
# Graphic
full_path = f"../Graphics/graphics/weapons/{player.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))
elif direction == 'left':
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))
else:
self.rect = self.image.get_rect(midbottom = player.rect.midtop + pygame.math.Vector2(-10, 0))

42
Game/main.py Normal file
View file

@ -0,0 +1,42 @@
import pygame
import sys
from utils.settings import *
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))
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)
def run(self):
while True:
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()
game.run()

0
Game/objects/__init__.py Normal file
View file

61
Game/objects/camera.py Normal file
View file

@ -0,0 +1,61 @@
import pygame
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.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)

160
Game/objects/enemy.py Normal file
View file

@ -0,0 +1,160 @@
import pygame
from utils.settings import *
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):
super().__init__(groups, is_AI)
# 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.hitbox = self.rect.inflate(0, -10)
# Stats
self.monster_name = monster_name
monster_info = monster_data[self.monster_name]
self.health = monster_info['health']
self.exp = monster_info['exp']
self.speed = monster_info['speed']
self.attack_damage = monster_info['damage']
self.resistance = monster_info['resistance']
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')
self.hit_sound = pygame.mixer.Sound('../Graphics/audio/hit.wav')
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
self.attack_cooldown = 400
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':[]}
main_path = f"../Graphics/graphics/monsters/{name}"
for animation in self.animations.keys():
self.animations[animation] = import_folder(f"{main_path}/{animation}")
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
self.status = 'attack'
elif distance <= self.notice_radius:
self.status = 'move'
else:
self.status = 'idle'
def actions(self, player):
if self.status == 'attack':
self.attack_sound.play()
self.attack_time = pygame.time.get_ticks()
self.damage_player(self.attack_damage, self.attack_type)
elif self.status == 'move':
self.direction = self.get_player_distance_direction(player)[1]
else:
self.direction = pygame.math.Vector2()
def animate(self):
animation = self.animations[self.status]
self.frame_index += self.animation_speed
if self.frame_index >= len(animation):
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)
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()
if not self.can_attack:
if current_time - self.attack_time >= self.attack_cooldown:
self.can_attack = True
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()
self.direction = self.get_player_distance_direction(player)[1]
if attack_type == 'weapon':
self.health -= player.get_full_weapon_damage()
else:
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)

144
Game/objects/entity.py Normal file
View file

@ -0,0 +1,144 @@
import pygame
from math import sin
import random
from utils.settings import *
class Entity(pygame.sprite.Sprite):
def __init__(self, groups, is_AI):
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 = {
0: ('up', -1, 0),
1: ('down', 1, 0),
2: ('left', 0, -1),
3: ('right', 0, 1),
4: ('attack', None, None),
5: ('magic', None, None),
6: ('rotate_weapon', None, None),
7: ('swap_magic', None, None)
}
self.distance_direction_to_player = [(float('inf'), 0, 0, None, None, None, None, None, None, None, None, None)]*5
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
self.collision('vertical')
self.rect.center = self.hitbox.center
def collision(self, direction):
if direction == 'horizontal':
for sprite in self.obstacle_sprites:
# The following works for static obstacles only
if sprite.hitbox.colliderect(self.hitbox):
# Moving Right
if self.direction.x > 0:
self.hitbox.right = sprite.hitbox.left
# 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
if sprite.hitbox.colliderect(self.hitbox):
# Moving Down
if self.direction.y > 0:
self.hitbox.bottom = sprite.hitbox.top
# Moving Up
if self.direction.y < 0:
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]:
self.direction.y = -1
self.status = 'up'
self.can_move = False
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]:
self.direction.x = -1
self.status = 'left'
self.can_move = False
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]:
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]:
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']
cost = list(magic_data.values())[self.magic_index]['cost']
self.create_magic_sprite(style, strength, cost)
# Rotating Weapons
if keys[pygame.K_LSHIFT] and self.can_rotate_weapon:
self.can_rotate_weapon = False
self.weapon_rotation_time = pygame.time.get_ticks()
if self.weapon_index < len(list(weapon_data.keys())) - 1:
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
self.magic_swap_time = pygame.time.get_ticks()
if self.magic_index < len(list(magic_data.keys())) - 1:
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:
return 255
else:
return 0

226
Game/objects/level.py Normal file
View file

@ -0,0 +1,226 @@
import pygame
from random import choice, randint
from utils.settings import *
from utils.debug import debug
from utils.support import *
from UI.ui import UI
from UI.upgrade import Upgrade
from effects.particles import AnimationPlayer
from effects.magic import MagicPlayer
from effects.weapon import Weapon
from objects.tile import Tile
from objects.player import Player
from objects.enemy import Enemy
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')
}
graphics = {
'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):
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)
# 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)
elif col =='395':
self.camera = Camera((x,y), [self.visible_sprites])
else:
if col == '390':
monster_name = 'bamboo'
elif col == '391':
monster_name = 'spirit'
elif col == '392':
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)
def create_attack_sprite(self):
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])
if style == 'flame':
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)
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])
target_sprite.kill()
else:
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']
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)])
# Sort by distance
state = sorted(state, key=lambda x: x[0])
# Consider only the closest 5 enemies
state = state[:5]
# 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))
# 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
if self.player.health < 0:
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])
def trigger_death_particles(self, position, particle_type):
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)
self.ui.display(self.camera)
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__()
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)

195
Game/objects/player.py Normal file
View file

@ -0,0 +1,195 @@
import pygame
import random
from utils.settings import *
from utils.support import import_folder
from objects.entity import Entity
from rl.agent import Agent
class Player(Entity):
def __init__(self, position, groups, obstacle_sprites, create_attack_sprite, delete_attack_sprite, create_magic_sprite, is_AI):
super().__init__(groups, is_AI)
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'
# Combat
self.attacking = False
self.attack_cooldown = 400
self.attack_time = None
# 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
}
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.exp = 0
self.speed = self.stats['speed']
# Damage timer
self.vulnerable = True
self.hurt_time = None
self.invulnerability_duration = 300
if is_AI:
pass
# self.agent = Agent(self.possible_actions, self.distance_direction_to_player, self.stats, self.exp)
self.obstacle_sprites = obstacle_sprites
# Import Sounds
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': []
}
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
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 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
def energy_recovery(self):
if self.energy < self.stats['energy']:
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)
if not self.vulnerable:
alpha = self.wave_value()
self.image.set_alpha(alpha)
else:
self.image.set_alpha(255)
def update(self):
self.input()
self.cooldowns()
self.get_status()
self.animate()
self.move(self.stats['speed'])
self.energy_recovery()

17
Game/objects/tile.py Normal file
View file

@ -0,0 +1,17 @@
import pygame
from utils.settings 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])

0
Game/rl/__init__.py Normal file
View file

16
Game/rl/agent.py Normal file
View file

@ -0,0 +1,16 @@
import random
from rl.brain import PPONet
class Agent:
def __init__(self, actions, inputs, player_info, reward):
self.input_dim = len(inputs) + len(player_info)
self.output_dim = len(actions)
self.reward = reward
self.net = PPONet(input_dim, output_dim)

4
Game/rl/brain.py Normal file
View file

@ -0,0 +1,4 @@
import torch
class PPONet:
pass

0
Game/utils/__init__.py Normal file
View file

12
Game/utils/debug.py Normal file
View file

@ -0,0 +1,12 @@
import pygame
pygame.init()
font = pygame.font.Font(None,30)
def debug(info, y =10, x = 10):
display_surface = pygame.display.get_surface()
debug_surf = font.render(str(info), True, 'White')
debug_rect = debug_surf.get_rect(topleft = (x,y))
pygame.draw.rect(display_surface, 'Black', debug_rect)
display_surface.blit(debug_surf, debug_rect)

57
Game/utils/settings.py Normal file
View file

@ -0,0 +1,57 @@
# game setup
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
# general colors
WATER_COLOR = '#71ddee'
UI_BG_COLOR = '#222222'
UI_BORDER_COLOR = '#111111'
TEXT_COLOR = '#EEEEEE'
# ui colors
HEALTH_COLOR = 'red'
ENERGY_COLOR = 'blue'
UI_BORDER_COLOR_ACTIVE = 'gold'
# Upgrade menu
TEXT_COLOR_SELECTED = '#111111'
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}}

21
Game/utils/support.py Normal file
View file

@ -0,0 +1,21 @@
import pygame
from csv import reader
from os import walk
def import_csv_layout(path):
terrain_map = []
with open(path) as level_map:
layout = reader(level_map, delimiter = ',')
for row in layout:
terrain_map.append(list(row))
return terrain_map
def import_folder(path):
surface_list = []
for _, __, img_files in walk(path):
for image in img_files:
full_path = f"{path}/{image}"
image_surf = pygame.image.load(full_path).convert_alpha()
surface_list.append(image_surf)
return surface_list

BIN
Graphics/Vasilis/Pot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Graphics/audio/death.wav Normal file

Binary file not shown.

BIN
Graphics/audio/flame.wav Normal file

Binary file not shown.

BIN
Graphics/audio/heal.wav Normal file

Binary file not shown.

BIN
Graphics/audio/hit.wav Normal file

Binary file not shown.

BIN
Graphics/audio/main.ogg Normal file

Binary file not shown.

BIN
Graphics/audio/sword.wav Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show more