from __future__ import annotations
import numpy as np
from PySide6.QtCore import QPoint, QRect, QSize
from PySide6.QtWidgets import QGraphicsPixmapItem, QGraphicsRectItem
from RWS.Modify import HistoryElement
from RWS.Core import RWELevel, CELLSIZE
from RWS.Loaders import Tile
[docs]
class PlacedTileHead:
[docs]
def __init__(self, tile: Tile, pos: QPoint, layer: int):
self.tile = tile
# self.tilebodies = []
self.pos = pos
self.layer = layer
self.graphics: list[QGraphicsPixmapItem] = []
[docs]
def tostring(self, level):
return self.tile.name
[docs]
def remove(self, level, redraw=True):
# for i in self.tilebodies:
# i.remove(level)
# not recommended to use this method since it doesn't create any history
ofs = self.tile.top_left
pos = self.pos - ofs
for x in range(pos.x(), pos.x() + self.tile.size.width()):
for y in range(pos.y(), pos.y() + self.tile.size.height()):
tile = level.l_tiles.tiles[x][y][self.layer]
if isinstance(tile, PlacedTileBody) and tile.headpos == self.pos and tile.headlayer == self.layer:
tile.remove(level)
if self.tile.multilayer and self.layer < 2:
for x in range(pos.x(), pos.x() + self.tile.size.width()):
for y in range(pos.y(), pos.y() + self.tile.size.height()):
tile = level.l_tiles.tiles[x][y][self.layer + 1]
if isinstance(tile, PlacedTileBody) and tile.headpos == self.pos and tile.headlayer == self.layer:
tile.remove(level)
level.l_tiles[self.pos][self.layer] = None
self.remove_graphics(level, redraw)
[docs]
def remove_graphics(self, level, redraw=True):
bounds = self.tile_bounds
if len(self.graphics) > 0:
for i in self.graphics:
i.removeFromIndex()
self.graphics.clear()
self.graphics = []
texture = level.viewport.modulenames["tiles"].get_layer(self.layer)
texture.render_rect(bounds)
if redraw:
texture.redraw()
[docs]
def add_graphics(self, level, redraw=True):
if len(self.graphics) > 0:
self.remove_graphics(level, False)
layer = level.viewport.modulenames["tiles"].get_layer(self.layer)
layer.draw_tile(self.pos, True)
if redraw:
layer.redraw()
@property
def tile_bounds(self):
tl = (self.pos - self.tile.option_based_top_left(1)) * CELLSIZE
return QRect(tl, (self.tile.size + QSize(self.tile.bfTiles, self.tile.bfTiles) * 2) * CELLSIZE)
[docs]
def copy(self):
return PlacedTileHead(self.tile, self.pos.__copy__(), self.layer)
[docs]
class PlacedTileBody:
[docs]
def __init__(self, tileheadoffset: QPoint, headlayer, pos: QPoint, layer: int):
self.headoffset = tileheadoffset
self.headlayer = headlayer
self.pos = pos
self.layer = layer
@property
def headpos(self):
return self.pos - self.headoffset
[docs]
def tilehead(self, level):
return level.l_tiles[self.headpos][self.headlayer]
[docs]
def tostring(self, level):
if not isinstance(level.l_tiles[self.headpos][self.headlayer], PlacedTileHead):
return "Error"
return self.tilehead(level).tile.name
[docs]
def remove(self, level: RWELevel):
level.l_tiles[self.pos][self.layer] = None
[docs]
def remove_graphics(self, *args):
pass
[docs]
def add_graphics(self, *args):
pass
[docs]
def tile(self, level) -> Tile:
return self.tilehead(level).tile
[docs]
def copy(self):
return PlacedTileBody(self.headoffset.__copy__(), self.headlayer, self.pos.__copy__(), self.layer)
[docs]
class PlacedMaterial:
[docs]
def __init__(self, tile: Tile, pos: QPoint, layer: int):
self.tile = tile
self.graphics: list[QGraphicsRectItem] = []
self.pos = pos
self.layer = layer
[docs]
def tostring(self, level):
return self.tile.name
[docs]
def copy(self):
return PlacedMaterial(self.tile, self.pos.__copy__(), self.layer)
[docs]
def remove(self, level, redraw=True):
level.l_tiles[self.pos][self.layer] = None
self.remove_graphics(level, redraw)
[docs]
def remove_graphics(self, level, redraw=True):
bounds = self.tile_bounds
if len(self.graphics) > 0:
for i in self.graphics:
i.removeFromIndex()
self.graphics.clear()
self.graphics = []
texture = level.viewport.modulenames["tiles"].get_layer(self.layer)
texture.render_rect(bounds)
if redraw:
texture.redraw()
[docs]
def add_graphics(self, level, redraw=True):
if len(self.graphics) > 0:
self.remove_graphics(level, False)
layer = level.viewport.modulenames["tiles"].get_layer(self.layer)
layer.draw_tile(self.pos, True)
if redraw:
layer.redraw()
@property
def tile_bounds(self):
return QRect(self.pos * CELLSIZE, QSize(CELLSIZE, CELLSIZE))
[docs]
def can_place(level: RWELevel, pos: QPoint, layer: int, tile: Tile, force_place: bool, force_geometry: bool,
area: list[list[bool]] = None, area2: list[list[bool]] = None) -> bool:
headpos = tile.top_left + pos
if not level.inside(headpos):
return False
# if area is available
for x in range(pos.x(), pos.x() + tile.size.width()):
for y in range(pos.y(), pos.y() + tile.size.height()):
if not level.inside(QPoint(x, y)):
continue
if area is not None and not area[x][y]:
return False
if area2 is not None and isinstance(tile.cols1, list) and not area2[x][y]:
return False
return check_collisions(level, pos, layer, tile, force_place, force_geometry)
[docs]
def check_collisions(level: RWELevel, pos: QPoint, layer: int, tile: Tile, force_place: bool = False, force_geometry: bool = False) -> bool:
for x in range(tile.size.width()):
for y in range(tile.size.height()):
tilepos = QPoint(x, y)
levelpos = tilepos + pos
if not level.inside(levelpos):
continue
result = point_collision(level, levelpos, layer, tilepos, tile, force_place, force_geometry)
if not result:
return False
return True
[docs]
def point_collision(level: RWELevel, pos: QPoint, layer: int, tilepos: QPoint, tile: Tile, force_place: bool = False, force_geometry: bool = False) -> bool:
tiledata = level.l_tiles(pos, layer)
try:
collision = tile.cols[tilepos.x() * tile.size.height() + tilepos.y()]
except IndexError:
collision = 1
# checking tile-tile collisions
if isinstance(tiledata, PlacedTileHead) and collision != -1: # when we know we are placing block on tile head
return False
elif not force_place and tiledata is not None and not isinstance(tiledata, PlacedMaterial): return False # when it's not force place
# checking tile_geo collisions
geodata = level.l_geo.blocks[pos.x(), pos.y(), layer]
if collision != -1 and geodata != collision and not force_geometry and not force_place: return False
# next layer(if exists)
if not isinstance(tile.cols1, list) or layer > 1: return True
try:
collision = tile.cols1[tilepos.x() * tile.size.height() + tilepos.y()]
except IndexError:
collision = 1
if collision != -1:
return True
# checking tile-tile collision on next layer
tiledata = level.l_tiles(pos, layer + 1)
if isinstance(tiledata, PlacedTileHead) and collision != -1: # when we know we are placing block on tile head
return False
elif not force_place and tiledata is not None and not isinstance(tiledata, PlacedMaterial): return False # when it's not force place
# checking tile-geo collision on next layer
geodata = level.l_geo.blocks[pos.x(), pos.y(), layer + 1]
if collision != -1 and geodata != collision and not force_geometry and not force_place: return False
return True
[docs]
def copy_tile(tile: PlacedTileHead | PlacedTileBody | PlacedMaterial | None):
if tile is None:
return None
return tile.copy()
[docs]
def tile_changes(level: RWELevel,
pos: QPoint,
head: QPoint,
tile: Tile,
layer: int,
col: int,
fp: bool,
fg: bool,
secondlayer: bool) -> [list | None, list | None]:
if col == -1:
return None, None
geochange = None
if fp and isinstance(level.l_tiles[pos][layer], PlacedTileHead):
return None, None
elif not fp and level.l_tiles[pos][layer] is not None:
return None, None
#level.viewport.modulenames["tiles"].get_layer(layer).clean_pixel(pos)
if fg and col != level.l_geo.blocks[pos.x(), pos.y(), layer]:
geochange = [pos, layer, level.l_geo.blocks[pos.x(), pos.y(), layer], col]
level.l_geo.blocks[pos.x(), pos.y(), layer] = np.uint8(col)
# level.data["GE"][pos.x()][pos.y()][layer][0] = col
level.viewport.modulenames["geo"].get_layer(layer).draw_geo(pos.x(), pos.y(), True)
if not secondlayer and pos == head:
change = [copy_tile(level.l_tiles(pos, layer)), PlacedTileHead(tile, pos, layer)]
if level.l_tiles[pos][layer] is not None:
level.l_tiles[pos][layer].remove_graphics(level, True)
# change = [pos, layer,
# {"tp": "tileHead", "data": [lingoIO.point([tile.cat.x(), tile.cat.y()]), tile.name]},
# copy_tile(level.l_tiles(pos, layer))]
level.l_tiles[pos][layer] = copy_tile(change[1])
return change, geochange
change = [copy_tile(level.l_tiles[pos][layer]), PlacedTileBody(pos - head, layer + (1 if secondlayer else 0), pos, layer)]
if level.l_tiles[pos][layer] is not None:
level.l_tiles[pos][layer].remove_graphics(level, True)
# change = [pos, layer,
# {"tp": "tileBody", "data": [lingoIO.point([head.x() + 1, head.y() + 1]), layer + 1]},
# copy_tile(level.l_tiles(pos, layer))]
level.l_tiles[pos][layer] = copy_tile(change[1])
return change, geochange
[docs]
def place_tile(level: RWELevel,
pos: QPoint,
layer: int,
tile: Tile,
area: np.array = None,
area2: np.array = None,
force_place: bool = False,
force_geometry: bool = False) -> PlacedTile | None:
if tile.type == "material" and area[pos.x(), pos.y()]:
change = [copy_tile(level.l_tiles[pos][layer]), PlacedMaterial(tile, pos, layer)]
geochange = []
if force_geometry and level.l_geo.blocks[pos.x(), pos.y(), layer] != 0:
geochange = [[pos, layer, level.l_geo.blocks[pos.x(), pos.y(), layer], 1]]
# level.data["GE"][pos.x()][pos.y()][layer][0] = 1
level.l_geo.blocks[pos.x(), pos.y(), layer] = np.uint8(1)
#level.data["TE"]["tlMatrix"][pos.x()][pos.y()][layer] = {"tp": "material", "data": tile.name}
if level.l_tiles[pos][layer] is not None:
level.l_tiles[pos][layer].remove_graphics(level, True)
level.l_tiles[pos][layer] = PlacedMaterial(tile, pos, layer)
area[pos.x(), pos.y()] = False
level.viewport.modulenames["tiles"].get_layer(layer).draw_tile(pos, True)
level.viewport.modulenames["geo"].get_layer(layer).draw_geo(pos.x(), pos.y(), True)
return PlacedTile(level, [change], geochange)
headpos = tile.top_left + pos
if not level.inside(headpos):
return None
changes = []
geochanges = []
isnextlayer = tile.multilayer and layer < 2
for x in range(tile.size.width()):
for y in range(tile.size.height()):
tilepos = QPoint(x, y) + pos
if not level.inside(tilepos):
continue
if area is not None:
area[tilepos.x(), tilepos.y()] = False
# area2[x][y] = False
try:
col = tile.cols[x * tile.size.height() + y]
except IndexError:
col = 1
if isnextlayer:
if area2 is not None:
area2[tilepos.x(), tilepos.y()] = False
try:
col2 = tile.cols1[x * tile.size.height() + y]
except IndexError:
col2 = 1
ch, gch = tile_changes(level, tilepos, headpos, tile, layer + 1, col2, force_place, force_geometry, True)
if ch is not None:
changes.append(ch)
if gch is not None:
geochanges.append(gch)
ch, gch = tile_changes(level, tilepos, headpos, tile, layer, col, force_place, force_geometry, False)
if ch is not None:
changes.append(ch)
if gch is not None:
geochanges.append(gch)
level.viewport.modulenames["tiles"].get_layer(layer).draw_tile(headpos, True)
return PlacedTile(level, changes, geochanges)
[docs]
def remove_tile(level: RWELevel, pos: QPoint, layer: int, strict=True):
tile = level.l_tiles[pos][layer]
if tile is None:
return None
if isinstance(tile, PlacedMaterial):
tile.remove(level, True)
return RemovedTile([[copy_tile(tile), None]], level)
headpos = tile.headpos if isinstance(tile, PlacedTileBody) else pos
headlayer = tile.headlayer if isinstance(tile, PlacedTileBody) else layer
headtile = level.l_tiles[headpos][headlayer]
if not isinstance(headtile, PlacedTileHead):
return None
changes = []
tilesize = headtile.tile.size
tl = headpos - headtile.tile.top_left
for x in range(tl.x(), tl.x() + tilesize.width()):
for y in range(tl.y(), tl.y() + tilesize.height()):
# searching for tilebodies
tb = level.l_tiles[QPoint(x, y)][layer]
if QPoint(x, y) == headpos and isinstance(tb, PlacedTileHead):
tb.remove_graphics(level, True)
changes.append([copy_tile(tb), None])
level.l_tiles[QPoint(x, y)][layer] = None
continue
if not isinstance(tb, PlacedTileBody):
continue
if strict and tb.headpos != headpos:
continue
changes.append([copy_tile(tb), None])
level.l_tiles[QPoint(x, y)][layer] = None
if not headtile.tile.multilayer or layer >= 2:
return RemovedTile(changes, level)
for x in range(tl.x(), tl.x() + tilesize.width()):
for y in range(tl.y(), tl.y() + tilesize.height()):
# searching for tilebodies
tb = level.l_tiles[QPoint(x, y)][layer + 1]
if not isinstance(tb, PlacedTileBody):
continue
if strict and tb.headpos != headpos:
continue
changes.append([copy_tile(tb), None])
level.l_tiles[QPoint(x, y)][layer + 1] = None
return RemovedTile(changes, level)
[docs]
class BaseTileChangelist:
[docs]
def __init__(self, changes, level: RWELevel):
self.changes = changes # [before, after]
self.level = level
self.module = level.viewport.modulenames["tiles"]
[docs]
def undo(self):
layers2redraw = [False, False, False]
for i in self.changes:
before: PlacedTileHead | PlacedTileBody | PlacedMaterial | None = i[0]
after: PlacedTileHead | PlacedTileBody | PlacedMaterial | None = i[1]
if before is None:
if after is None:
continue
pos = after.pos
layer = after.layer
if self.level.l_tiles[pos][layer] is not None:
self.level.l_tiles[pos][layer].remove_graphics(self.level, False)
self.level.l_tiles[pos][layer] = None
layers2redraw[layer] = True
continue
pos = before.pos
layer = before.layer
if self.level.l_tiles[pos][layer] is not None:
self.level.l_tiles[pos][layer].remove_graphics(self.level, False)
self.level.l_tiles[pos][layer] = before.copy()
self.level.l_tiles[pos][layer].add_graphics(self.level, False)
layers2redraw[layer] = True
[self.module.get_layer(i).redraw() for i in layers2redraw if i]
[docs]
def redo(self):
layers2redraw = [False, False, False]
for i in self.changes:
before: PlacedTileHead | PlacedTileBody | PlacedMaterial | None = i[0]
after: PlacedTileHead | PlacedTileBody | PlacedMaterial | None = i[1]
if after is None:
if before is None:
continue
pos = before.pos
layer = before.layer
if self.level.l_tiles.tiles[pos.x()][pos.y()][layer] is not None:
self.level.l_tiles.tiles[pos.x()][pos.y()][layer].remove_graphics(self.level, False)
self.level.l_tiles.tiles[pos.x()][pos.y()][layer] = None
layers2redraw[layer] = True
continue
pos = after.pos
layer = after.layer
if self.level.l_tiles[pos][layer] is not None:
self.level.l_tiles[pos][layer].remove_graphics(self.level, False)
self.level.l_tiles.tiles[pos.x()][pos.y()][layer] = after.copy()
self.level.l_tiles.tiles[pos.x()][pos.y()][layer].add_graphics(self.level, False)
layers2redraw[layer] = True
[self.module.get_layer(i).redraw() for i in layers2redraw if i]
[docs]
class PlacedTile(BaseTileChangelist):
[docs]
def __init__(self, level, changes, geochanges=None):
super().__init__(changes, level)
if geochanges is None:
geochanges = []
self.geochanges = geochanges # [pos, layer, after, before]
[docs]
def undo(self):
super().undo()
for i in self.geochanges:
self.level.l_geo.blocks[i[0].x(), i[0].y(), i[1]] = np.uint8(i[2])
# element.history.level.data["GE"][i[0].x()][i[0].y()][i[1]][0] = i[3]
self.level.viewport.modulenames["geo"].get_layer(i[1]).draw_geo(i[0].x(), i[0].y(), True)
[docs]
def redo(self):
super().redo()
for i in self.geochanges:
self.level.history.level.l_geo.blocks[i[0].x(), i[0].y(), i[1]] = np.uint8(i[3])
# element.history.level.data["GE"][i[0].x()][i[0].y()][i[1]][0] = i[2]
self.level.viewport.modulenames["geo"].get_layer(i[1]).draw_geo(i[0].x(), i[0].y(), True)
[docs]
class RemovedTile(BaseTileChangelist):
pass
[docs]
class TileHistory(HistoryElement):
[docs]
def __init__(self, history, tile: Tile, layer: int, force_place=False, force_geometry=False):
super().__init__(history)
self.area = np.ones([self.history.level.level_width, self.history.level.level_height], np.bool)
self.area2 = np.ones([self.history.level.level_width, self.history.level.level_height], np.bool)
self.layer = layer
self.tile = tile
self.savedtiles: list[BaseTileChangelist] = []
self.fp = force_place
self.fg = force_geometry
self.tilemodule = self.level.viewport.modulenames["tiles"]
self.geomodule = self.level.viewport.modulenames["geo"]
[docs]
def redraw(self):
self.tilemodule.get_layer(self.layer).redraw()
if self.fg:
self.geomodule.get_layer(self.layer).redraw()
if self.layer < 2:
if self.fg:
self.geomodule.get_layer(self.layer + 1).redraw()
self.tilemodule.get_layer(self.layer + 1).redraw()
[docs]
def undo_changes(self):
for i in self.savedtiles:
i.undo()
self.redraw()
[docs]
def redo_changes(self):
for i in self.savedtiles:
i.redo()
self.redraw()