Skip to content

Commit 865d800

Browse files
committed
add currency picking
1 parent 1c5e231 commit 865d800

File tree

7 files changed

+2752
-2
lines changed

7 files changed

+2752
-2
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[flake8]
22
max-line-length = 88
3-
extend-ignore = E203, E704
3+
extend-ignore = E203, E704, Q000

src/tf2_utils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = "tf2-utils"
22
__author__ = "offish"
3-
__version__ = "2.0.9"
3+
__version__ = "2.1.0"
44
__license__ = "MIT"
55

66
from .sku import (
@@ -32,5 +32,6 @@
3232
InternalServerError,
3333
)
3434
from .inventory import Inventory, map_inventory
35+
from .currency import CurrencyExchange
3536

3637
# flake8: noqa

src/tf2_utils/currency.py

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
from .item import Item
2+
3+
4+
class CurrencyExchange:
5+
def __init__(
6+
self,
7+
their_inventory: list[dict],
8+
our_inventory: list[dict],
9+
intent: str,
10+
item_price: int,
11+
key_price: int,
12+
item_is_not_pure: bool = True,
13+
) -> None:
14+
self.their_inventory = their_inventory
15+
self.our_inventory = our_inventory
16+
17+
if intent not in ["buy", "sell"]:
18+
raise ValueError(f"{intent} is not a valid intent")
19+
20+
self.is_buying = intent == "buy"
21+
self.is_selling = intent == "sell"
22+
self.item_price = item_price
23+
self.scrap_price = item_price
24+
self.key_price = key_price
25+
self.item_is_not_pure = item_is_not_pure
26+
27+
self.__is_possible = False
28+
self.their_scrap = 0
29+
self.our_scrap = 0
30+
self.their_overview = {}
31+
self.our_overview = {}
32+
self.their_combination = [] # list of metal names
33+
self.our_combination = [] # list of metal names
34+
35+
def get_pure_value(self, name: str) -> int:
36+
match name:
37+
# refined
38+
case "Mann Co. Supply Crate Key":
39+
return self.key_price
40+
41+
case "Refined Metal":
42+
return 9
43+
44+
# reclaimed
45+
case "Reclaimed Metal":
46+
return 3
47+
48+
# scrap
49+
case "Scrap Metal":
50+
return 1
51+
52+
raise ValueError(f"{name} is not pure")
53+
54+
def get_pure_in_inventory(self, inventory: list[dict]) -> tuple[int, list[dict]]:
55+
"""scrap value, list of metal item dicts"""
56+
scrap = 0
57+
metal = []
58+
59+
for item in inventory:
60+
item_util = Item(item)
61+
name = item["market_hash_name"]
62+
63+
if not item_util.is_tradable():
64+
continue
65+
66+
if not item_util.is_pure():
67+
continue
68+
69+
pure_value = self.get_pure_value(name)
70+
item["pure_value"] = pure_value
71+
72+
scrap += pure_value
73+
metal.append(item)
74+
75+
return scrap, metal
76+
77+
def is_possible(self) -> bool:
78+
return self.__is_possible
79+
80+
def __overview_to_items(
81+
self, combination: list[str], inventory: list[dict]
82+
) -> list[dict]:
83+
items = []
84+
85+
for metal_name in combination:
86+
for item in inventory:
87+
if item["market_hash_name"] != metal_name:
88+
continue
89+
90+
if item.get("picked"):
91+
continue
92+
93+
items.append(item)
94+
item["picked"] = True
95+
break
96+
97+
return items
98+
99+
def get_currencies(self) -> tuple[list[dict], list[dict]]:
100+
assert self.__is_possible, "Currencies does not add up"
101+
102+
their_items = self.__overview_to_items(
103+
self.their_combination, self.their_inventory
104+
)
105+
our_items = self.__overview_to_items(self.our_combination, self.our_inventory)
106+
107+
return their_items, our_items
108+
109+
@staticmethod
110+
def format_overview(pure: list[dict]) -> dict:
111+
overview = {
112+
"Mann Co. Supply Crate Key": 0,
113+
"Refined Metal": 0,
114+
"Reclaimed Metal": 0,
115+
"Scrap Metal": 0,
116+
}
117+
118+
for item in pure:
119+
for pure_name in overview:
120+
if item["market_hash_name"] != pure_name:
121+
continue
122+
123+
overview[pure_name] += 1
124+
break
125+
126+
return overview
127+
128+
def __pick_currencies(self, user: str) -> tuple[bool, list[str]]:
129+
"""ref, ref, scrap, rec"""
130+
overview = (
131+
self.their_overview.copy() if user == "them" else self.our_overview.copy()
132+
)
133+
price = self.scrap_price
134+
135+
combination = []
136+
137+
if self.is_buying and user == "them" and self.item_is_not_pure:
138+
price -= self.item_price
139+
140+
if self.is_selling and user == "us" and self.item_is_not_pure:
141+
price -= self.item_price
142+
143+
while price != 0:
144+
did_add = False
145+
146+
for name in overview:
147+
value = self.get_pure_value(name)
148+
149+
if overview[name] == 0:
150+
continue
151+
152+
if price % value != 0:
153+
continue
154+
155+
# goes from key->ref->rec->scrap will pick most expensieve
156+
# currency available first
157+
price -= value
158+
overview[name] -= 1
159+
combination.append(name)
160+
did_add = True
161+
162+
break
163+
164+
if not did_add:
165+
return False, []
166+
167+
return True, combination
168+
169+
def __does_add_up(self) -> bool:
170+
their_value = 0
171+
our_value = 0
172+
173+
for name in self.their_combination:
174+
their_value += self.get_pure_value(name)
175+
176+
for name in self.our_combination:
177+
our_value += self.get_pure_value(name)
178+
179+
if self.is_buying and self.item_is_not_pure:
180+
their_value += self.item_price
181+
182+
if self.is_selling and self.item_is_not_pure:
183+
our_value += self.item_price
184+
185+
return their_value == our_value
186+
187+
def __set_combinations(self) -> bool:
188+
success_our, our_combination = self.__pick_currencies("us")
189+
success_their, their_combination = self.__pick_currencies("them")
190+
191+
if not success_our or not success_their:
192+
return False
193+
194+
self.our_combination = our_combination
195+
self.their_combination = their_combination
196+
197+
return True
198+
199+
def __has_enough(self) -> bool:
200+
temp_price = self.scrap_price
201+
202+
if self.item_is_not_pure:
203+
temp_price -= self.item_price
204+
205+
if self.is_buying:
206+
return self.their_scrap >= temp_price and self.our_scrap >= self.scrap_price
207+
208+
return self.their_scrap >= self.scrap_price and self.our_scrap >= temp_price
209+
210+
def calculate(self) -> None:
211+
self.their_scrap, their_pure = self.get_pure_in_inventory(self.their_inventory)
212+
self.our_scrap, our_pure = self.get_pure_in_inventory(self.our_inventory)
213+
214+
self.their_overview = self.format_overview(their_pure)
215+
self.our_overview = self.format_overview(our_pure)
216+
217+
if not self.__has_enough():
218+
return
219+
220+
while self.__has_enough():
221+
success = self.__set_combinations()
222+
223+
if success:
224+
break
225+
226+
# since we could not find a combination, we add 1 scrap to each side and
227+
# try again, do this till we have either user does not have enough anymore
228+
self.scrap_price += 1
229+
230+
if not self.__does_add_up():
231+
return
232+
233+
# everything adds up and looks good
234+
self.__is_possible = True

src/tf2_utils/item.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ def __init__(self, item: dict) -> None:
1111
def is_tf2(self) -> bool:
1212
return self.item["appid"] == 440
1313

14+
def is_tradable(self) -> bool:
15+
return self.item.get("tradable", 1) == 1
16+
1417
def has_name(self, name: str) -> bool:
1518
return self.name == name
1619

@@ -211,6 +214,19 @@ def is_unusual_cosmetic(self) -> bool:
211214
def is_australium(self) -> bool:
212215
return "Australium" in self.name
213216

217+
def is_pure(self) -> bool:
218+
return (
219+
self.is_craftable()
220+
and self.is_unique()
221+
and self.name
222+
in [
223+
"Mann Co. Supply Crate Key",
224+
"Refined Metal",
225+
"Reclaimed Metal",
226+
"Scrap Metal",
227+
]
228+
)
229+
214230
def is_key(self) -> bool:
215231
return (
216232
self.is_craftable()

0 commit comments

Comments
 (0)