pneuma-pygame/entities/player.py

216 lines
7.4 KiB
Python
Raw Normal View History

import pygame
2023-11-17 02:19:03 +00:00
import numpy as np
2023-10-04 02:37:28 +00:00
from random import randint
from configs.game.weapon_config import weapon_data
from configs.game.spell_config import magic_data
from .components.stats import StatsHandler
from .components._input import InputHandler
from .components.animaton import AnimationHandler
2023-10-04 02:37:28 +00:00
from effects.particle_effects import AnimationPlayer
2023-11-17 02:19:03 +00:00
from agents.ppo.agent import Agent
2023-11-14 21:44:43 +00:00
class Player(pygame.sprite.Sprite):
2023-11-17 02:19:03 +00:00
def __init__(self, position, groups, obstacle_sprites, visible_sprites, attack_sprites, attackable_sprites, role, player_id):
super().__init__(groups)
# Setup Sprites
self.sprite_type = 'player'
2023-11-14 21:44:43 +00:00
self.status = 'down'
self.player_id = player_id
self.visible_sprites = visible_sprites
self.attack_sprites = attack_sprites
self.obstacle_sprites = obstacle_sprites
2023-10-04 02:37:28 +00:00
self.attackable_sprites = attackable_sprites
# Setup Graphics
2023-10-04 02:37:28 +00:00
self.animation_player = AnimationPlayer()
self.animation = AnimationHandler(self.sprite_type)
self.animation.import_assets(position)
self.image = self.animation.image
self.rect = self.animation.rect
2023-10-04 02:37:28 +00:00
# Setup Inputs
self._input = InputHandler(
self.sprite_type, self.animation_player) # , self.status)
# Setup Stats
2023-11-13 12:34:22 +00:00
self.role = role
self.stats = StatsHandler(self.sprite_type, self.role)
2023-10-04 02:37:28 +00:00
self.distance_direction_from_enemy = None
2023-11-19 03:27:47 +00:00
# Setup AI
self.score = 0
self.learn_iters = 0
self.n_steps = 0
self.N = 20
def get_status(self):
if self._input.movement.direction.x == 0 and self._input.movement.direction.y == 0:
2023-10-04 02:37:28 +00:00
if 'idle' not in self.status and 'attack' not in self.status:
self.status += '_idle'
if self._input.attacking:
self._input.movement.direction.x = 0
self._input.movement.direction.y = 0
2023-10-04 02:37:28 +00:00
if 'attack' not 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', '')
2023-10-04 02:37:28 +00:00
def 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_damaged(
self, attack_sprite.sprite_type)
def get_full_weapon_damage(self):
base_damage = self.stats.attack
weapon_damage = weapon_data[self._input.combat.weapon]['damage']
return (base_damage + weapon_damage)
def get_full_magic_damage(self):
base_damage = self.stats.magic
spell_damage = magic_data[self._input.combat.magic]['strength']
return (base_damage + spell_damage)
2023-11-14 21:44:43 +00:00
def get_current_state(self):
2023-11-17 02:19:03 +00:00
2023-11-19 03:27:47 +00:00
if self.distance_direction_from_enemy != []:
sorted_distances = sorted(
self.distance_direction_from_enemy, key=lambda x: x[0])
else:
sorted_distances = np.zeros(self.num_features)
nearest_dist, _, nearest_enemy = sorted_distances[0]
2023-11-17 02:19:03 +00:00
self.action_features = [self._input.action]
2023-11-19 03:27:47 +00:00
self.reward_features = [self.stats.exp,
np.exp(-(nearest_dist)),
np.exp(-(nearest_enemy.stats.health)),
- np.exp(self.stats.health)
]
2023-11-17 02:19:03 +00:00
self.state_features = [
2023-11-21 17:09:30 +00:00
# TODO: Find a way to normalize
2023-11-21 21:35:24 +00:00
self.rect.center[0]/3616,
self.rect.center[1]/3168,
self._input.movement.direction.x,
self._input.movement.direction.y,
2023-11-21 17:09:30 +00:00
self.stats.health/self.stats.stats['health'],
self.stats.energy/self.stats.stats['energy']
2023-11-17 02:19:03 +00:00
]
enemy_states = []
2023-11-19 03:27:47 +00:00
for distance, direction, enemy in sorted_distances[:5]:
2023-11-17 02:19:03 +00:00
enemy_states.extend([
2023-11-21 21:35:24 +00:00
distance/sorted_distances[-1][0],
2023-11-17 02:19:03 +00:00
direction[0],
direction[1],
2023-11-21 17:09:30 +00:00
enemy.stats.health/enemy.stats.monster_info['health'],
enemy.stats.attack/enemy.stats.monster_info['attack'],
enemy.stats.exp/enemy.stats.monster_info['exp'],
2023-11-17 02:19:03 +00:00
])
2023-11-19 03:27:47 +00:00
2023-11-17 02:19:03 +00:00
self.state_features.extend(enemy_states)
2023-11-19 03:27:47 +00:00
if hasattr(self, 'num_features'):
while len(self.state_features) < self.num_features:
self.state_features.append(0)
self.state_features = np.array(self.state_features)
min_feat = np.min(self.state_features)
max_feat = np.max(self.state_features)
self.state_features = (self.state_features -
min_feat) / (max_feat-min_feat)
def get_max_num_states(self):
2023-11-17 02:19:03 +00:00
self.get_current_state()
2023-11-19 03:27:47 +00:00
self.num_features = len(self.state_features)
2023-11-17 02:19:03 +00:00
2023-11-19 03:27:47 +00:00
def setup_agent(self):
self.agent = Agent(
input_dims=len(self.state_features),
n_actions=len(self._input.possible_actions),
batch_size=5,
n_epochs=4)
try:
self.agent.load_models()
except FileNotFoundError as e:
print(f"{e}. Skipping loading...")
2023-11-14 21:44:43 +00:00
def is_dead(self):
if self.stats.health == 0:
2023-11-19 03:27:47 +00:00
self.stats.exp = -100
2023-11-14 21:44:43 +00:00
return True
else:
return False
def update(self):
2023-11-17 02:19:03 +00:00
# Get the current state
self.get_current_state()
2023-11-14 21:44:43 +00:00
# Choose action based on current state
2023-11-17 02:19:03 +00:00
action, probs, value = self.agent.choose_action(self.state_features)
2023-11-14 21:44:43 +00:00
2023-11-17 02:19:03 +00:00
self.n_steps += 1
2023-11-14 21:44:43 +00:00
# Apply chosen action
2023-11-19 03:27:47 +00:00
self._input.check_input(action,
self.stats.speed,
self.animation.hitbox,
self.obstacle_sprites,
self.animation.rect,
self)
2023-11-14 21:44:43 +00:00
2023-11-17 02:19:03 +00:00
self.done = self.is_dead()
2023-11-14 21:44:43 +00:00
2023-11-17 02:19:03 +00:00
self.score = self.stats.exp
self.agent.remember(self.state_features, action,
probs, value, self.stats.exp, self.done)
if self.n_steps % self.N == 0:
self.agent.learn()
self.learn_iters += 1
2023-11-14 21:44:43 +00:00
2023-11-17 02:19:03 +00:00
self.get_current_state()
2023-11-14 21:44:43 +00:00
2023-11-17 02:19:03 +00:00
if self.done:
2023-11-14 21:44:43 +00:00
self.agent.learn()
# Refresh objects based on input
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)