2023-11-29 10:53:30 +00:00
|
|
|
import os
|
2023-09-27 18:03:37 +00:00
|
|
|
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
|
2023-09-27 18:03:37 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
2023-09-27 18:03:37 +00:00
|
|
|
|
|
|
|
class Player(pygame.sprite.Sprite):
|
2023-11-23 11:44:23 +00:00
|
|
|
def __init__(self,
|
2023-11-29 10:53:30 +00:00
|
|
|
player_id,
|
|
|
|
role,
|
2023-11-23 11:44:23 +00:00
|
|
|
position,
|
|
|
|
groups,
|
|
|
|
obstacle_sprites,
|
|
|
|
visible_sprites,
|
|
|
|
attack_sprites,
|
2023-11-29 10:53:30 +00:00
|
|
|
attackable_sprites
|
|
|
|
):
|
2023-09-27 18:03:37 +00:00
|
|
|
super().__init__(groups)
|
|
|
|
|
2023-11-29 10:53:30 +00:00
|
|
|
self.initial_position = position
|
2023-11-14 21:44:43 +00:00
|
|
|
self.player_id = player_id
|
2023-11-29 10:53:30 +00:00
|
|
|
self.distance_direction_from_enemy = None
|
|
|
|
|
|
|
|
# Sprite Setup
|
|
|
|
self.sprite_type = "player"
|
|
|
|
self.obstacle_sprites = obstacle_sprites
|
2023-09-27 18:03:37 +00:00
|
|
|
self.visible_sprites = visible_sprites
|
|
|
|
self.attack_sprites = attack_sprites
|
2023-10-04 02:37:28 +00:00
|
|
|
self.attackable_sprites = attackable_sprites
|
2023-09-27 18:03:37 +00:00
|
|
|
|
2023-11-29 10:53:30 +00:00
|
|
|
# Graphics Setup
|
2023-10-04 02:37:28 +00:00
|
|
|
self.animation_player = AnimationPlayer()
|
|
|
|
self.animation = AnimationHandler(self.sprite_type)
|
|
|
|
self.animation.import_assets(position)
|
2023-11-29 10:53:30 +00:00
|
|
|
# Input Setup
|
2023-10-04 02:37:28 +00:00
|
|
|
self._input = InputHandler(
|
2023-11-29 10:53:30 +00:00
|
|
|
self.sprite_type, self.animation_player)
|
2023-10-04 02:37:28 +00:00
|
|
|
|
2023-09-27 18:03:37 +00:00
|
|
|
# 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
|
|
|
|
2023-11-29 10:53:30 +00:00
|
|
|
def setup_agent(self,
|
|
|
|
gamma,
|
|
|
|
alpha,
|
|
|
|
policy_clip,
|
|
|
|
batch_size,
|
|
|
|
N,
|
|
|
|
n_epochs,
|
|
|
|
gae_lambda,
|
|
|
|
chkpt_dir,
|
|
|
|
no_load=False):
|
2023-09-27 18:03:37 +00:00
|
|
|
|
2023-12-04 04:08:41 +00:00
|
|
|
self.max_num_enemies = len(self.distance_direction_from_enemy)
|
2023-11-29 10:53:30 +00:00
|
|
|
self.get_current_state()
|
2023-12-02 17:58:24 +00:00
|
|
|
self.num_features = len(self.state_features)
|
|
|
|
|
2023-11-29 10:53:30 +00:00
|
|
|
self.agent = Agent(
|
2023-12-02 17:58:24 +00:00
|
|
|
input_dims=self.num_features,
|
2023-11-29 10:53:30 +00:00
|
|
|
n_actions=len(self._input.possible_actions),
|
|
|
|
gamma=gamma,
|
|
|
|
alpha=alpha,
|
|
|
|
policy_clip=policy_clip,
|
|
|
|
batch_size=batch_size,
|
|
|
|
N=N,
|
|
|
|
n_epochs=n_epochs,
|
|
|
|
gae_lambda=gae_lambda,
|
|
|
|
chkpt_dir=chkpt_dir
|
|
|
|
)
|
|
|
|
print(
|
|
|
|
f"\nAgent initialized on player {self.player_id} using {self.agent.actor.device}.")
|
|
|
|
|
|
|
|
if not no_load:
|
|
|
|
print("Attempting to load models ...")
|
|
|
|
try:
|
|
|
|
self.agent.load_models(
|
|
|
|
actr_chkpt=f"A{self.player_id}",
|
|
|
|
crtc_chkpt=f"C{self.player_id}"
|
|
|
|
)
|
|
|
|
print("Models loaded ...\n")
|
|
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
print(
|
|
|
|
f"FileNotFound for player {self.player_id}.\
|
|
|
|
\nSkipping loading ...\n")
|
2023-11-19 03:27:47 +00:00
|
|
|
|
2023-09-27 18:03:37 +00:00
|
|
|
def get_status(self):
|
2023-11-29 10:53:30 +00:00
|
|
|
if self._input.movement.direction.x == 0\
|
|
|
|
and self._input.movement.direction.y == 0:
|
|
|
|
|
|
|
|
if 'idle' not in self._input.status and 'attack' not in self._input.status:
|
|
|
|
self._input.status += '_idle'
|
2023-09-27 18:03:37 +00:00
|
|
|
|
|
|
|
if self._input.attacking:
|
|
|
|
self._input.movement.direction.x = 0
|
|
|
|
self._input.movement.direction.y = 0
|
2023-11-29 10:53:30 +00:00
|
|
|
if 'attack' not in self._input.status:
|
|
|
|
if 'idle' in self._input.status:
|
|
|
|
self._input.status = self._input.status.replace(
|
|
|
|
'idle', 'attack')
|
2023-09-27 18:03:37 +00:00
|
|
|
else:
|
2023-11-29 10:53:30 +00:00
|
|
|
self._input.status += '_attack'
|
2023-09-27 18:03:37 +00:00
|
|
|
else:
|
2023-11-29 10:53:30 +00:00
|
|
|
if 'attack' in self._input.status:
|
|
|
|
self._input.status = self._input.status.replace('_attack', '')
|
2023-09-27 18:03:37 +00:00
|
|
|
|
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(
|
2023-11-23 11:44:23 +00:00
|
|
|
position=pos - offset,
|
|
|
|
groups=[self.visible_sprites])
|
|
|
|
|
2023-10-04 02:37:28 +00:00
|
|
|
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
|
|
|
|
2023-11-22 00:08:09 +00:00
|
|
|
self.reward_features = [
|
2023-11-22 19:49:09 +00:00
|
|
|
self.stats.exp,
|
2023-12-04 04:08:41 +00:00
|
|
|
|
2023-11-23 15:37:02 +00:00
|
|
|
2*np.exp(-nearest_dist**2),
|
2023-12-04 04:08:41 +00:00
|
|
|
|
|
|
|
1/(np.exp((nearest_enemy.stats.health -
|
|
|
|
nearest_enemy.stats.monster_info['health'])/nearest_enemy.stats.monster_info['health'])) - 1,
|
|
|
|
|
|
|
|
1/(np.exp((len(self.distance_direction_from_enemy) -
|
|
|
|
self.max_num_enemies)/self.max_num_enemies)) - 1,
|
|
|
|
|
|
|
|
1 - 1/(np.exp((self.stats.health -
|
|
|
|
self.stats.stats['health'])/self.stats.stats['health']))
|
2023-11-29 10:53:30 +00:00
|
|
|
if not self.is_dead() > 0 else -1
|
2023-11-22 00:08:09 +00:00
|
|
|
]
|
2023-11-19 03:27:47 +00:00
|
|
|
|
2023-11-17 02:19:03 +00:00
|
|
|
self.state_features = [
|
2023-11-29 10:53:30 +00:00
|
|
|
np.exp(-self.animation.rect.center[0]),
|
|
|
|
np.exp(-self.animation.rect.center[1]),
|
2023-11-21 21:35:24 +00:00
|
|
|
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-30 17:28:17 +00:00
|
|
|
for distance, direction, enemy in self.distance_direction_from_enemy:
|
2023-11-17 02:19:03 +00:00
|
|
|
enemy_states.extend([
|
2023-11-22 19:49:09 +00:00
|
|
|
np.exp(-distance),
|
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'],
|
2023-11-22 19:49:09 +00:00
|
|
|
np.exp(-enemy.stats.exp**2),
|
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)
|
|
|
|
|
2023-11-14 21:44:43 +00:00
|
|
|
def is_dead(self):
|
2023-11-23 11:44:23 +00:00
|
|
|
if self.stats.health <= 0:
|
2023-11-29 10:53:30 +00:00
|
|
|
self.stats.health = 0
|
|
|
|
self.animation.import_assets((3264, 448))
|
2023-11-14 21:44:43 +00:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2023-11-29 11:10:04 +00:00
|
|
|
def agent_update(self):
|
|
|
|
|
|
|
|
# Get the current state
|
|
|
|
self.get_current_state()
|
|
|
|
|
|
|
|
# Choose action based on current state
|
|
|
|
action, probs, value\
|
|
|
|
= self.agent.choose_action(self.state_features)
|
|
|
|
|
|
|
|
# Apply chosen action
|
|
|
|
self._input.check_input(action,
|
|
|
|
self.stats.speed,
|
|
|
|
self.animation.hitbox,
|
|
|
|
self.obstacle_sprites,
|
|
|
|
self.animation.rect,
|
|
|
|
self)
|
|
|
|
|
|
|
|
self.agent.remember(self.state_features, action,
|
|
|
|
probs, value, self.stats.exp, self.is_dead())
|
|
|
|
|
|
|
|
self.get_current_state()
|
|
|
|
|
2023-12-06 12:03:59 +00:00
|
|
|
print(self.reward_features)
|
|
|
|
|
2023-09-27 18:03:37 +00:00
|
|
|
def update(self):
|
2023-11-17 02:19:03 +00:00
|
|
|
|
2023-11-29 10:53:30 +00:00
|
|
|
if not self.is_dead():
|
2023-11-29 11:10:04 +00:00
|
|
|
|
|
|
|
self.agent_update()
|
2023-11-14 21:44:43 +00:00
|
|
|
|
2023-11-29 10:53:30 +00:00
|
|
|
# Cooldowns and Regen
|
|
|
|
self.stats.health_recovery()
|
|
|
|
self.stats.energy_recovery()
|
2023-09-27 18:03:37 +00:00
|
|
|
|
2023-11-29 10:53:30 +00:00
|
|
|
else:
|
2023-12-04 04:08:41 +00:00
|
|
|
self.stats.exp = max(-1, self.stats.exp - .1)
|
2023-11-29 10:53:30 +00:00
|
|
|
|
|
|
|
# Refresh player based on input and animate
|
2023-09-27 18:03:37 +00:00
|
|
|
self.get_status()
|
2023-11-29 10:53:30 +00:00
|
|
|
self.animation.animate(
|
|
|
|
self._input.status, self._input.combat.vulnerable)
|
2023-09-27 18:03:37 +00:00
|
|
|
self._input.cooldowns(self._input.combat.vulnerable)
|