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

Look at items and NPCs #33

Merged
merged 13 commits into from
Jan 1, 2024
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ about but haven't gotten the professional opportunity to try.

## Current state:

### 2024-01-01
* Happy new year!
* Added the ability to `look <target>` at items in the current room, player inventory,
or NPCs in the current room, to see their descriptions.

### 2023-09-06
* Added basic, stationary NPCs. They can't be interacted with yet, but soon enough.
* Added the ability to create item and NPC spawning rules, to populate specific rooms
Expand Down
23 changes: 23 additions & 0 deletions cibo/actions/__action__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from cibo.password import Password
from cibo.resources.doors import Doors
from cibo.resources.items import Items
from cibo.resources.npcs import Npcs
from cibo.resources.resources import Resources
from cibo.resources.rooms import Rooms


Expand All @@ -28,6 +30,17 @@ def __init__(self, server_config: ServerConfig) -> None:

self._password_hasher = Password()

@property
def resources(self) -> Resources:
"""Resource helper methods that aren't necesarily associated with just one
resource type.

Returns:
Resources: The helper methods.
"""

return self._world.resources

@property
def rooms(self) -> Rooms:
"""All the rooms in the world.
Expand Down Expand Up @@ -57,6 +70,16 @@ def items(self) -> Items:

return self._world.items

@property
def npcs(self) -> Npcs:
"""All the NPCs in the world.

Returns:
Npcs: The NPCs.
"""

return self._world.npcs

@property
def output(self) -> Output:
"""Access the output formatter, to send messages to clients.
Expand Down
2 changes: 1 addition & 1 deletion cibo/actions/commands/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def get_formatted_inventory(self, client: Client) -> str:
"""

inventory_items = [
self.items.get_by_id(item.item_id).name for item in client.player.inventory
item.name for item in self.items.get_from_dataset(client.player.inventory)
]

inventory = "\n".join([str(item).capitalize() for item in inventory_items])
Expand Down
84 changes: 59 additions & 25 deletions cibo/actions/commands/look.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

from cibo.actions.__action__ import Action
from cibo.client import Client
from cibo.exception import ClientNotLoggedIn, RoomNotFound
from cibo.models.data.item import Item
from cibo.models.data.npc import Npc
from cibo.exception import ActionMissingArguments, ClientNotLoggedIn
from cibo.models.data.item import Item as ItemData
from cibo.models.data.npc import Npc as NpcData
from cibo.models.item import Item
from cibo.models.npc import Npc
from cibo.models.room import Room


Expand All @@ -21,7 +23,7 @@ def aliases(self) -> List[str]:
def required_args(self) -> List[str]:
return []

def room_desc_message(self, room: Room, client: Client) -> Panel:
def room_desc_message(self, client: Client, room: Room) -> Panel:
"""A stylized description of the room, including its exits and occupants."""

items = self.get_formatted_items(client)
Expand All @@ -41,9 +43,28 @@ def room_desc_message(self, room: Room, client: Client) -> Panel:
padding=(1, 4),
)

def _get_player_occupants(self, client: Client) -> List[str]:
def resource_desc_message(self, client: Client, args: List[str]) -> str:
"""A stylized description of an item in the room, an item in the player's
inventory, or an NPC in the room. In that order.
"""

resource = self.resources.get_by_name(
(
self._get_room_items(client)
+ self._get_player_items(client)
+ self._get_npc_occupants(client)
),
args[0],
)

if resource:
return f"You look at {resource.name}:\n\n {resource.description.look}"

return "You don't see that..."

def _get_player_occupants(self, client: Client) -> List[Client]:
return [
f"[cyan]{occupant_client.player.name}[/] is standing here."
occupant_client
for occupant_client in self._telnet.get_connected_clients()
if (
occupant_client.player
Expand All @@ -54,17 +75,18 @@ def _get_player_occupants(self, client: Client) -> List[str]:
)
]

def _get_npc_occupants(self, client: Client) -> List[str]:
return [
self._world.npcs.get_by_id(npc.npc_id).room_description
for npc in Npc.get_by_current_room_id(client.player.current_room_id)
]
def _get_npc_occupants(self, client: Client) -> List[Npc]:
return self.npcs.get_from_dataset(
NpcData.get_by_current_room_id(client.player.current_room_id)
)

def _get_room_items(self, client: Client) -> List[str]:
return [
self.items.get_by_id(item.item_id).room_description
for item in Item.get_by_current_room_id(client.player.current_room_id)
]
def _get_room_items(self, client: Client) -> List[Item]:
return self.items.get_from_dataset(
ItemData.get_by_current_room_id(client.player.current_room_id)
)

def _get_player_items(self, client: Client) -> List[Item]:
return self.items.get_from_dataset(client.player.inventory)

def get_formatted_occupants(self, client: Client) -> str:
"""Formats and lists out all occupants of the client's current room, excluding
Expand All @@ -78,8 +100,13 @@ def get_formatted_occupants(self, client: Client) -> str:
str: The occupants for the room.
"""

player_occupants = self._get_player_occupants(client)
npc_occupants = self._get_npc_occupants(client)
player_occupants = [
f"[cyan]{occupant_client.player.name}[/] is standing here."
for occupant_client in self._get_player_occupants(client)
]
npc_occupants = [
npc.room_description for npc in self._get_npc_occupants(client)
]
combined_occupants = player_occupants + npc_occupants

joined_occupants = "\n• ".join(
Expand All @@ -105,24 +132,31 @@ def get_formatted_items(self, client: Client) -> str:
str: The individual items the room contains.
"""

room_items = self._get_room_items(client)
room_items = [item.room_description for item in self._get_room_items(client)]

joined_items = "\n• ".join([str(item) for item in room_items])

return f"\n• [bright_blue]{joined_items}[/]" if len(room_items) > 0 else ""

def process(self, client: Client, _command: Optional[str], args: List[str]) -> None:
try:
if not client.is_logged_in or args:
if not client.is_logged_in:
raise ClientNotLoggedIn

# the player is just looking at the room in general
room = self.rooms.get_by_id(client.player.current_room_id)
if not args:
raise ActionMissingArguments

except (ClientNotLoggedIn, RoomNotFound):
self.output.send_private_message(
client, self.resource_desc_message(client, args)
)

except ClientNotLoggedIn:
self.output.send_prompt(client)

else:
except ActionMissingArguments:
# the player is just looking at the room in general
room = self.rooms.get_by_id(client.player.current_room_id)

self.output.send_private_message(
client, self.room_desc_message(room, client)
client, self.room_desc_message(client, room)
)
6 changes: 4 additions & 2 deletions cibo/actions/commands/open.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def opening_door_message(self, player_name: str, door_name: str) -> Announcement
f"{door_name.capitalize()} opens.",
)

def door_is_open(self, door_name: str) -> str:
def door_is_open_message(self, door_name: str) -> str:
"""The door is already open."""

return f"{door_name.capitalize()} is already open."
Expand Down Expand Up @@ -93,7 +93,9 @@ def process(self, client: Client, _command: str, args: List[str]) -> None:
)

except DoorIsOpen:
self.output.send_private_message(client, self.door_is_open(door.name))
self.output.send_private_message(
client, self.door_is_open_message(door.name)
)

except DoorIsClosed:
door.open_()
Expand Down
2 changes: 1 addition & 1 deletion cibo/resources/__resource__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Resource(ABC):
"""An object that exists in the world."""

def _generate_resources(self, filename: str) -> list:
"""Generate all the resources, from the local JSON file that houses them,
"""Generate all the resources, from the local JSON file that houses them.

Returns:
List[Room]: All the resources.
Expand Down
14 changes: 14 additions & 0 deletions cibo/resources/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import List

from cibo.exception import ItemNotFound
from cibo.models.data.item import Item as ItemData
from cibo.models.description import EntityDescription
from cibo.models.item import Item
from cibo.resources.__resource__ import Resource
Expand Down Expand Up @@ -51,3 +52,16 @@ def get_by_id(self, id_: int) -> Item:
return item

raise ItemNotFound

def get_from_dataset(self, items_dataset: List[ItemData]) -> List[Item]:
"""Compiles a list of items, using the IDs from a set of corresponding
item data models.

Args:
items_dataset (List[ItemData]): The set of item data models.

Returns:
List[Item]: The compiled list of items.
"""

return [self.get_by_id(item.item_id) for item in items_dataset]
14 changes: 14 additions & 0 deletions cibo/resources/npcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import List

from cibo.exception import NpcNotFound
from cibo.models.data.npc import Npc as NpcData
from cibo.models.description import EntityDescription
from cibo.models.npc import Npc
from cibo.resources.__resource__ import Resource
Expand Down Expand Up @@ -48,3 +49,16 @@ def get_by_id(self, id_: int) -> Npc:
return npc

raise NpcNotFound

def get_from_dataset(self, npcs_dataset: List[NpcData]) -> List[Npc]:
"""Compiles a list of NPCs, using the IDs from a set of corresponding
NPC data models.

Args:
npcs_dataset (List[NpcData]): The set of NPC data models.

Returns:
List[Npc]: The compiled list of NPCs.
"""

return [self.get_by_id(npc.npc_id) for npc in npcs_dataset]
61 changes: 61 additions & 0 deletions cibo/resources/resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Resource helper and batch methods that aren't necesarily associated with just one
resource type.
"""

from typing import List, Optional, Union

from cibo.models.item import Item
from cibo.models.npc import Npc


class Resources:
"""Resource helper and batch methods that aren't necesarily associated with just
one resource type.
"""

def get_by_name(
self, resources: List[Union[Item, Npc]], sub: str
) -> Optional[Union[Item, Npc]]:
"""Finds a resource in a list of resources, with a name that matches or
contains the provided substring. If the substring starts with a number
followed by a period, that number is used as an index assuming there are
multiple matches.


Args:
resources (List[Union[Item, Npc]]): The resources to search against.
sub (str): What to search for in the resource name.

Returns:
Optional[Union[Item, Npc]]: The matching resource, if one is found.
"""

_sub = sub
sub_segments = sub.split(".")

# after splitting on periods in the substring, if the first character is a
# number, we want to be able to target that specific index in the resources
# list
if sub_segments[0].isdigit():
# a specified index of 0 (zero) returns None -- see the next comment below
if len(sub_segments) > 1 and int(sub_segments[0]) > 0:
_sub = sub_segments[1]
else:
return None

results = [resource for resource in resources if _sub in resource.name]

if not results:
return None

if sub_segments[0].isdigit():
try:
# we expect the initial index to be 1 (not zero) because it's
# more intuitive from a user perspective, so we have to decrease it
# here by 1 to be accurate against our list
return results[(int(sub_segments[0]) - 1)]

except IndexError:
return None

return results[0]
3 changes: 3 additions & 0 deletions cibo/resources/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from cibo.resources.items import Items
from cibo.resources.npcs import Npcs
from cibo.resources.regions import Regions
from cibo.resources.resources import Resources
from cibo.resources.rooms import Rooms
from cibo.resources.sectors import Sectors
from cibo.resources.spawns import Spawns
Expand All @@ -20,6 +21,8 @@ class World:
"""

def __init__(self) -> None:
self.resources = Resources()

self.regions = Regions(getenv("REGIONS_PATH", "/cibo/config/regions.json"))
self.sectors = Sectors(
getenv("SECTORS_PATH", "/cibo/config/sectors.json"), self.regions
Expand Down
Loading