Merge branch 'master' into 'doc'
# Conflicts: # squirrelbattle/entities/items.py # squirrelbattle/interfaces.py
This commit is contained in:
@ -2,13 +2,11 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from enum import Enum, auto
|
||||
from math import sqrt
|
||||
from random import choice, randint
|
||||
from math import ceil, sqrt
|
||||
from random import choice, choices, randint
|
||||
from typing import List, Optional, Any, Dict, Tuple
|
||||
from queue import PriorityQueue
|
||||
from functools import reduce
|
||||
from random import choice, randint, choices
|
||||
from typing import List, Optional, Any
|
||||
|
||||
from .display.texturepack import TexturePack
|
||||
from .translations import gettext as _
|
||||
@ -34,16 +32,47 @@ class Logs:
|
||||
self.messages = []
|
||||
|
||||
|
||||
class Slope():
|
||||
X: int
|
||||
Y: int
|
||||
|
||||
def __init__(self, y: int, x: int) -> None:
|
||||
self.Y = y
|
||||
self.X = x
|
||||
|
||||
def compare(self, other: "Slope") -> int:
|
||||
y, x = other.Y, other.X
|
||||
return self.Y * x - self.X * y
|
||||
|
||||
def __lt__(self, other: "Slope") -> bool:
|
||||
return self.compare(other) < 0
|
||||
|
||||
def __eq__(self, other: "Slope") -> bool:
|
||||
return self.compare(other) == 0
|
||||
|
||||
def __gt__(self, other: "Slope") -> bool:
|
||||
return self.compare(other) > 0
|
||||
|
||||
def __le__(self, other: "Slope") -> bool:
|
||||
return self.compare(other) <= 0
|
||||
|
||||
def __ge__(self, other: "Slope") -> bool:
|
||||
return self.compare(other) >= 0
|
||||
|
||||
|
||||
class Map:
|
||||
"""
|
||||
The Map object represents a with its width, height
|
||||
and tiles, that have their custom properties.
|
||||
"""
|
||||
floor: int
|
||||
width: int
|
||||
height: int
|
||||
start_y: int
|
||||
start_x: int
|
||||
tiles: List[List["Tile"]]
|
||||
visibility: List[List[bool]]
|
||||
seen_tiles: List[List[bool]]
|
||||
entities: List["Entity"]
|
||||
logs: Logs
|
||||
# coordinates of the point that should be
|
||||
@ -53,11 +82,16 @@ class Map:
|
||||
|
||||
def __init__(self, width: int, height: int, tiles: list,
|
||||
start_y: int, start_x: int):
|
||||
self.floor = 0
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.start_y = start_y
|
||||
self.start_x = start_x
|
||||
self.tiles = tiles
|
||||
self.visibility = [[False for _ in range(len(tiles[0]))]
|
||||
for _ in range(len(tiles))]
|
||||
self.seen_tiles = [[False for _ in range(len(tiles[0]))]
|
||||
for _ in range(len(tiles))]
|
||||
self.entities = []
|
||||
self.logs = Logs()
|
||||
|
||||
@ -147,18 +181,138 @@ class Map:
|
||||
"""
|
||||
Puts randomly {count} entities on the map, only on empty ground tiles.
|
||||
"""
|
||||
for ignored in range(count):
|
||||
for _ignored in range(count):
|
||||
y, x = 0, 0
|
||||
while True:
|
||||
y, x = randint(0, self.height - 1), randint(0, self.width - 1)
|
||||
tile = self.tiles[y][x]
|
||||
if tile.can_walk():
|
||||
break
|
||||
entity = choices(Entity.get_all_entity_classes(),\
|
||||
weights = Entity.get_weights(), k=1)[0]()
|
||||
entity = choices(Entity.get_all_entity_classes(),
|
||||
weights=Entity.get_weights(), k=1)[0]()
|
||||
entity.move(y, x)
|
||||
self.add_entity(entity)
|
||||
|
||||
def compute_visibility(self, y: int, x: int, max_range: int) -> None:
|
||||
"""
|
||||
Sets the visible tiles to be the ones visible by an entity at point
|
||||
(y, x), using a twaked shadow casting algorithm
|
||||
"""
|
||||
|
||||
for line in self.visibility:
|
||||
for i in range(len(line)):
|
||||
line[i] = False
|
||||
self.set_visible(0, 0, 0, (y, x))
|
||||
for octant in range(8):
|
||||
self.compute_visibility_octant(octant, (y, x), max_range, 1,
|
||||
Slope(1, 1), Slope(0, 1))
|
||||
|
||||
def crop_top_visibility(self, octant: int, origin: Tuple[int, int],
|
||||
x: int, top: Slope) -> int:
|
||||
if top.X == 1:
|
||||
top_y = x
|
||||
else:
|
||||
top_y = ceil(((x * 2 - 1) * top.Y + top.X) / (top.X * 2))
|
||||
if self.is_wall(top_y, x, octant, origin):
|
||||
top_y += top >= Slope(top_y * 2 + 1, x * 2) and not \
|
||||
self.is_wall(top_y + 1, x, octant, origin)
|
||||
else:
|
||||
ax = x * 2
|
||||
ax += self.is_wall(top_y + 1, x + 1, octant, origin)
|
||||
top_y += top > Slope(top_y * 2 + 1, ax)
|
||||
return top_y
|
||||
|
||||
def crop_bottom_visibility(self, octant: int, origin: Tuple[int, int],
|
||||
x: int, bottom: Slope) -> int:
|
||||
if bottom.Y == 0:
|
||||
bottom_y = 0
|
||||
else:
|
||||
bottom_y = ceil(((x * 2 - 1) * bottom.Y + bottom.X)
|
||||
/ (bottom.X * 2))
|
||||
bottom_y += bottom >= Slope(bottom_y * 2 + 1, x * 2) and \
|
||||
self.is_wall(bottom_y, x, octant, origin) and \
|
||||
not self.is_wall(bottom_y + 1, x, octant, origin)
|
||||
return bottom_y
|
||||
|
||||
def compute_visibility_octant(self, octant: int, origin: Tuple[int, int],
|
||||
max_range: int, distance: int, top: Slope,
|
||||
bottom: Slope) -> None:
|
||||
for x in range(distance, max_range + 1):
|
||||
top_y = self.crop_top_visibility(octant, origin, x, top)
|
||||
bottom_y = self.crop_bottom_visibility(octant, origin, x, bottom)
|
||||
was_opaque = -1
|
||||
for y in range(top_y, bottom_y - 1, -1):
|
||||
if x + y > max_range:
|
||||
continue
|
||||
is_opaque = self.is_wall(y, x, octant, origin)
|
||||
is_visible = is_opaque\
|
||||
or ((y != top_y or top > Slope(y * 4 - 1, x * 4 + 1))
|
||||
and (y != bottom_y
|
||||
or bottom < Slope(y * 4 + 1, x * 4 - 1)))
|
||||
# is_visible = is_opaque\
|
||||
# or ((y != top_y or top >= Slope(y, x))
|
||||
# and (y != bottom_y or bottom <= Slope(y, x)))
|
||||
if is_visible:
|
||||
self.set_visible(y, x, octant, origin)
|
||||
if x == max_range:
|
||||
continue
|
||||
if is_opaque and was_opaque == 0:
|
||||
nx, ny = x * 2, y * 2 + 1
|
||||
nx -= self.is_wall(y + 1, x, octant, origin)
|
||||
if top > Slope(ny, nx):
|
||||
if y == bottom_y:
|
||||
bottom = Slope(ny, nx)
|
||||
break
|
||||
else:
|
||||
self.compute_visibility_octant(
|
||||
octant, origin, max_range, x + 1, top,
|
||||
Slope(ny, nx))
|
||||
elif y == bottom_y: # pragma: no cover
|
||||
return
|
||||
elif not is_opaque and was_opaque == 1:
|
||||
nx, ny = x * 2, y * 2 + 1
|
||||
nx += self.is_wall(y + 1, x + 1, octant, origin)
|
||||
if bottom >= Slope(ny, nx): # pragma: no cover
|
||||
return
|
||||
top = Slope(ny, nx)
|
||||
was_opaque = is_opaque
|
||||
if was_opaque != 0:
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def translate_coord(y: int, x: int, octant: int,
|
||||
origin: Tuple[int, int]) -> Tuple[int, int]:
|
||||
ny, nx = origin
|
||||
if octant == 0:
|
||||
return ny - y, nx + x
|
||||
elif octant == 1:
|
||||
return ny - x, nx + y
|
||||
elif octant == 2:
|
||||
return ny - x, nx - y
|
||||
elif octant == 3:
|
||||
return ny - y, nx - x
|
||||
elif octant == 4:
|
||||
return ny + y, nx - x
|
||||
elif octant == 5:
|
||||
return ny + x, nx - y
|
||||
elif octant == 6:
|
||||
return ny + x, nx + y
|
||||
elif octant == 7:
|
||||
return ny + y, nx + x
|
||||
|
||||
def is_wall(self, y: int, x: int, octant: int,
|
||||
origin: Tuple[int, int]) -> bool:
|
||||
y, x = self.translate_coord(y, x, octant, origin)
|
||||
return 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]) and \
|
||||
self.tiles[y][x].is_wall()
|
||||
|
||||
def set_visible(self, y: int, x: int, octant: int,
|
||||
origin: Tuple[int, int]) -> None:
|
||||
y, x = self.translate_coord(y, x, octant, origin)
|
||||
if 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]):
|
||||
self.visibility[y][x] = True
|
||||
self.seen_tiles[y][x] = True
|
||||
|
||||
def tick(self, p: Any) -> None:
|
||||
"""
|
||||
Triggers all entity events.
|
||||
@ -210,6 +364,7 @@ class Tile(Enum):
|
||||
EMPTY = auto()
|
||||
WALL = auto()
|
||||
FLOOR = auto()
|
||||
LADDER = auto()
|
||||
|
||||
@staticmethod
|
||||
def from_ascii_char(ch: str) -> "Tile":
|
||||
@ -226,7 +381,25 @@ class Tile(Enum):
|
||||
Translates a Tile to the corresponding character according
|
||||
to the texture pack.
|
||||
"""
|
||||
return getattr(pack, self.name)
|
||||
val = getattr(pack, self.name)
|
||||
return val[0] if isinstance(val, tuple) else val
|
||||
|
||||
def visible_color(self, pack: TexturePack) -> Tuple[int, int]:
|
||||
"""
|
||||
Retrieve the tuple (fg_color, bg_color) of the current Tile
|
||||
if it is visible.
|
||||
"""
|
||||
val = getattr(pack, self.name)
|
||||
return (val[2], val[4]) if isinstance(val, tuple) else \
|
||||
(pack.tile_fg_visible_color, pack.tile_bg_color)
|
||||
|
||||
def hidden_color(self, pack: TexturePack) -> Tuple[int, int]:
|
||||
"""
|
||||
Retrieve the tuple (fg_color, bg_color) of the current Tile.
|
||||
"""
|
||||
val = getattr(pack, self.name)
|
||||
return (val[1], val[3]) if isinstance(val, tuple) else \
|
||||
(pack.tile_fg_color, pack.tile_bg_color)
|
||||
|
||||
def is_wall(self) -> bool:
|
||||
"""
|
||||
@ -234,6 +407,12 @@ class Tile(Enum):
|
||||
"""
|
||||
return self == Tile.WALL
|
||||
|
||||
def is_ladder(self) -> bool:
|
||||
"""
|
||||
Is this Tile a ladder?
|
||||
"""
|
||||
return self == Tile.LADDER
|
||||
|
||||
def can_walk(self) -> bool:
|
||||
"""
|
||||
Checks if an entity (player or not) can move in this tile.
|
||||
@ -448,7 +627,7 @@ class Entity:
|
||||
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \
|
||||
TeddyBear, GiantSeaEagle
|
||||
from squirrelbattle.entities.friendly import Merchant, Sunflower, \
|
||||
Trumpet
|
||||
Trumpet
|
||||
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \
|
||||
Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP
|
||||
return {
|
||||
@ -525,12 +704,12 @@ class FightingEntity(Entity):
|
||||
The entity deals damage to the opponent
|
||||
based on their respective stats.
|
||||
"""
|
||||
diceroll = randint(0, 100)
|
||||
diceroll = randint(1, 100)
|
||||
damage = self.strength
|
||||
string = " "
|
||||
if diceroll <= self.critical: # It is a critical hit
|
||||
if diceroll <= self.critical: # It is a critical hit
|
||||
damage *= 4
|
||||
string = _(" It's a critical hit! ")
|
||||
string = " " + _("It's a critical hit!") + " "
|
||||
return _("{name} hits {opponent}.")\
|
||||
.format(name=_(self.translated_name.capitalize()),
|
||||
opponent=_(opponent.translated_name)) + string + \
|
||||
|
Reference in New Issue
Block a user