1
1
from __future__ import annotations
2
2
3
3
from collections import Counter
4
- from numbers import Number
5
- from typing import TYPE_CHECKING , Optional , TypeVar
4
+ from typing import TYPE_CHECKING , Literal , Optional , TypeVar
6
5
7
6
import attrs
8
7
import structlog
9
8
9
+ from .items import Item
10
+
10
11
if TYPE_CHECKING :
11
12
from .game import Game
12
- from .items import Item
13
13
from .locations import Location
14
14
15
15
16
- PerkValue = TypeVar ("PerkValue" , bound = Number )
16
+ PerkValue = TypeVar ("PerkValue" , int , float )
17
17
18
18
19
19
@attrs .define
@@ -25,15 +25,15 @@ class Player:
25
25
fishing_xp : int = 0
26
26
crafting_xp : int = 0
27
27
exploring_xp : int = 0
28
- inventory : Counter [Item , int ] = attrs .field (factory = Counter , converter = Counter )
28
+ inventory : Counter [Item ] = attrs .field (factory = Counter , converter = Counter )
29
29
max_inventory : int = 100
30
30
stamina : int = 0
31
31
max_stamina : int = 100
32
32
perks : set [str ] = attrs .Factory (set )
33
33
has_all_perks : bool = False
34
- exploring_effectiveness : dict [Location : int ] = attrs .Factory (dict )
34
+ exploring_effectiveness : dict [Location , int ] = attrs .Factory (dict )
35
35
# Tracking stuff.
36
- overflow_items : Counter [Item , int ] = attrs .field (factory = Counter , converter = Counter )
36
+ overflow_items : Counter [Item ] = attrs .field (factory = Counter , converter = Counter )
37
37
seconds_until_stamina : int = 120
38
38
39
39
log : structlog .stdlib .BoundLogger = structlog .stdlib .get_logger (mod = "player" )
@@ -71,7 +71,7 @@ def has_perk(self, perk: str) -> bool:
71
71
# TODO validate perk names so I can catch typos.
72
72
return self .has_all_perks or perk in self .perks
73
73
74
- def perk_value (self , perks : dict [str , PerkValue ]) -> PerkValue :
74
+ def perk_value (self , perks : dict [str , PerkValue ]) -> PerkValue | Literal [ 0 ] :
75
75
"""Handle the very common case of needing to sum multiple values from different perks."""
76
76
return sum (value for perk , value in perks .items () if self .has_perk (perk ))
77
77
@@ -93,7 +93,7 @@ def sell_item(self, item: Item, quantity: Optional[int] = None) -> None:
93
93
silver = quantity * item .sell_price * (1 + sell_bonus )
94
94
# This check we have enough of the item.
95
95
self .remove_item (item , quantity )
96
- self .silver += silver
96
+ self .silver += round ( silver )
97
97
self .log .debug ("Selling item" , item = item .name , quantity = quantity , silver = silver )
98
98
99
99
def buy_item (self , item : Item , quantity : Optional [int ] = None ) -> None :
@@ -116,3 +116,48 @@ def exploring_effectiveness_for(self, location: Location) -> int:
116
116
if self .has_perk ("Sprint Shoes II" ):
117
117
multiplier *= 2
118
118
return self .exploring_effectiveness .get (location , 1 ) * multiplier
119
+
120
+ def items_needed_to_craft (self , item : Item ) -> dict [Item , int ]:
121
+ """Return how many of each direct ingredient are not currently available."""
122
+ if not item .recipe :
123
+ raise ValueError (f"{ item .name } is not craftable" )
124
+ needed : dict [Item , int ] = {}
125
+ for name , quantity in item .recipe .items ():
126
+ ingredient = Item [name ]
127
+ ingredient_needed = quantity - self .inventory [ingredient ]
128
+ if ingredient_needed > 0 :
129
+ needed [ingredient ] = ingredient_needed
130
+ return needed
131
+
132
+ def craft (self , item : Item ) -> None :
133
+ if item .craft_price is None :
134
+ raise ValueError (f"{ item .name } is not craftable" )
135
+ needed = self .items_needed_to_craft (item )
136
+ if needed :
137
+ raise ValueError (f"{ item .name } is missing ingredients: { needed } " )
138
+ price_reduction = self .perk_value (
139
+ {
140
+ "Artisan I" : 0.05 ,
141
+ "Artisan II" : 0.1 ,
142
+ "Artisan III" : 0.15 ,
143
+ "Artisan IV" : 0.2 ,
144
+ "Toolbox I" : 0.1 ,
145
+ }
146
+ )
147
+ craft_price = round (item .craft_price * (1 - price_reduction ))
148
+ if self .silver < craft_price :
149
+ raise ValueError ("not enough silver" )
150
+ xp_bonus = self .perk_value (
151
+ {
152
+ "Crafting Primer" : 0.1 ,
153
+ "Crafting Primer II" : 0.1 ,
154
+ "Crafting Almanac" : 0.1 ,
155
+ }
156
+ )
157
+ xp = round (item .xp * (1 + xp_bonus ))
158
+ self .log .debug ("Crafting" , item = item , price = craft_price , xp = xp )
159
+ for name , quantity in item .recipe .items ():
160
+ self .remove_item (Item [name ], quantity )
161
+ self .silver -= craft_price
162
+ self .add_item (item )
163
+ self .crafting_xp += xp
0 commit comments