301 lines
11 KiB
Python
301 lines
11 KiB
Python
import os
|
|
import pygame
|
|
import numpy as np
|
|
|
|
from random import choice
|
|
|
|
from config.system.window import TILESIZE
|
|
|
|
from utils.debug import debug
|
|
from utils.resource_loader import import_csv_layout, import_folder
|
|
|
|
from interface.ui import UI
|
|
|
|
from entities.player import Player
|
|
from entities.enemy import Enemy
|
|
from entities.terrain import Terrain
|
|
|
|
from camera import Camera
|
|
|
|
|
|
class Level:
|
|
|
|
def __init__(self, n_players):
|
|
|
|
self.paused = False
|
|
self.done = False
|
|
|
|
# Get display surface
|
|
self.display_surface = pygame.display.get_surface()
|
|
|
|
# Setup Sprite groups
|
|
self.visible_sprites = Camera()
|
|
self.obstacle_sprites = pygame.sprite.Group()
|
|
self.attack_sprites = pygame.sprite.Group()
|
|
self.attackable_sprites = pygame.sprite.Group()
|
|
|
|
# Map generation
|
|
self.n_players = n_players
|
|
self.generate_map()
|
|
|
|
# Handle generated entities
|
|
self.get_entities()
|
|
self.get_distance_direction()
|
|
self.dead_players = np.zeros(self.n_players)
|
|
|
|
# Setup UI
|
|
self.ui = UI()
|
|
|
|
def generate_map(self):
|
|
|
|
self.possible_player_locations = []
|
|
|
|
player_id = 0
|
|
|
|
self.layouts = {
|
|
'boundary': import_csv_layout(os.path.join('map',
|
|
'FloorBlocks.csv')),
|
|
'grass': import_csv_layout(os.path.join('map',
|
|
'Grass.csv')),
|
|
'objects': import_csv_layout(os.path.join('map',
|
|
'Objects.csv')),
|
|
'entities': import_csv_layout(os.path.join('map',
|
|
'Entities.csv'))
|
|
}
|
|
|
|
self.graphics = {
|
|
'grass': import_folder(os.path.join('graphics', 'grass')),
|
|
'objects': import_folder(os.path.join('graphics', 'objects'))
|
|
}
|
|
|
|
for style, layout in self.layouts.items():
|
|
for row_index, row in enumerate(layout):
|
|
for col_index, col in enumerate(row):
|
|
if int(col) != -1:
|
|
|
|
x = col_index * TILESIZE
|
|
y = row_index * TILESIZE
|
|
|
|
# Generate unpassable terrain
|
|
if style == 'boundary':
|
|
|
|
if col == '600':
|
|
self.map_edge = (x, y)
|
|
|
|
elif col != '700':
|
|
Terrain((x, y),
|
|
[self.obstacle_sprites],
|
|
'invisible')
|
|
elif col == '700' and self.n_players > 1:
|
|
print(f"Prison set at:{(x, y)}")
|
|
# # Generate grass
|
|
# if style == 'grass':
|
|
# random_grass_image = choice(self.graphics['grass'])
|
|
#
|
|
# Terrain((x, y), [
|
|
# self.visible_sprites,
|
|
# self.obstacle_sprites,
|
|
# self.attackable_sprites
|
|
# ],
|
|
# 'grass',
|
|
# random_grass_image)
|
|
#
|
|
# # Generate objects like trees and statues
|
|
# if style == 'objects':
|
|
# surface = self.graphics['objects'][int(col)]
|
|
# Terrain((x, y), [
|
|
# self.visible_sprites,
|
|
# self.obstacle_sprites
|
|
# ],
|
|
# 'object',
|
|
# surface)
|
|
|
|
# Generate observer, players and monsters
|
|
if style == 'entities':
|
|
|
|
# Generate player(s)
|
|
if col == '400':
|
|
self.possible_player_locations.append((x, y))
|
|
|
|
# Monster generation
|
|
elif col in ['390', '391', '392', '393']:
|
|
if col == '390':
|
|
monster_name = 'bamboo'
|
|
elif col == '391':
|
|
monster_name = 'spirit'
|
|
elif col == '392':
|
|
monster_name = 'raccoon'
|
|
elif col == '393':
|
|
monster_name = 'squid'
|
|
Enemy(name=monster_name,
|
|
position=(x, y),
|
|
groups=[self.visible_sprites,
|
|
self.attackable_sprites],
|
|
visible_sprites=self.visible_sprites,
|
|
obstacle_sprites=self.obstacle_sprites)
|
|
|
|
for player_id in range(self.n_players):
|
|
Player(
|
|
player_id,
|
|
'tank',
|
|
choice(self.possible_player_locations),
|
|
self.map_edge,
|
|
[self.visible_sprites],
|
|
self.obstacle_sprites,
|
|
self.visible_sprites,
|
|
self.attack_sprites,
|
|
self.attackable_sprites
|
|
)
|
|
|
|
def reset(self):
|
|
|
|
for grass in self.grass_sprites:
|
|
grass.kill()
|
|
|
|
for enemy in self.enemy_sprites:
|
|
enemy.kill()
|
|
|
|
for style, layout in self.layouts.items():
|
|
for row_index, row in enumerate(layout):
|
|
for col_index, col in enumerate(row):
|
|
if int(col) != -1:
|
|
x = col_index * TILESIZE
|
|
y = row_index * TILESIZE
|
|
# # Regenerate grass
|
|
# if style == 'grass':
|
|
# random_grass_image = choice(
|
|
# self.graphics['grass'])
|
|
#
|
|
# Terrain((x, y), [
|
|
# self.visible_sprites,
|
|
# self.obstacle_sprites,
|
|
# self.attackable_sprites
|
|
# ],
|
|
# 'grass',
|
|
# random_grass_image)
|
|
|
|
if style == 'entities':
|
|
|
|
if col in ['390', '391', '392', '393']:
|
|
if col == '390':
|
|
monster_name = 'bamboo'
|
|
elif col == '391':
|
|
monster_name = 'spirit'
|
|
elif col == '392':
|
|
monster_name = 'raccoon'
|
|
elif col == '393':
|
|
monster_name = 'squid'
|
|
|
|
Enemy(monster_name,
|
|
(x, y),
|
|
[self.visible_sprites,
|
|
self.attackable_sprites],
|
|
self.visible_sprites,
|
|
self.obstacle_sprites)
|
|
|
|
for player in self.player_sprites:
|
|
|
|
player.animation.import_assets(
|
|
choice(self.possible_player_locations))
|
|
|
|
player.stats.health\
|
|
= player.stats.stats['health']
|
|
|
|
player.stats.energy\
|
|
= player.stats.stats['energy']
|
|
|
|
player.stats.exp = 0
|
|
|
|
self.get_entities()
|
|
self.get_distance_direction()
|
|
self.dead_players = np.zeros(self.n_players)
|
|
self.done = False
|
|
|
|
def get_entities(self):
|
|
|
|
self.player_sprites = [sprite
|
|
for sprite in self.visible_sprites.sprites()
|
|
if sprite.sprite_type == 'player']
|
|
|
|
self.enemy_sprites = [sprite
|
|
for sprite in self.visible_sprites.sprites()
|
|
if sprite.sprite_type == 'enemy']
|
|
|
|
self.grass_sprites = [sprite
|
|
for sprite in self.visible_sprites.sprites()
|
|
if sprite.sprite_type == 'grass']
|
|
|
|
def get_distance_direction(self):
|
|
for player in self.player_sprites:
|
|
player.distance_direction_from_enemy = []
|
|
|
|
for enemy in self.enemy_sprites:
|
|
enemy.distance_direction_from_player = []
|
|
|
|
for player in self.player_sprites:
|
|
if not player.is_dead():
|
|
player_vector = pygame.math.Vector2(
|
|
player.animation.rect.center
|
|
)
|
|
|
|
for enemy in self.enemy_sprites:
|
|
enemy_vector = pygame.math.Vector2(
|
|
enemy.animation.rect.center
|
|
)
|
|
distance\
|
|
= (player_vector - enemy_vector).magnitude()
|
|
|
|
if distance > 0:
|
|
direction\
|
|
= (player_vector - enemy_vector).normalize()
|
|
else:
|
|
direction\
|
|
= pygame.math.Vector2()
|
|
|
|
enemy.distance_direction_from_player.append(
|
|
(distance, direction, player))
|
|
player.distance_direction_from_enemy.append(
|
|
(distance, -direction, enemy))
|
|
|
|
def apply_damage_to_player(self):
|
|
for enemy in self.enemy_sprites:
|
|
for distance, _, player in enemy.distance_direction_from_player:
|
|
|
|
if (distance < enemy.stats.attack_radius
|
|
and player._input.combat.vulnerable):
|
|
|
|
player.stats.health -= enemy.stats.attack
|
|
player._input.combat.vulnerable = False
|
|
player._input.combat.hurt_time = pygame.time.get_ticks()
|
|
|
|
def toggle_pause(self):
|
|
self.paused = not self.paused
|
|
|
|
def run(self):
|
|
# Draw the game
|
|
self.visible_sprites.custom_draw()
|
|
self.ui.display()
|
|
|
|
if not self.paused:
|
|
# Update the game
|
|
for player in self.player_sprites:
|
|
if player.stats.health > 0:
|
|
player.attack_logic()
|
|
|
|
self.get_entities()
|
|
self.get_distance_direction()
|
|
self.visible_sprites.update()
|
|
self.apply_damage_to_player()
|
|
|
|
else:
|
|
debug('PAUSED')
|
|
|
|
for player in self.player_sprites:
|
|
if player.is_dead():
|
|
print(f"\nPlayer {player.player_id} is dead\n")
|
|
player.stats.exp = -10
|
|
player.update()
|
|
self.dead_players[player.player_id] = player.is_dead()
|
|
|
|
self.done = True if (self.dead_players.all() == 1
|
|
or self.enemy_sprites == []) else False
|