Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Item inventory features #21

Merged
merged 13 commits into from
Apr 15, 2023
133 changes: 129 additions & 4 deletions game/common/avatar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,100 @@


class Avatar(GameObject):
def __init__(self, item: Item | None = None, position: Vector | None = None):
"""
Notes for the inventory:

The avatar's inventory is a list of items. Each item has a quantity and a stack_size (the max amount of an
item that can be held in a stack. Think of the Minecraft inventory).

This upcoming example is just to facilitate understanding the concept. The Dispensing Station concept that will
be mentioned is completely optional to implement if you desire. The Dispensing Station is used to help with the
explanation.

----------------------------------------------------------------------------------------------------------------

Items:
Every Item has a quantity and a stack_size. The quantity is how much of the Item the player *currently* has.
The stack_size is the max of that Item that can be in a stack. For example, if the quantity is 5, and the
stack_size is 5 (5/5), the item cannot be added to that stack

Picking up items:
Example 1:
When you pick up an item (which will now be referred to as picked_up_item), picked_up_item has a given
quantity. In this case, let's say the quantity of picked_up_item is 2.

Imagine you already have this item in your inventory (which will now be referred to as inventory_item),
and inventory_item has a quantity of 1 and a stack_size of 10 (think of this as a fraction: 1/10).

When you pick up picked_up_item, inventory_item will be checked.
If picked_up_item's quantity + inventory_item < stack_size, it'll be added without issue.
Remember, for this example: picked_up_item quantity is 2, and inventory_item quantity is 1, and stack_size
is 10.
Inventory_item quantity before picking up: 1/10
2 + 1 < 10 --> True
Inventory_item quantity after picking up: 3/10

----------------------------------------------------------------------------------------------------------------

Example 2:
For the next two examples, the total inventory size will be considered.

Let's say inventory_item has quantity 4 and a stack_size of 5. Now say that picked_up_item has quantity 3.
Recall: if picked_up_item's quantity + inventory_item < stack_size, it will be added without issue
Inventory_item quantity before picking up: 4/5
3 + 4 < 5 --> False

What do we do in this situation? If you want to add picked_up_item to inventory_item and there is an
overflow of quantity, that is handled for you.

Let's say that your inventory size (which will now be referred to as max_inventory_size) is 5. You already
have inventory_item in there that has a quantity of 4 and a stack_size of 5. An image of the inventory is
below. 'None' is used to help show the max_inventory_size. Inventory_item quantity and stack_size will be
listed in parentheses as a fraction.
Inventory:
[inventory_item (4/5), None, None, None, None]

Now we will add picked_up_item and its quantity of 3:
Inventory before:
[inventory_item (4/5), None, None, None, None]

3 + 4 < 5 --> False
inventory_item (4/5) will now be inventory_item (5/5)
picked_up_item now has a quantity of 2
Since we have a surplus, we will append the same item with a quantity of 2 in the inventory.

The result is:
[inventory_item (5/5), inventory_item (2/5), None, None, None]

----------------------------------------------------------------------------------------------------------------
Example 3:

For this last example, assume your inventory looks like this:
[inventory_item (5/5), inventory_item (5/5) inventory_item (5/5) inventory_item (5/5), inventory_item (4/5)]

You can only fit one more inventory_item into the last stack before the inventory is full.
Let's say that picked_up_item has quantity of 3 again.

Inventory before:
[inventory_item (5/5), inventory_item (5/5) inventory_item (5/5) inventory_item (5/5), inventory_item (4/5)]
3 + 4 < 5 --> False
inventory_item (4/5) will now be inventory_item (5/5)
picked_up_item now has a quantity of 2
However, despite the surplus, we cannot add it into our inventory, so the remaining quantity of
picked_up_item is left where it was first found.
Inventory after:
[inventory_item (5/5), inventory_item (5/5) inventory_item (5/5) inventory_item (5/5), inventory_item (5/5)]
"""

def __init__(self, item: Item | None = None, position: Vector | None = None, inventory: list[Item] = [],
max_inventory_size: int = 10):
super().__init__()
self.object_type: ObjectType = ObjectType.AVATAR
self.held_item: Item | None = item
self.score: int = 0
self.position: Vector | None = position
self.max_inventory_size = max_inventory_size
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved
self.inventory: list[Item] = inventory

@property
def held_item(self) -> Item | None:
Expand All @@ -25,6 +113,14 @@ def score(self) -> int:
def position(self) -> Vector | None:
return self.__position

@property
def inventory(self) -> list[Item]:
return self.__inventory

@property
def max_inventory_size(self) -> int:
return self.__max_inventory_size

@held_item.setter
def held_item(self, item: Item | None) -> None:
# If it's not an item, and it's not None, raise the error
Expand All @@ -40,30 +136,59 @@ def score(self, score: int) -> None:

@position.setter
def position(self, position: Vector | None) -> None:
if position is not None and not(isinstance(position, Vector) and list(map(type, position)) == Vector):
if position is not None and not isinstance(position, Vector):
raise ValueError(f"{self.__class__.__name__}.position must be a Vector or None.")
self.__position = position

@inventory.setter
def inventory(self, inventory: list[Item]) -> None:
if inventory is None or not isinstance(inventory, list) \
or (len(inventory) > 0 and any(map(lambda item: not isinstance(item, Item), inventory))):
raise ValueError(f"{self.__class__.__name__}.inventory must be a list of Items.")
if len(inventory) > self.max_inventory_size:
raise ValueError(f"{self.__class__.__name__}.inventory size must be less than max_inventory_size")
self.__inventory = inventory

@max_inventory_size.setter
def max_inventory_size(self, size: int) -> None:
if size is None or not isinstance(size, int):
raise ValueError(f"{self.__class__.__name__}.max_inventory_size must be an int.")
self.__max_inventory_size = size

def pick_up(self, item: Item) -> Item | None:
t = item
[t := i.pick_up(t) for i in self.inventory]

if t is not None and len(self.inventory) < self.max_inventory_size:
self.inventory.append(t)
return None

return t

def to_json(self) -> dict:
data: dict = super().to_json()
data['held_item'] = self.held_item.to_json() if self.held_item is not None else None
data['score'] = self.score
data['position'] = self.position
data['inventory'] = self.inventory
data['max_inventory_size'] = self.max_inventory_size
return data

def from_json(self, data: dict) -> Self:
super().from_json(data)
self.score: int = data['score']
self.position: Vector | None = data['position']
self.inventory: list[Item] = data['inventory']
self.max_inventory_size: int = data['max_inventory_size']
held_item: Item | None = data['held_item']
if held_item is None:
self.held_item = None
return self

match held_item['object_type']:
case ObjectType.ITEM:
self.held_item = Item().from_json(data['held_item'])
case _:
raise ValueError(f"{self.__class__.__name__}.held_item needs to be an item.")

return self
16 changes: 15 additions & 1 deletion game/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,18 @@ class ActionType(Enum):
INTERACT_DOWN = auto()
INTERACT_LEFT = auto()
INTERACT_RIGHT = auto()
INTERACT_CENTER = auto()
INTERACT_CENTER = auto()
SELECT_SLOT_0 = auto()
SELECT_SLOT_1 = auto()
SELECT_SLOT_2 = auto()
SELECT_SLOT_3 = auto()
SELECT_SLOT_4 = auto()
SELECT_SLOT_5 = auto()
SELECT_SLOT_6 = auto()
SELECT_SLOT_7 = auto()
SELECT_SLOT_8 = auto()
SELECT_SLOT_9 = auto()
"""
These last 10 enums are for selecting a slot from the Avatar class' inventory.
You can add/remove these as needed for the purposes of your game.
"""
53 changes: 48 additions & 5 deletions game/common/items/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@


class Item(GameObject):
def __init__(self, value: int = 1, durability: int | None = 100):
def __init__(self, value: int = 1, durability: int | None = 100, quantity: int = 1, stack_size: int = 0):
super().__init__()
self.object_type: ObjectType = ObjectType.ITEM
self.value: int = value
self.durability: int | None = durability # durability can be None if infinite durability

self.value: int = value # Value can more specified based on purpose (e.g., the sell price)
self.durability: int | None = durability # durability can be None if infinite durability
self.quantity: int = quantity # the current amount of this item
self.stack_size = stack_size # the max quantity this item can contain
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved

@property
def durability(self) -> int | None:
return self.__durability
Expand All @@ -18,8 +20,16 @@ def durability(self) -> int | None:
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:
def durability(self, durability: int | None) -> int | None:
if durability is not None and not isinstance(durability, int):
raise ValueError(f'{self.__class__.__name__}.durability must be an int or None.')
self.__durability = durability
Expand All @@ -30,14 +40,47 @@ def value(self, value: int) -> None:
raise ValueError(f'{self.__class__.__name__}.value must be an int.')
self.__value = value

@quantity.setter
def quantity(self, quantity: int) -> int:
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved

if quantity is None or not isinstance(quantity, int):
raise ValueError(f'{self.__class__.__name__}.quantity must be an int.')
if quantity < 0:
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(f'{self.__class__.__name__}.quantity must be greater than 0.')
self.__quantity = quantity

@stack_size.setter
def stack_size(self, stack_size: int) -> int:
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved
if stack_size is not isinstance(stack_size, int) or stack_size < self.quantity:
raise ValueError(f'{self.__class__.__name__}.stack_size must be an int and greater than the quantity.')
self.__stack_size = stack_size

def pick_up(self, item: Self) -> Self | 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
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved
self.quantity = self.stack_size
return item

# Add the given item's quantity to the self item
self.quantity += item.quantity

def to_json(self) -> dict:
data: dict = super().to_json()
data['durability'] = self.durability
data['value'] = self.value
data['quantity'] = self.quantity
data['stack_size'] = self.stack_size
return data

def from_json(self, data: dict) -> Self:
super().from_json(data)
self.durability: int | None = data['durability']
self.value: int = data['value']
self.quantity: int = data['quantity']
self.stack_size: int = data['stack_size']
return self
41 changes: 41 additions & 0 deletions game/controllers/inventory_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from game.common.enums import *
from game.common.avatar import Avatar
from game.common.items.item import Item
from game.common.player import Player
from game.controllers.controller import Controller
from game.common.map.game_board import GameBoard


class InventoryController(Controller):
def __init__(self):
super().__init__()

def handle_actions(self, client: Player, world: GameBoard):
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved
# If a larger inventory is created, create more enums and add them here as needed
item: Item
avatar: Avatar = Player.avatar
match client.action:
KingPhilip14 marked this conversation as resolved.
Show resolved Hide resolved
case ActionType.SELECT_SLOT_0:
item = avatar.inventory[0]
case ActionType.SELECT_SLOT_1:
item = avatar.inventory[1]
case ActionType.SELECT_SLOT_2:
item = avatar.inventory[2]
case ActionType.SELECT_SLOT_3:
item = avatar.inventory[3]
case ActionType.SELECT_SLOT_4:
item = avatar.inventory[4]
case ActionType.SELECT_SLOT_5:
item = avatar.inventory[5]
case ActionType.SELECT_SLOT_6:
item = avatar.inventory[6]
case ActionType.SELECT_SLOT_7:
item = avatar.inventory[7]
case ActionType.SELECT_SLOT_8:
item = avatar.inventory[8]
case ActionType.SELECT_SLOT_9:
item = avatar.inventory[9]
case _: # default case if it's not selecting an inventory slot
return

avatar.held_item = item