from game.common.enums import ObjectType
from game.common.game_object import GameObject
from typing import Self
[docs]class Item(GameObject):
"""
`Item Class Notes:`
Items have 4 different attributes: value, durability, quantity, stack-size.
-----
Value:
The value of an item can be anything depending on the purpose of the game. For example, if a player can
sell an item to a shop for monetary value, the value attribute would be used. However, this value may be
used for anything to better fit the purpose of the game being created.
-----
Durability:
The value of an item's durability can be either None or an integer.
If durability is an integer, the stack size *must* be 1. Think of Minecraft and the mechanics of tools. You
can only have one sword with a durability (they don't stack).
If the durability is equal to None, it represents the item having infinite durability. The item can now be
stacked with others of the same type. In the case the game being created needs items without durability,
set this attribute to None to prevent any difficulties.
-----
Quantity and Stack Size:
These two work in tandem. Fractions (x/y) will be used to better explain the concept.
Quantity simply refers to the amount of the current item the player has. For example, having 5 gold, 10
gold, or 0 gold all work for representing the quantity.
The stack_size represents how *much* of an item can be in a stack. Think of this like the Minecraft inventory
system. For example, you can have 64 blocks of dirt, and if you were to gain one more, you would have a new
stack starting at 1.
In the case of items here, it works with the avatar.py file's inventory system (refer to those notes on how
the inventory works in depth). The Minecraft analogy should help understand the concept.
To better show this, quantity and stack size work like the following fraction model: quantity/stack_size.
The quantity can never be 0 and must always be a minimum of 1. Furthermore, quantity cannot be greater than
the stack_size. So 65/64 will not be possible.
-----
Pick Up Method:
When picking up an item, it will first check the item's ObjectType. If the Object interacted with is not of
the ObjectType ITEM enum, an error will be thrown.
Otherwise, the method will check if the picked up item will be able to fit in the inventory. If some of the
item's quantity can be picked up and a surplus will be left over, the player will be pick up what they can.
Then, the same item they picked up will have its quantity modified to be what is left over. That surplus of
the item is then returned to allow for it to be placed back where it was found on the map.
If the player can pick up an item without any inventory issues, the entire item and its quantity will be added.
**Refer to avatar.py for a more in-depth explanation of how picking up items work with examples.**
"""
def __init__(self, value: int = 1, durability: int | None = None, quantity: int = 1, stack_size: int = 1):
super().__init__()
self.__quantity = None # This is here to prevent an error
self.__durability = None # This is here to prevent an error
self.object_type: ObjectType = ObjectType.ITEM
self.value: int = value # Value can more specified based on purpose (e.g., the sell price)
self.stack_size: int = stack_size # the max quantity this item can contain
self.durability: int | None = durability # durability can be None to represent infinite durability
self.quantity: int = quantity # the current amount of this item
@property
def durability(self) -> int | None:
return self.__durability
@property
def value(self) -> int:
return self.__value
@property
def quantity(self) -> int:
return self.__quantity
@property
def stack_size(self) -> int:
return self.__stack_size
@durability.setter
def durability(self, durability: int | None) -> None:
if durability is not None and not isinstance(durability, int):
raise ValueError(
f'{self.__class__.__name__}.durability must be an int. It is a(n) {durability.__class__.__name__} with the value of {durability}.')
if durability is not None and self.stack_size != 1:
raise ValueError(
f'{self.__class__.__name__}.durability must be set to None if stack_size is not equal to 1.'
f' {self.__class__.__name__}.durability has the value of {durability}.')
self.__durability = durability
@value.setter
def value(self, value: int) -> None:
if value is None or not isinstance(value, int):
raise ValueError(
f'{self.__class__.__name__}.value must be an int.'
f' It is a(n) {value.__class__.__name__} with the value of {value}.')
self.__value: int = value
@quantity.setter
def quantity(self, quantity: int) -> None:
if quantity is None or not isinstance(quantity, int):
raise ValueError(
f'{self.__class__.__name__}.quantity must be an int.'
f' It is a(n) {quantity.__class__.__name__} with the value of {quantity}.')
if quantity < 0:
raise ValueError(
f'{self.__class__.__name__}.quantity must be greater than or equal to 0.'
f' {self.__class__.__name__}.quantity has the value of {quantity}.')
# The self.quantity is set to the lower value between stack_size and the given quantity
# The remaining given quantity is returned if it's larger than self.quantity
if quantity > self.stack_size:
raise ValueError(f'{self.__class__.__name__}.quantity cannot be greater than '
f'{self.__class__.__name__}.stack_size.'
f' {self.__class__.__name__}.quantity has the value of {quantity}.')
self.__quantity: int = quantity
@stack_size.setter
def stack_size(self, stack_size: int) -> None:
if stack_size is None or not isinstance(stack_size, int):
raise ValueError(
f'{self.__class__.__name__}.stack_size must be an int.'
f' It is a(n) {stack_size.__class__.__name__} with the value of {stack_size}.')
if self.durability is not None and stack_size != 1:
raise ValueError(f'{self.__class__.__name__}.stack_size must be 1 if {self.__class__.__name__}.durability '
f'is not None. {self.__class__.__name__}.stack_size has the value of {stack_size}.')
if self.__quantity is not None and stack_size < self.__quantity:
raise ValueError(
f'{self.__class__.__name__}.stack_size must be greater than or equal to the quantity.'
f' {self.__class__.__name__}.stack_size has the value of {stack_size}.')
self.__stack_size: int = stack_size
[docs] def take(self, item: Self) -> Self | None:
if item is not None and not isinstance(item, Item):
raise ValueError(
f'{item.__class__.__name__}.item must be an item.'
f' It is a(n) {item.__class__.__name__} with the value of {item}.')
# If the item is None, just return None
if item is None:
return None
# If the items don't match, return the given item without modifications
if self.object_type != item.object_type:
return item
# If subtracting the given item's quantity from self's quantity is less than 0, raise an error
if self.quantity - item.quantity < 0:
item.quantity -= self.quantity
self.quantity = 0
return item
# Reduce self.quantity
self.quantity -= item.quantity
item.quantity = 0
return None
[docs] def pick_up(self, item: Self) -> Self | None:
if item is not None and not isinstance(item, Item):
raise ValueError(
f'{item.__class__.__name__}.item must be an item.'
f' It is a(n) {item.__class__.__name__} with the value of {item}.')
# If the item is None, just return None
if item is None:
return None
# If the items don't match, return the given item without modifications
if self.object_type != item.object_type:
return item
# If the picked up quantity goes over the stack_size, add to make the quantity equal the stack_size
if self.quantity + item.quantity > self.stack_size:
item.quantity -= self.stack_size - self.quantity
self.quantity: int = self.stack_size
return item
# Add the given item's quantity to the self item
self.quantity += item.quantity
item.quantity = 0
return None
[docs] def to_json(self) -> dict:
data: dict = super().to_json()
data['stack_size'] = self.stack_size
data['durability'] = self.durability
data['value'] = self.value
data['quantity'] = self.quantity
return data
[docs] def from_json(self, data: dict) -> Self:
super().from_json(data)
self.durability: int | None = data['durability']
self.stack_size: int = data['stack_size']
self.quantity: int = data['quantity']
self.value: int = data['value']
return self