268 lines
12 KiB
Python
268 lines
12 KiB
Python
import pygame
|
|
from settings import *
|
|
from collections import deque
|
|
from ray_casting import mapping
|
|
from numba.core import types
|
|
from numba.typed import Dict
|
|
from numba import int32
|
|
|
|
|
|
class Sprites:
|
|
def __init__(self):
|
|
self.sprite_parameters = {
|
|
'sprite_barrel': {
|
|
'sprite': pygame.image.load('sprites/barrel/base/0.png').convert_alpha(),
|
|
'viewing_angles': None,
|
|
'shift': 1.8,
|
|
'scale': (0.4, 0.4),
|
|
'side': 30,
|
|
'damage': 0,
|
|
'animation': deque(
|
|
[pygame.image.load(f'sprites/barrel/anim/{i}.png').convert_alpha() for i in range(12)]),
|
|
'death_animation': deque([pygame.image.load(f'sprites/barrel/death/{i}.png')
|
|
.convert_alpha() for i in range(4)]),
|
|
'is_dead': None,
|
|
'dead_shift': 2.6,
|
|
'animation_dist': 800,
|
|
'animation_speed': 10,
|
|
'blocked': True,
|
|
'flag': 'decor',
|
|
'obj_action': []
|
|
},
|
|
'sprite_pin': {
|
|
'sprite': pygame.image.load('sprites/pin/base/0.png').convert_alpha(),
|
|
'viewing_angles': None,
|
|
'shift': 0.6,
|
|
'scale': (0.6, 0.6),
|
|
'side': 30,
|
|
'damage': 0,
|
|
'animation': deque([pygame.image.load(f'sprites/pin/anim/{i}.png').convert_alpha() for i in range(8)]),
|
|
'death_animation': [],
|
|
'is_dead': 'immortal',
|
|
'dead_shift': None,
|
|
'animation_dist': 800,
|
|
'animation_speed': 10,
|
|
'blocked': True,
|
|
'flag': 'decor',
|
|
'obj_action': []
|
|
},
|
|
'sprite_flame': {
|
|
'sprite': pygame.image.load('sprites/flame/base/0.png').convert_alpha(),
|
|
'viewing_angles': None,
|
|
'shift': 0.7,
|
|
'scale': (0.6, 0.6),
|
|
'side': 30,
|
|
'damage': 0,
|
|
'animation': deque(
|
|
[pygame.image.load(f'sprites/flame/anim/{i}.png').convert_alpha() for i in range(16)]),
|
|
'death_animation': [],
|
|
'is_dead': 'immortal',
|
|
'dead_shift': 1.8,
|
|
'animation_dist': 1800,
|
|
'animation_speed': 5,
|
|
'blocked': None,
|
|
'flag': 'decor',
|
|
'obj_action': []
|
|
},
|
|
'npc_devil': {
|
|
'sprite': [pygame.image.load(f'sprites/devil/base/{i}.png').convert_alpha() for i in range(8)],
|
|
'viewing_angles': True,
|
|
'shift': 0.0,
|
|
'scale': (1.1, 1.1),
|
|
'side': 50,
|
|
'damage': 2,
|
|
'animation': [],
|
|
'death_animation': deque([pygame.image.load(f'sprites/devil/death/{i}.png')
|
|
.convert_alpha() for i in range(6)]),
|
|
'is_dead': None,
|
|
'dead_shift': 0.6,
|
|
'animation_dist': None,
|
|
'animation_speed': 10,
|
|
'blocked': True,
|
|
'flag': 'npc',
|
|
'obj_action': deque(
|
|
[pygame.image.load(f'sprites/devil/anim/{i}.png').convert_alpha() for i in range(9)]),
|
|
},
|
|
'npc_soldier0': {
|
|
'sprite': [pygame.image.load(f'sprites/npc/soldier0/base/{i}.png').convert_alpha() for i in range(8)],
|
|
'viewing_angles': True,
|
|
'shift': 0.8,
|
|
'scale': (0.4, 0.6),
|
|
'side': 30,
|
|
'damage': 2,
|
|
'animation': [],
|
|
'death_animation': deque([pygame.image.load(f'sprites/npc/soldier0/death/{i}.png')
|
|
.convert_alpha() for i in range(10)]),
|
|
'is_dead': None,
|
|
'dead_shift': 1.7,
|
|
'animation_dist': None,
|
|
'animation_speed': 6,
|
|
'blocked': True,
|
|
'flag': 'npc',
|
|
'obj_action': deque([pygame.image.load(f'sprites/npc/soldier0/action/{i}.png')
|
|
.convert_alpha() for i in range(4)])
|
|
},
|
|
}
|
|
|
|
self.list_of_objects = [
|
|
SpriteObject(self.sprite_parameters['sprite_barrel'], (7.1, 2.1)),
|
|
SpriteObject(self.sprite_parameters['sprite_barrel'], (5.9, 2.1)),
|
|
SpriteObject(self.sprite_parameters['sprite_pin'], (8.7, 2.5)),
|
|
SpriteObject(self.sprite_parameters['npc_devil'], (7, 4)),
|
|
SpriteObject(self.sprite_parameters['sprite_flame'], (8.6, 5.6)),
|
|
SpriteObject(self.sprite_parameters['npc_soldier0'], (2.5, 1.5)),
|
|
SpriteObject(self.sprite_parameters['npc_soldier0'], (5.51, 1.5)),
|
|
SpriteObject(self.sprite_parameters['npc_soldier0'], (6.61, 2.92)),
|
|
SpriteObject(self.sprite_parameters['npc_soldier0'], (7.68, 1.47)),
|
|
SpriteObject(self.sprite_parameters['npc_soldier0'], (8.75, 3.65)),
|
|
SpriteObject(self.sprite_parameters['npc_soldier0'], (1.27, 11.5)),
|
|
SpriteObject(self.sprite_parameters['npc_soldier0'], (1.26, 8.29)),
|
|
]
|
|
|
|
@property
|
|
def sprite_shot(self):
|
|
return min([obj.is_on_fire for obj in self.list_of_objects], default=(float('inf'), 0))
|
|
|
|
@property
|
|
def blocked_doors(self):
|
|
blocked_doors = Dict.empty(key_type=types.UniTuple(int32, 2), value_type=int32)
|
|
for obj in self.list_of_objects:
|
|
if obj.flag in {'door_h', 'door_v'} and obj.blocked:
|
|
i, j = mapping(obj.x, obj.y)
|
|
blocked_doors[(i, j)] = 0
|
|
return blocked_doors
|
|
|
|
|
|
class SpriteObject:
|
|
def __init__(self, parameters, pos):
|
|
self.object = parameters['sprite'].copy()
|
|
self.viewing_angles = parameters['viewing_angles']
|
|
self.shift = parameters['shift']
|
|
self.scale = parameters['scale']
|
|
self.animation = parameters['animation'].copy()
|
|
# ---------------------
|
|
self.death_animation = parameters['death_animation'].copy()
|
|
self.is_dead = parameters['is_dead']
|
|
self.dead_shift = parameters['dead_shift']
|
|
# ---------------------
|
|
self.animation_dist = parameters['animation_dist']
|
|
self.animation_speed = parameters['animation_speed']
|
|
self.blocked = parameters['blocked']
|
|
self.flag = parameters['flag']
|
|
self.obj_action = parameters['obj_action'].copy()
|
|
self.x, self.y = pos[0] * TILE, pos[1] * TILE
|
|
self.side = parameters['side']
|
|
self.damage = parameters['damage']
|
|
self.dead_animation_count = 0
|
|
self.animation_count = 0
|
|
self.npc_action_trigger = False
|
|
self.door_open_trigger = False
|
|
self.door_prev_pos = self.y if self.flag == 'door_h' else self.x
|
|
self.delete = False
|
|
if self.viewing_angles:
|
|
if len(self.object) == 8:
|
|
self.sprite_angles = [frozenset(range(338, 361)) | frozenset(range(0, 23))] + \
|
|
[frozenset(range(i, i + 45)) for i in range(23, 338, 45)]
|
|
else:
|
|
self.sprite_angles = [frozenset(range(348, 361)) | frozenset(range(0, 11))] + \
|
|
[frozenset(range(i, i + 23)) for i in range(11, 348, 23)]
|
|
self.sprite_positions = {angle: pos for angle, pos in zip(self.sprite_angles, self.object)}
|
|
|
|
@property
|
|
def is_on_fire(self):
|
|
if CENTER_RAY - self.side // 2 < self.current_ray < CENTER_RAY + self.side // 2 and self.blocked:
|
|
return self.distance_to_sprite, self.proj_height
|
|
return float('inf'), None
|
|
|
|
def sprite_shot(self):
|
|
return min([obj.is_on_fire for obj in self.list_of_objects], default=(float('inf'), 0))
|
|
|
|
@property
|
|
def pos(self):
|
|
return self.x - self.side // 2, self.y - self.side // 2
|
|
|
|
def object_locate(self, player):
|
|
|
|
dx, dy = self.x - player.x, self.y - player.y
|
|
self.distance_to_sprite = math.sqrt(dx ** 2 + dy ** 2)
|
|
|
|
self.theta = math.atan2(dy, dx)
|
|
gamma = self.theta - player.angle
|
|
if dx > 0 and 180 <= math.degrees(player.angle) <= 360 or dx < 0 and dy < 0:
|
|
gamma += DOUBLE_PI
|
|
self.theta -= 1.4 * gamma
|
|
|
|
delta_rays = int(gamma / DELTA_ANGLE)
|
|
self.current_ray = CENTER_RAY + delta_rays
|
|
if self.flag not in {'door_h', 'door_v'}:
|
|
self.distance_to_sprite *= math.cos(HALF_FOV - self.current_ray * DELTA_ANGLE)
|
|
|
|
fake_ray = self.current_ray + FAKE_RAYS
|
|
if 0 <= fake_ray <= FAKE_RAYS_RANGE and self.distance_to_sprite > 30:
|
|
self.proj_height = min(int(PROJ_COEFF / self.distance_to_sprite),
|
|
DOUBLE_HEIGHT if self.flag not in {'door_h', 'door_v'} else HEIGHT)
|
|
sprite_width = int(self.proj_height * self.scale[0])
|
|
sprite_height = int(self.proj_height * self.scale[1])
|
|
half_sprite_width = sprite_width // 2
|
|
half_sprite_height = sprite_height // 2
|
|
shift = half_sprite_height * self.shift
|
|
|
|
# logic for doors, npc, decor
|
|
if self.is_dead and self.is_dead != 'immortal':
|
|
sprite_object = self.dead_animation()
|
|
shift = half_sprite_height * self.dead_shift
|
|
sprite_height = int(sprite_height / 1.3)
|
|
elif self.npc_action_trigger:
|
|
sprite_object = self.npc_in_action()
|
|
else:
|
|
self.object = self.visible_sprite()
|
|
sprite_object = self.sprite_animation()
|
|
|
|
|
|
# sprite scale and pos
|
|
sprite_pos = (self.current_ray * SCALE - half_sprite_width, HALF_HEIGHT - half_sprite_height + shift)
|
|
sprite = pygame.transform.scale(sprite_object, (sprite_width, sprite_height))
|
|
return (self.distance_to_sprite, sprite, sprite_pos)
|
|
else:
|
|
return (False,)
|
|
|
|
def sprite_animation(self):
|
|
if self.animation and self.distance_to_sprite < self.animation_dist:
|
|
sprite_object = self.animation[0]
|
|
if self.animation_count < self.animation_speed:
|
|
self.animation_count += 1
|
|
else:
|
|
self.animation.rotate()
|
|
self.animation_count = 0
|
|
return sprite_object
|
|
return self.object
|
|
|
|
def visible_sprite(self):
|
|
if self.viewing_angles:
|
|
if self.theta < 0:
|
|
self.theta += DOUBLE_PI
|
|
self.theta = 360 - int(math.degrees(self.theta))
|
|
|
|
for angles in self.sprite_angles:
|
|
if self.theta in angles:
|
|
return self.sprite_positions[angles]
|
|
return self.object
|
|
|
|
def dead_animation(self):
|
|
if len(self.death_animation):
|
|
if self.dead_animation_count < self.animation_speed:
|
|
self.dead_sprite = self.death_animation[0]
|
|
self.dead_animation_count += 1
|
|
else:
|
|
self.dead_sprite = self.death_animation.popleft()
|
|
self.dead_animation_count = 0
|
|
return self.dead_sprite
|
|
|
|
def npc_in_action(self):
|
|
sprite_object = self.obj_action[0]
|
|
if self.animation_count < self.animation_speed:
|
|
self.animation_count += 1
|
|
else:
|
|
self.obj_action.rotate()
|
|
self.animation_count = 0
|
|
return sprite_object |