254 lines
7.8 KiB
Python
254 lines
7.8 KiB
Python
import lzma
|
|
import math
|
|
import pickle
|
|
from dataclasses import dataclass
|
|
from typing import Any, List, Optional, Tuple
|
|
|
|
import numpy as np
|
|
import numpy.typing as npt
|
|
|
|
from mower import __rootdir__
|
|
|
|
|
|
@dataclass
|
|
class Tile:
|
|
heightType: int
|
|
buildableType: int
|
|
passableWall: bool
|
|
|
|
|
|
@dataclass
|
|
class Vector3:
|
|
x: float
|
|
y: float
|
|
z: float
|
|
|
|
def clone(self) -> "Vector3":
|
|
return Vector3(self.x, self.y, self.z)
|
|
|
|
|
|
@dataclass
|
|
class Vector2:
|
|
x: float
|
|
y: float
|
|
|
|
def clone(self) -> "Vector2":
|
|
return Vector2(self.x, self.y)
|
|
|
|
|
|
@dataclass
|
|
class Level:
|
|
stageId: str
|
|
code: str
|
|
levelId: str
|
|
name: str
|
|
height: int
|
|
width: int
|
|
tiles: List[List[Tile]] = None
|
|
view: List[List[int]] = None
|
|
|
|
@classmethod
|
|
def from_json(cls, json_data: dict[Any, Any]) -> "Level":
|
|
raw_tiles = json_data["tiles"]
|
|
tiles = []
|
|
for row in raw_tiles:
|
|
row_tiles = []
|
|
for tile in row:
|
|
row_tiles.append(
|
|
Tile(
|
|
tile["heightType"],
|
|
tile["buildableType"],
|
|
tile["tileKey"] == "tile_passable_wall",
|
|
)
|
|
)
|
|
tiles.append(row_tiles)
|
|
|
|
return cls(
|
|
stageId=json_data["stageId"],
|
|
code=json_data["code"],
|
|
levelId=json_data["levelId"],
|
|
name=json_data["name"],
|
|
height=json_data["height"],
|
|
width=json_data["width"],
|
|
tiles=tiles,
|
|
view=json_data["view"],
|
|
)
|
|
|
|
def get_width(self):
|
|
return self.width
|
|
|
|
def get_height(self):
|
|
return self.height
|
|
|
|
def get_tile(self, row: int, col: int) -> Optional[Tile]:
|
|
if 0 <= row <= self.height and 0 <= col <= self.width:
|
|
return self.tiles[row][col]
|
|
return None
|
|
|
|
|
|
class Calc:
|
|
screen_width: int
|
|
screen_height: int
|
|
ratio: float
|
|
|
|
view: Vector3
|
|
view_side: Vector3
|
|
level: Level
|
|
|
|
matrix_p: npt.NDArray[np.float32]
|
|
matrix_x: npt.NDArray[np.float32]
|
|
matrix_y: npt.NDArray[np.float32]
|
|
|
|
def __init__(self, screen_width: int, screen_height: int, level: Level):
|
|
self.screen_width = screen_width
|
|
self.screen_height = screen_height
|
|
self.ratio = screen_height / screen_width
|
|
self.level = level
|
|
self.matrix_p = np.array(
|
|
[
|
|
[self.ratio / math.tan(math.pi * 20 / 180), 0, 0, 0],
|
|
[0, 1 / math.tan(math.pi * 20 / 180), 0, 0],
|
|
[0, 0, -(1000 + 0.3) / (1000 - 0.3), -(1000 * 0.3 * 2) / (1000 - 0.3)],
|
|
[0, 0, -1, 0],
|
|
]
|
|
)
|
|
self.matrix_x = np.array(
|
|
[
|
|
[1, 0, 0, 0],
|
|
[0, math.cos(math.pi * 30 / 180), -math.sin(math.pi * 30 / 180), 0],
|
|
[0, -math.sin(math.pi * 30 / 180), -math.cos(math.pi * 30 / 180), 0],
|
|
[0, 0, 0, 1],
|
|
]
|
|
)
|
|
self.matrix_y = np.array(
|
|
[
|
|
[math.cos(math.pi * 10 / 180), 0, math.sin(math.pi * 10 / 180), 0],
|
|
[0, 1, 0, 0],
|
|
[-math.sin(math.pi * 10 / 180), 0, math.cos(math.pi * 10 / 180), 0],
|
|
[0, 0, 0, 1],
|
|
]
|
|
)
|
|
self.view = Vector3(level.view[0][0], level.view[0][1], level.view[0][2])
|
|
self.view_side = Vector3(level.view[1][0], level.view[1][1], level.view[1][2])
|
|
|
|
def adapter(self) -> Tuple[float, float]:
|
|
fromRatio = 9 / 16
|
|
toRatio = 3 / 4
|
|
if self.ratio < fromRatio - 0.00001:
|
|
return 0, 0
|
|
t = (self.ratio - fromRatio) / (toRatio - fromRatio)
|
|
return -1.4 * t, -2.8 * t
|
|
|
|
def get_focus_offset(self, tile_x: int, tile_y: int) -> Vector3:
|
|
x = tile_x - (self.level.width - 1) / 2
|
|
y = (self.level.height - 1) / 2 - tile_y
|
|
return Vector3(x, y, 0)
|
|
|
|
def get_character_world_pos(self, tile_x: int, tile_y: int) -> Vector3:
|
|
x = tile_x - (self.level.width - 1) / 2
|
|
y = (self.level.height - 1) / 2 - tile_y
|
|
tile = self.level.get_tile(tile_y, tile_x)
|
|
assert tile is not None
|
|
z = tile.heightType * -0.4
|
|
return Vector3(x, y, z)
|
|
|
|
def get_character_display_world_pos(self, tile_x: int, tile_y: int) -> Vector3:
|
|
x = tile_x - (self.level.width - 1) / 2
|
|
y = (self.level.height - 1) / 2 - tile_y
|
|
tile = self.level.get_tile(tile_y, tile_x)
|
|
assert tile is not None
|
|
z = (1 if tile.passableWall else tile.heightType) * -0.4
|
|
return Vector3(x, y, z)
|
|
|
|
def get_with_draw_world_pos(self, tile_x: int, tile_y: int) -> Vector3:
|
|
ret = self.get_character_world_pos(tile_x, tile_y)
|
|
ret.x -= 1.3143386840820312
|
|
ret.y += 1.314337134361267
|
|
ret.z = -0.3967874050140381
|
|
return ret
|
|
|
|
def get_skill_world_pos(self, tile_x: int, tile_y: int) -> Vector3:
|
|
ret = self.get_character_world_pos(tile_x, tile_y)
|
|
ret.x += 1.3143386840820312
|
|
ret.y -= 1.314337134361267
|
|
ret.z = -0.3967874050140381
|
|
return ret
|
|
|
|
def get_character_screen_pos(
|
|
self, tile_x: int, tile_y: int, side: bool = False, focus: bool = False
|
|
) -> Vector2:
|
|
if focus:
|
|
side = True
|
|
if side:
|
|
world_pos = self.get_character_world_pos(tile_x, tile_y)
|
|
else:
|
|
world_pos = self.get_character_display_world_pos(tile_x, tile_y)
|
|
if focus:
|
|
offset = self.get_focus_offset(tile_x, tile_y)
|
|
else:
|
|
offset = Vector3(0.0, 0.0, 0.0)
|
|
return self.world_to_screen_pos(world_pos, side, offset)
|
|
|
|
def get_with_draw_screen_pos(self, tile_x: int, tile_y: int) -> Vector2:
|
|
world_pos = self.get_with_draw_world_pos(tile_x, tile_y)
|
|
offset = self.get_focus_offset(tile_x, tile_y)
|
|
return self.world_to_screen_pos(world_pos, True, offset)
|
|
|
|
def get_skill_screen_pos(self, tile_x: int, tile_y: int) -> Vector2:
|
|
world_pos = self.get_skill_world_pos(tile_x, tile_y)
|
|
offset = self.get_focus_offset(tile_x, tile_y)
|
|
return self.world_to_screen_pos(world_pos, True, offset)
|
|
|
|
def world_to_screen_matrix(
|
|
self, side: bool = False, offset: Optional[Vector3] = None
|
|
) -> npt.NDArray[np.float32]:
|
|
if offset is None:
|
|
offset = Vector3(0.0, 0.0, 0.0)
|
|
adapter_y, adapter_z = self.adapter()
|
|
if side:
|
|
x, y, z = self.view_side.x, self.view_side.y, self.view_side.z
|
|
else:
|
|
x, y, z = self.view.x, self.view.y, self.view.z
|
|
x += offset.x
|
|
y += offset.y + adapter_y
|
|
z += offset.z + adapter_z
|
|
raw = np.array(
|
|
[
|
|
[1, 0, 0, -x],
|
|
[0, 1, 0, -y],
|
|
[0, 0, 1, -z],
|
|
[0, 0, 0, 1],
|
|
],
|
|
np.float32,
|
|
)
|
|
if side:
|
|
matrix = np.dot(self.matrix_x, self.matrix_y)
|
|
matrix = np.dot(matrix, raw)
|
|
else:
|
|
matrix = np.dot(self.matrix_x, raw)
|
|
return np.dot(self.matrix_p, matrix)
|
|
|
|
def world_to_screen_pos(
|
|
self, pos: Vector3, side: bool = False, offset: Optional[Vector3] = None
|
|
) -> Vector2:
|
|
matrix = self.world_to_screen_matrix(side, offset)
|
|
x, y, _, w = np.dot(matrix, np.array([pos.x, pos.y, pos.z, 1]))
|
|
x = (1 + x / w) / 2
|
|
y = (1 + y / w) / 2
|
|
return Vector2(x * self.screen_width, (1 - y) * self.screen_height)
|
|
|
|
|
|
LEVELS: List[Level] = []
|
|
with lzma.open(f"{__rootdir__}/static/levels.pkl", "rb") as f:
|
|
level_table = pickle.load(f)
|
|
for data in level_table:
|
|
LEVELS.append(Level.from_json(data))
|
|
|
|
|
|
def find_level(code: Optional[str], name: Optional[str]) -> Optional[Level]:
|
|
for level in LEVELS:
|
|
if code is not None and code == level.code:
|
|
return level
|
|
if name is not None and name == level.name:
|
|
return level
|
|
return None
|