pneuma-pygame/entities/player.py

232 lines
7.3 KiB
Python

import pygame
import numpy as np
from random import randint
from config.game.weapon_config import weapon_data
from config.game.spell_config import magic_data
from .entity import Entity
from effects.particle_effects import AnimationPlayer
from ml.ppo.agent import Agent
class Player(Entity):
def __init__(self,
player_id,
role,
position,
map_edge,
groups,
obstacle_sprites,
visible_sprites,
attack_sprites,
attackable_sprites
):
super().__init__(groups=groups,
obstacle_sprites=obstacle_sprites,
visible_sprites=visible_sprites,
attack_sprites=attack_sprites, attackable_sprites=attackable_sprites)
# Setup stats
self.sprite_type = 'player'
self.get_stats(self.sprite_type, role=role)
# Graphics Setup
self.animation_player = AnimationPlayer()
self.import_assets(position)
# Set misc
self.initial_position = position
self.map_edge = map_edge
self.player_id = player_id
self.distance_direction_from_enemy = None
def setup_agent(self,
gamma,
alpha,
policy_clip,
batch_size,
n_epochs,
gae_lambda,
chkpt_dir,
entropy_coef,
load=None):
self.max_num_enemies = len(self.distance_direction_from_enemy)
self.get_current_state()
self.num_features = len(self.state_features)
self.agent = Agent(
input_dims=self.num_features,
n_actions=len(self.possible_actions),
gamma=gamma,
alpha=alpha,
policy_clip=policy_clip,
batch_size=batch_size,
n_epochs=n_epochs,
gae_lambda=gae_lambda,
entropy_coef=entropy_coef,
chkpt_dir=chkpt_dir
)
print(
f"\nAgent initialized on player {self.player_id} using {self.agent.actor.device}.")
if load:
print("Attempting to load models ...")
try:
self.agent.load_models(
actr_chkpt=f"{chkpt_dir}/../run{load}/A{self.player_id}",
crtc_chkpt=f"{chkpt_dir}/../run{load}/C{self.player_id}"
)
print("Models loaded ...\n")
except FileNotFoundError:
print(
f"FileNotFound for player {self.player_id}.\
\nSkipping loading ...\n")
def get_status(self):
if self.direction.x == 0\
and self.direction.y == 0:
if 'idle' not in self.status and 'attack' not in self.status:
self.status += '_idle'
if self.attacking:
self.direction.x = 0
self.direction.y = 0
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', '')
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.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_current_state(self):
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_en_dir, nearest_enemy = sorted_distances[0]
self.action_features = [self.action]
if hasattr(self, 'state_features'):
self.old_state_features = self.state_features
self.reward = self.exp\
+ self.health/self.stats['health']
# - nearest_dist/np.sqrt(np.sum(self.map_edge))
self.state_features = [
self.rect.center[0]/self.map_edge[0],
self.rect.center[1]/self.map_edge[1],
self.direction.x,
self.direction.y,
self.health/self.stats['health'],
self.energy/self.stats['energy'],
]
for distance, direction, enemy in sorted_distances[:5]:
self.state_features.extend([
distance/np.sqrt(np.sum(self.map_edge)),
direction[0],
direction[1],
enemy.health /
enemy.monster_info['health'],
enemy.exp,
])
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)
def is_dead(self):
if self.health <= 0:
self.health = 0
self.import_assets((3264, 448))
return True
else:
return False
def agent_update(self):
# Get the current state
self.get_current_state()
# Choose action based on current state
self.action, probs, value\
= self.agent.choose_action(self.state_features)
# Apply chosen action
self.check_input(self.action,
self.speed,
self.hitbox,
self.obstacle_sprites,
self.rect
)
self.agent.remember(self.state_features, self.action,
probs, value, self.reward, self.is_dead())
self.get_current_state()
def update(self):
self.agent_update()
# Cooldowns and Regen
self.health_recovery()
self.energy_recovery()
# Refresh player based on input and animate
self.get_status()
self.animate(
self.status, self.vulnerable)
self.cooldowns(self.vulnerable)