Skip to content

Commit db69806

Browse files
committed
made shopkeepers have randomly generated inventories
1 parent 56645c7 commit db69806

File tree

5 files changed

+144
-40
lines changed

5 files changed

+144
-40
lines changed

entity.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from game_map import GameMap
2222
from input_handlers import EventHandler
2323
from status_effect import StatusEffect
24+
from npc_handler import NPCHandler
2425

2526

2627
T = TypeVar("T", bound="Entity")
@@ -176,7 +177,8 @@ def __init__(
176177
color: Tuple[int, int, int] = (255, 255, 255),
177178
name: str = "<Unnamed>",
178179
inspect_message: Optional[str] = "I don't know what this thing is.",
179-
interact_input_handler_cls: Type[EventHandler]
180+
npc_handler_cls: Type[NPCHandler],
181+
input_handler_cls: Type[EventHandler],
180182
):
181183
super().__init__(
182184
x=x,
@@ -189,10 +191,16 @@ def __init__(
189191
inspect_message=inspect_message,
190192
)
191193
self.is_npc = True
192-
self.input_handler_class = interact_input_handler_cls
194+
self.npc_handler_class = npc_handler_cls
195+
self.input_handler_class = input_handler_cls
193196

194197
def init_handler(self, engine: Engine) -> None:
195-
self.input_handler = self.input_handler_class(engine)
198+
self.npc_handler = self.npc_handler_class(engine, self.input_handler_class, self)
199+
self.npc_handler.init_handler()
200+
201+
@property
202+
def input_handler(self) -> EventHandler:
203+
return self.npc_handler.input_handler
196204

197205
def interact(self) -> EventHandler:
198206
"""
@@ -258,6 +266,7 @@ def __init__(
258266
inspect_message: Optional[str] = "I don't know what this thing is.",
259267
duration: int = 0,
260268
is_permanent: bool = False,
269+
moves_around: bool = False,
261270
zone_component: type[ZoneComponent],
262271
):
263272
super().__init__(
@@ -275,24 +284,32 @@ def __init__(
275284
self.is_permanent = is_permanent
276285
self.zone_component = zone_component(self)
277286
self.zone_component.parent = self
287+
self.moves_around = moves_around
278288

279289
def on_tick_actor(self, actor: Actor) -> None:
280290
self.zone_component.on_actor_tick(actor)
281291
def on_update(self) -> None:
282-
# Pick a random direction to move to
283-
direction_x, direction_y = random.choice(
284-
[
285-
(-1, -1), # Northwest
286-
(0, -1), # North
287-
(1, -1), # Northeast
288-
(-1, 0), # West
289-
(1, 0), # East
290-
(-1, 1), # Southwest
291-
(0, 1), # South
292-
(1, 1), # Southeast
293-
]
294-
)
295-
296-
# Check if there's a walkble tile in the random direction
297-
if self.gamemap.is_walkable_tile(self.x + direction_x, self.y + direction_y):
298-
self.move(direction_x, direction_y)
292+
if self.moves_around:
293+
# Pick a random direction to move to
294+
direction_x, direction_y = random.choice(
295+
[
296+
(-1, -1), # Northwest
297+
(0, -1), # North
298+
(1, -1), # Northeast
299+
(-1, 0), # West
300+
(1, 0), # East
301+
(-1, 1), # Southwest
302+
(0, 1), # South
303+
(1, 1), # Southeast
304+
]
305+
)
306+
307+
# Check if there's a walkble tile in the random direction
308+
if self.gamemap.is_walkable_tile(self.x + direction_x, self.y + direction_y):
309+
# Check if there's not anoder zone in the random direction.
310+
if not any(
311+
entity
312+
for entity in self.gamemap.zones
313+
if entity.x == self.x + direction_x and entity.y == self.y + direction_y
314+
):
315+
self.move(direction_x, direction_y)

entity_factories.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
2+
13
from components.ai import HostileEnemy, SpawnerEnemy, ZoneSpawnerEnemy, InvisblePouncerEnemy
24
from components import consumable, equippable
35
from components.fighter import Fighter
@@ -8,6 +10,7 @@
810
from input_handlers import ShopkeepMenuEventHandler
911
from status_effect import Poisoned, Bleeding, Spored
1012
from components.zone import HydrogenSulfideGas, NitrousOxideGas, SporeAir
13+
from npc_handler import ShopKeeperHandler
1114

1215
ALL_ENTITIES = []
1316

@@ -95,15 +98,15 @@
9598
value=10,
9699
)
97100
ALL_ENTITIES.append(healing_gel)
98-
XL_healing_gel = Item(
101+
xl_healing_gel = Item(
99102
char="!",
100103
color=(0, 255, 0),
101104
name="XL Healing Gel",
102105
consumable=consumable.HealingConsumable(amount=18),
103106
inspect_message="A Extra Large healing Gel. Don't choke while gobbling it up!",
104107
value=25,
105108
)
106-
ALL_ENTITIES.append(XL_healing_gel)
109+
ALL_ENTITIES.append(xl_healing_gel)
107110
taser = Item(
108111
char="~",
109112
color=(255, 255, 0),
@@ -145,7 +148,7 @@
145148
acid_kinfe = Item(
146149
char="/", color=(102, 255, 255), name="Acid kinfe", equippable=equippable.AcidKinfe(),
147150
inspect_message="It's kinfe that's acidic. Be careful when holding it!",
148-
value=50,
151+
value=75,
149152
)
150153
ALL_ENTITIES.append(acid_kinfe)
151154
sharp_kinfe = Item(char="/", color=(102, 255, 255), name="Sharp kinfe", equippable=equippable.SharpKinfe(),
@@ -159,7 +162,7 @@
159162
name="Professional acid kinfe",
160163
equippable=equippable.ProfessionalAcidKinfe(),
161164
inspect_message="It's a acid kinfe that's been professionally made, for all kinds of industries. Very useful!",
162-
value=100,
165+
value=150,
163166
)
164167
ALL_ENTITIES.append(professional_acid_kinfe)
165168
scrap_chest_plate = Item(
@@ -198,7 +201,7 @@
198201
name="Steelpike chest plate",
199202
equippable=equippable.SteelPikeChestPlate(),
200203
inspect_message="Steel chest plate + spikes = this. Quite a good one if i say so myself!",
201-
value=120,
204+
value=140,
202205
)
203206
ALL_ENTITIES.append(steelpike_chest_plate)
204207
acid_metal_chest_plate = Item(
@@ -207,14 +210,15 @@
207210
name="Acid metal chest plate",
208211
equippable=equippable.AcidMetalChestPlate(),
209212
inspect_message="It's a chest plate made of acid metal. I don't know the chemistry of this material, but it's corrosive on touch, like a lot of acid.",
210-
value=145,
213+
value=185,
211214
)
212215
ALL_ENTITIES.append(acid_metal_chest_plate)
213216
shopkeep_npc = NPC(
214217
char="@",
215218
color=(102, 255, 102),
216219
name="Shop Keeper",
217-
interact_input_handler_cls=ShopkeepMenuEventHandler,
220+
npc_handler_cls=ShopKeeperHandler,
221+
input_handler_cls=ShopkeepMenuEventHandler,
218222
inspect_message="It's a shop keeper. He's a friendly humanoid robot. He's selling some items. At least some freind in this world.",
219223
)
220224
ALL_ENTITIES.append(shopkeep_npc)
@@ -226,6 +230,7 @@
226230
duration=10,
227231
is_permanent=False,
228232
zone_component=HydrogenSulfideGas,
233+
moves_around=True
229234
)
230235
ALL_ENTITIES.append(hydrogen_sulfide_gas)
231236
poison_gas_granade = Item(
@@ -245,6 +250,7 @@
245250
duration=10,
246251
is_permanent=False,
247252
zone_component=NitrousOxideGas,
253+
moves_around=True
248254
)
249255
ALL_ENTITIES.append(nitrous_oxide_gas)
250256
stun_gas_granade = Item(
@@ -272,10 +278,11 @@
272278
char="▒",
273279
color=(255, 153, 0),
274280
name="Spore filled air",
275-
inspect_message="It's a cloud full of spores. They look sticky.",
281+
inspect_message="It's a cloud full of spores. They look sticky, and they will probably crawl under your skin for a while... Yikes...",
276282
duration=10,
277283
is_permanent=False,
278284
zone_component=SporeAir,
285+
moves_around=True
279286
)
280287
ALL_ENTITIES.append(spore_filled_air)
281288
bloom_shroom = Actor(
@@ -287,7 +294,7 @@
287294
inventory=Inventory(capacity=0),
288295
level=Level(xp_given=200),
289296
equipment=Equipment(),
290-
inspect_message="It's a large mushroom, like the little baby ones. It's releasing spores from it's cap. Is that a bad thing?",
297+
inspect_message="It's a large mushroom, like the little baby ones. It's releasing spores like it's a breeding season! Is that a bad thing?",
291298
)
292299
bloom_shroom.ai.setup(spore_filled_air, 3)
293300
ALL_ENTITIES.append(bloom_shroom)

input_handlers.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import random
4-
from typing import Callable, Optional, Tuple, TYPE_CHECKING, Union
4+
from typing import Callable, List, Optional, Tuple, TYPE_CHECKING, Union
55

66
import tcod.event
77
from tcod import libtcodpy
@@ -17,6 +17,7 @@
1717
MovementAction
1818
)
1919
import color
20+
import entity_factories
2021
import exceptions
2122
import os
2223
import threading
@@ -25,6 +26,7 @@
2526
if TYPE_CHECKING:
2627
from engine import Engine
2728
from entity import Item
29+
from entity import NPC
2830

2931

3032
MOVE_KEYS = {
@@ -736,13 +738,13 @@ class BuyItemsEventHandler(AskUserEventHandler):
736738
def __init__(self, engine: actions.Engine, previous_handler: EventHandler):
737739
super().__init__(engine)
738740

739-
from entity_factories import healing_gel, XL_healing_gel, taser
740741
self.previous_handler = previous_handler
741-
self.items = [
742-
copy.deepcopy(healing_gel),
743-
copy.deepcopy(XL_healing_gel),
744-
copy.deepcopy(taser)
745-
]
742+
self.items = []
743+
for item in previous_handler.item_list:
744+
for entity in entity_factories.ALL_ENTITIES:
745+
if entity.internal_name == item:
746+
self.items.append(copy.deepcopy(entity))
747+
746748

747749
def on_exit(self) -> Action | BaseEventHandler | None:
748750
return self.previous_handler
@@ -771,14 +773,13 @@ def on_render(self, console: tcod.event.Any) -> None:
771773
)
772774

773775
# Draw the avaible items in a list, similar to the inventory menu.
774-
# TODO: Add the items in, for now its placeholder items.
775776
for i, item in enumerate(self.items):
776777
item_key = chr(ord("a") + i)
777778
console.print(
778779
x=x + 1,
779780
y=y + 1 + i,
780781
fg=color.text_console,
781-
string=f"{item_key}) {item.name} - {item.value} Credit".upper(),
782+
string=f"{item_key}) {item.name} - {item.value} Credits".upper(),
782783
)
783784

784785
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[ActionOrHandler]:
@@ -808,6 +809,12 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[ActionOrHandler]:
808809
class ShopkeepMenuEventHandler(AskUserEventHandler):
809810
TITLE = "SHOP"
810811

812+
def __init__(self, engine: actions.Engine, NPC: NPC, item_list: List[Item]):
813+
super().__init__(engine)
814+
815+
self.NPC = NPC
816+
self.item_list = item_list
817+
811818
def on_render(self, console: tcod.Console) -> None:
812819
super().on_render(console)
813820

npc_handler.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from __future__ import annotations
2+
3+
4+
import random
5+
from typing import List, Optional, Tuple, Type, TypeVar, TYPE_CHECKING, Union
6+
7+
from engine import Engine
8+
9+
from render_order import RenderOrder
10+
11+
if TYPE_CHECKING:
12+
from input_handlers import EventHandler
13+
from entity import NPC, Item
14+
15+
shop_item_chances: List[Tuple[int, int]] = [
16+
("healing_gel", 15),
17+
("stun_gun", 15),
18+
("old_kinfe", 15),
19+
("iron_chest_plate", 15),
20+
("xl_healing_gel", 5),
21+
("taser", 10),
22+
("sharp_kinfe", 10),
23+
("stun_gas_granade", 5),
24+
("poison_gas_granade", 5),
25+
("acid_kinfe", 5),
26+
("spikey_chest_plate", 5),
27+
("fireball_gun", 5),
28+
]
29+
30+
31+
def get_weighted_elements_at_random(
32+
weighted_chances: List[Tuple[int, int]],
33+
amount: int,
34+
) -> List[int]:
35+
chosen_weights = random.choices(
36+
population=[element for element, weight in weighted_chances for i in range(weight)],
37+
k=amount,
38+
)
39+
40+
return chosen_weights
41+
42+
class NPCHandler:
43+
"""
44+
A class used in NPCs to setup their input handler with more data that is sorted in this class.
45+
"""
46+
def __init__(self, engine: Engine, input_handler_cls: Type[EventHandler], npc: NPC):
47+
self.engine = engine
48+
self.input_handler_cls = input_handler_cls
49+
self.npc = npc
50+
self.input_handler = None
51+
52+
def init_handler(self) -> None:
53+
"""
54+
Initialize the input handler for the NPC. This method can be overridden to add more data to the input handler.
55+
"""
56+
self.input_handler = self.input_handler_cls(self.engine, self.npc)
57+
58+
class ShopKeeperHandler(NPCHandler):
59+
"""
60+
Used to handler diferent kinds of shops. Along with tracking the inventory of the shop.
61+
"""
62+
def __init__(self, engine: Engine, input_handler_cls: Type[EventHandler], npc: NPC):
63+
super().__init__(engine, input_handler_cls, npc)
64+
items = get_weighted_elements_at_random(shop_item_chances, random.randint(3, 8))
65+
self.shop_items = []
66+
for item in items:
67+
# Check for repeats and do not add if there is a repeat.
68+
if item in self.shop_items:
69+
continue
70+
self.shop_items.append(item)
71+
72+
def init_handler(self) -> None:
73+
self.input_handler = self.input_handler_cls(self.engine, self.npc, self.shop_items)

procgen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525

2626
item_chances: Dict[int, List[Tuple[Entity, int]]] = {
2727
0: [(entity_factories.healing_gel, 35)],
28-
2: [(entity_factories.stun_gun, 10), (entity_factories.old_kinfe, 5), (entity_factories.iron_chest_plate, 5), (entity_factories.XL_healing_gel, 5)],
28+
2: [(entity_factories.stun_gun, 10), (entity_factories.old_kinfe, 5), (entity_factories.iron_chest_plate, 5), (entity_factories.xl_healing_gel, 5)],
2929
4: [(entity_factories.taser, 25), (entity_factories.sharp_kinfe, 15), (entity_factories.spikey_chest_plate, 12)],
30-
5: [(entity_factories.acid_kinfe, 15)],
30+
5: [(entity_factories.acid_kinfe, 15), (entity_factories.xl_healing_gel, 25)],
3131
6: [(entity_factories.fireball_gun, 25), (entity_factories.steel_chest_plate, 12), (entity_factories.professional_acid_kinfe, 15)],
3232
7: [(entity_factories.steelpike_chest_plate, 15)],
3333
8: [(entity_factories.acid_metal_chest_plate, 15)],

0 commit comments

Comments
 (0)