128 lines
4.1 KiB
Python
128 lines
4.1 KiB
Python
# Copyright 2023 <omicron.me@protonmail.com>
|
|
# Copyright 2023 <andreasruden91@gmail.com>
|
|
#
|
|
# This file is part of d2warehouse.
|
|
#
|
|
# d2warehouse is free software: you can redistribute it and/or modify it under the
|
|
# terms of the GNU General Public License as published by the Free Software
|
|
# Foundation, either version 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# d2warehouse is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# Mercator. If not, see <https://www.gnu.org/licenses/>.
|
|
from d2warehouse.item import Item
|
|
from d2warehouse.fileformat import STASH_TAB_MAGIC, ITEM_DATA_MAGIC
|
|
from struct import pack
|
|
|
|
|
|
class StashError(RuntimeError):
|
|
pass
|
|
|
|
|
|
class StashFullError(StashError):
|
|
pass
|
|
|
|
|
|
class Stash:
|
|
def __init__(self) -> None:
|
|
self.tabs: list[StashTab] = []
|
|
|
|
def raw(self) -> bytes:
|
|
return b"".join(tab.raw() for tab in self.tabs)
|
|
|
|
def add(self, item: Item) -> None:
|
|
for tab in self.tabs:
|
|
try:
|
|
tab.add(item)
|
|
return
|
|
except StashFullError:
|
|
pass
|
|
raise StashFullError(
|
|
"Could not locate an open spot in the stash to add the item"
|
|
)
|
|
|
|
|
|
class StashTab:
|
|
def __init__(self) -> None:
|
|
self.gold: int = 0
|
|
self.version: int = 99
|
|
self.item_data: bytes = b""
|
|
self.items: list[Item] = []
|
|
|
|
def raw(self) -> bytes:
|
|
"""Get the computed raw representation of the stash"""
|
|
item_raw = b"".join(item.raw() for item in self.items)
|
|
raw_length = len(item_raw) + 0x44
|
|
return (
|
|
STASH_TAB_MAGIC
|
|
+ pack("I", 1)
|
|
+ pack("I", self.version)
|
|
+ pack("I", self.gold)
|
|
+ pack("I", raw_length)
|
|
+ b"\x00" * 44
|
|
+ ITEM_DATA_MAGIC
|
|
+ pack("H", len(self.items))
|
|
+ item_raw
|
|
)
|
|
|
|
def remove(self, item: Item) -> None:
|
|
for idx, current_item in enumerate(self.items):
|
|
if item is current_item:
|
|
self.items.pop(idx)
|
|
return
|
|
raise StashError("Item not found in stash")
|
|
|
|
def add(self, item: Item) -> None:
|
|
width, height = item.size()
|
|
dest = self._find_destination(width, height)
|
|
if not dest:
|
|
raise StashFullError(
|
|
"Could not locate an open spot in the stash to add the item"
|
|
)
|
|
x, y = dest
|
|
item.set_position(x, y)
|
|
self.items.append(item)
|
|
|
|
@staticmethod
|
|
def _is_free_location(
|
|
slots: list[list[bool]], x: int, y: int, width: int, height: int
|
|
) -> bool:
|
|
"""Check if a given location can fit an item of a given width and height"""
|
|
if x + width > len(slots):
|
|
return False
|
|
if y + height > len(slots[0]):
|
|
return False
|
|
for i in range(width):
|
|
for j in range(height):
|
|
if slots[x + i][y + j]:
|
|
return False
|
|
return True
|
|
|
|
def _find_destination(self, width: int, height: int) -> tuple[int, int] | None:
|
|
"""Find a free destination to store an item of given width and height"""
|
|
slots = self._build_slot_matrix()
|
|
stash_width = len(slots)
|
|
stash_height = len(slots[0])
|
|
for y in range(stash_width):
|
|
for x in range(stash_height):
|
|
if self._is_free_location(slots, x, y, width, height):
|
|
return x, y
|
|
return None
|
|
|
|
def _build_slot_matrix(self, rows: int = 10, cols: int = 10) -> list[list[bool]]:
|
|
"""Build a matrix of filled and empty slots for the stash tab."""
|
|
slots = []
|
|
for i in range(cols):
|
|
slots.append([False] * rows)
|
|
for item in self.items:
|
|
x, y = item.pos_x, item.pos_y
|
|
width, height = item.size()
|
|
for n in range(width):
|
|
for m in range(height):
|
|
slots[x + n][y + m] = True
|
|
return slots
|