Skip to content

Commit a231850

Browse files
committed
Make hint costs relative
1 parent 1b2283b commit a231850

7 files changed

+62
-59
lines changed

CommonClient.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def __init__(self, server_address, password, found_items: bool):
129129
self.input_requests = 0
130130

131131
# game state
132-
self.player_names: typing.Dict[int: str] = {0: "Server"}
132+
self.player_names: typing.Dict[int: str] = {0: "Archipelago"}
133133
self.exit_event = asyncio.Event()
134134
self.watcher_event = asyncio.Event()
135135

@@ -194,7 +194,7 @@ async def send_msgs(self, msgs):
194194

195195
def consume_players_package(self, package: typing.List[tuple]):
196196
self.player_names = {slot: name for team, slot, name, orig_name in package if self.team == team}
197-
self.player_names[0] = "Server"
197+
self.player_names[0] = "Archipelago"
198198

199199
def event_invalid_slot(self):
200200
raise Exception('Invalid Slot; please verify that you have connected to the correct world.')
@@ -305,8 +305,8 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
305305
logger.info('Password required')
306306
logger.info(f"Forfeit setting: {args['forfeit_mode']}")
307307
logger.info(f"Remaining setting: {args['remaining_mode']}")
308-
logger.info(f"A !hint costs {args['hint_cost']} points and you get {args['location_check_points']}"
309-
f" for each location checked.")
308+
logger.info(f"A !hint costs {args['hint_cost']}% of checks points and you get {args['location_check_points']}"
309+
f" for each location checked. Use !hint for more information.")
310310
ctx.hint_cost = int(args['hint_cost'])
311311
ctx.check_points = int(args['location_check_points'])
312312
ctx.forfeit_mode = args['forfeit_mode']

Main.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import zlib
88
import concurrent.futures
99
import pickle
10-
from typing import Dict
10+
from typing import Dict, Tuple
1111

1212
from BaseClasses import MultiWorld, CollectionState, Region, Item
1313
from worlds.alttp.Items import ItemFactory, item_name_groups
@@ -501,7 +501,7 @@ def write_multidata(roms, mods):
501501
rom_names.append(rom_name)
502502
slot_data = {}
503503
client_versions = {}
504-
minimum_versions = {"server": (0, 0, 4), "clients": client_versions}
504+
minimum_versions = {"server": (0, 1, 1), "clients": client_versions}
505505
games = {}
506506
for slot in world.player_ids:
507507
client_versions[slot] = (0, 0, 3)
@@ -520,18 +520,19 @@ def write_multidata(roms, mods):
520520
slots_data[option_name] = int(option.value)
521521
for slot in world.minecraft_player_ids:
522522
slot_data[slot] = fill_minecraft_slot_data(world, slot)
523+
524+
locations_data: Dict[int, Dict[int, Tuple[int, int]]] = {player: {} for player in world.player_ids}
525+
for location in world.get_filled_locations():
526+
if type(location.address) == int:
527+
locations_data[location.player][location.address] = (location.item.code, location.item.player)
523528
multidata = zlib.compress(pickle.dumps({
524529
"slot_data" : slot_data,
525530
"games": games,
526531
"names": parsed_names,
527532
"connect_names": connect_names,
528533
"remote_items": {player for player in range(1, world.players + 1) if
529534
world.remote_items[player]},
530-
"locations": {
531-
(location.address, location.player):
532-
(location.item.code, location.item.player)
533-
for location in world.get_filled_locations() if
534-
type(location.address) is int},
535+
"locations": locations_data,
535536
"checks_in_area": checks_in_area,
536537
"server_options": get_options()["server_options"],
537538
"er_hint_data": er_hint_data,

MultiServer.py

+41-45
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo
7878
self.connect_names = {} # names of slots clients can connect to
7979
self.allow_forfeits = {}
8080
self.remote_items = set()
81-
self.locations = {}
81+
self.locations:typing.Dict[int, typing.Dict[int, typing.Tuple[int, int]]] = {}
8282
self.host = host
8383
self.port = port
8484
self.server_password = server_password
@@ -114,6 +114,11 @@ def __init__(self, host: str, port: int, server_password: str, password: str, lo
114114
self.minimum_client_versions: typing.Dict[int, Utils.Version] = {}
115115
self.seed_name = ""
116116

117+
def get_hint_cost(self, slot):
118+
if self.hint_cost:
119+
return max(0, int(self.hint_cost * 0.01 * len(self.locations[slot])))
120+
return 0
121+
117122
def load(self, multidatapath: str, use_embedded_server_options: bool = False):
118123
with open(multidatapath, 'rb') as f:
119124
data = f.read()
@@ -132,7 +137,7 @@ def _load(self, decoded_obj: dict, use_embedded_server_options: bool):
132137

133138
mdata_ver = decoded_obj["minimum_versions"]["server"]
134139
if mdata_ver > Utils._version_tuple:
135-
raise RuntimeError(f"Supplied Multidata requires a server of at least version {mdata_ver},"
140+
raise RuntimeError(f"Supplied Multidata (.archipelago) requires a server of at least version {mdata_ver},"
136141
f"however this server is of version {Utils._version_tuple}")
137142
clients_ver = decoded_obj["minimum_versions"].get("clients", {})
138143
self.minimum_client_versions = {}
@@ -437,10 +442,6 @@ def get_received_items(ctx: Context, team: int, player: int) -> typing.List[Netw
437442
return ctx.received_items.setdefault((team, player), [])
438443

439444

440-
def tuplize_received_items(items):
441-
return [NetworkItem(item.item, item.location, item.player) for item in items]
442-
443-
444445
def send_new_items(ctx: Context):
445446
for client in ctx.endpoints:
446447
if client.auth: # can't send to disconnected client
@@ -449,22 +450,22 @@ def send_new_items(ctx: Context):
449450
asyncio.create_task(ctx.send_msgs(client, [{
450451
"cmd": "ReceivedItems",
451452
"index": client.send_index,
452-
"items": tuplize_received_items(items)[client.send_index:]}]))
453+
"items": items[client.send_index:]}]))
453454
client.send_index = len(items)
454455

455456

456457
def forfeit_player(ctx: Context, team: int, slot: int):
457458
# register any locations that are in the multidata
458-
all_locations = {location_id for location_id, location_slot in ctx.locations if location_slot == slot}
459+
all_locations = set(ctx.locations[slot])
459460
ctx.notify_all("%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
460461
register_location_checks(ctx, team, slot, all_locations)
461462

462463

463464
def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[int]:
464465
items = []
465-
for (location, location_slot) in ctx.locations:
466-
if location_slot == slot and location not in ctx.location_checks[team, slot]:
467-
items.append(ctx.locations[location, slot][0]) # item ID
466+
for location_id in ctx.locations[slot]:
467+
if location_id not in ctx.location_checks[team, slot]:
468+
items.append(ctx.locations[slot][location_id][0]) # item ID
468469
return sorted(items)
469470

470471

@@ -473,8 +474,8 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
473474
if new_locations:
474475
ctx.client_activity_timers[team, slot] = datetime.datetime.now(datetime.timezone.utc)
475476
for location in new_locations:
476-
if (location, slot) in ctx.locations:
477-
item_id, target_player = ctx.locations[(location, slot)]
477+
if location in ctx.locations[slot]:
478+
item_id, target_player = ctx.locations[slot][location]
478479
new_item = NetworkItem(item_id, location, slot)
479480
if target_player != slot or slot in ctx.remote_items:
480481
get_received_items(ctx, team, target_player).append(new_item)
@@ -504,29 +505,26 @@ def notify_team(ctx: Context, team: int, text: str):
504505
def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[NetUtils.Hint]:
505506
hints = []
506507
seeked_item_id = lookup_any_item_name_to_id[item]
507-
for check, result in ctx.locations.items():
508-
item_id, receiving_player = result
509-
if receiving_player == slot and item_id == seeked_item_id:
510-
location_id, finding_player = check
511-
found = location_id in ctx.location_checks[team, finding_player]
512-
entrance = ctx.er_hint_data.get(finding_player, {}).get(location_id, "")
513-
hints.append(NetUtils.Hint(receiving_player, finding_player, location_id, item_id, found, entrance))
508+
for finding_player, check_data in ctx.locations.items():
509+
for location_id, result in check_data.items():
510+
item_id, receiving_player = result
511+
if receiving_player == slot and item_id == seeked_item_id:
512+
found = location_id in ctx.location_checks[team, finding_player]
513+
entrance = ctx.er_hint_data.get(finding_player, {}).get(location_id, "")
514+
hints.append(NetUtils.Hint(receiving_player, finding_player, location_id, item_id, found, entrance))
514515

515516
return hints
516517

517518

518519
def collect_hints_location(ctx: Context, team: int, slot: int, location: str) -> typing.List[NetUtils.Hint]:
519-
hints = []
520-
seeked_location = Regions.lookup_name_to_id[location]
521-
for check, result in ctx.locations.items():
522-
location_id, finding_player = check
523-
if finding_player == slot and location_id == seeked_location:
524-
item_id, receiving_player = result
525-
found = location_id in ctx.location_checks[team, finding_player]
526-
entrance = ctx.er_hint_data.get(finding_player, {}).get(location_id, "")
527-
hints.append(NetUtils.Hint(receiving_player, finding_player, location_id, item_id, found, entrance))
528-
break # each location has 1 item
529-
return hints
520+
521+
seeked_location: int = Regions.lookup_name_to_id[location]
522+
item_id, receiving_player = ctx.locations[slot].get(seeked_location, (None, None))
523+
if item_id:
524+
found = seeked_location in ctx.location_checks[team, slot]
525+
entrance = ctx.er_hint_data.get(slot, {}).get(seeked_location, "")
526+
return [NetUtils.Hint(receiving_player, slot, seeked_location, item_id, found, entrance)]
527+
return []
530528

531529

532530
def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
@@ -864,7 +862,7 @@ def _cmd_hint(self, item_or_location: str = "") -> bool:
864862
"""Use !hint {item_name/location_name}, for example !hint Lamp or !hint Link's House. """
865863
points_available = get_client_points(self.ctx, self.client)
866864
if not item_or_location:
867-
self.output(f"A hint costs {self.ctx.hint_cost} points. "
865+
self.output(f"A hint costs {self.ctx.get_hint_cost(self.client.slot)} points. "
868866
f"You have {points_available} points.")
869867
hints = {hint.re_check(self.ctx, self.client.team) for hint in
870868
self.ctx.hints[self.client.team, self.client.slot]}
@@ -885,7 +883,7 @@ def _cmd_hint(self, item_or_location: str = "") -> bool:
885883
hints = collect_hints(self.ctx, self.client.team, self.client.slot, item_name)
886884
else: # location name
887885
hints = collect_hints_location(self.ctx, self.client.team, self.client.slot, item_name)
888-
886+
cost = self.ctx.get_hint_cost(self.client.slot)
889887
if hints:
890888
new_hints = set(hints) - self.ctx.hints[self.client.team, self.client.slot]
891889
old_hints = set(hints) - new_hints
@@ -899,8 +897,8 @@ def _cmd_hint(self, item_or_location: str = "") -> bool:
899897

900898
if not not_found_hints: # everything's been found, no need to pay
901899
can_pay = 1000
902-
elif self.ctx.hint_cost:
903-
can_pay = points_available // self.ctx.hint_cost
900+
elif cost:
901+
can_pay = points_available // cost
904902
else:
905903
can_pay = 1000
906904

@@ -926,7 +924,7 @@ def _cmd_hint(self, item_or_location: str = "") -> bool:
926924
else:
927925
self.output(f"You can't afford the hint. "
928926
f"You have {points_available} points and need at least "
929-
f"{self.ctx.hint_cost}")
927+
f"{self.ctx.get_hint_cost(self.client.slot)}")
930928
notify_hints(self.ctx, self.client.team, hints)
931929
self.ctx.save()
932930
return True
@@ -941,21 +939,19 @@ def _cmd_hint(self, item_or_location: str = "") -> bool:
941939

942940
def get_checked_checks(ctx: Context, client: Client) -> typing.List[int]:
943941
return [location_id for
944-
location_id, slot in ctx.locations if
945-
slot == client.slot and
942+
location_id in ctx.locations[client.slot] if
946943
location_id in ctx.location_checks[client.team, client.slot]]
947944

948945

949946
def get_missing_checks(ctx: Context, client: Client) -> typing.List[int]:
950947
return [location_id for
951-
location_id, slot in ctx.locations if
952-
slot == client.slot and
948+
location_id in ctx.locations[client.slot] if
953949
location_id not in ctx.location_checks[client.team, client.slot]]
954950

955951

956952
def get_client_points(ctx: Context, client: Client) -> int:
957953
return (ctx.location_check_points * len(ctx.location_checks[client.team, client.slot]) -
958-
ctx.hint_cost * ctx.hints_used[client.team, client.slot])
954+
ctx.get_hint_cost(client.slot) * ctx.hints_used[client.team, client.slot])
959955

960956

961957
async def process_client_cmd(ctx: Context, client: Client, args: dict):
@@ -1032,7 +1028,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
10321028
}]
10331029
items = get_received_items(ctx, client.team, client.slot)
10341030
if items:
1035-
reply.append({"cmd": 'ReceivedItems', "index": 0, "items": tuplize_received_items(items)})
1031+
reply.append({"cmd": 'ReceivedItems', "index": 0, "items": items})
10361032
client.send_index = len(items)
10371033

10381034
await ctx.send_msgs(client, reply)
@@ -1047,7 +1043,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
10471043
if items:
10481044
client.send_index = len(items)
10491045
await ctx.send_msgs(client, [{"cmd": "ReceivedItems","index": 0,
1050-
"items": tuplize_received_items(items)}])
1046+
"items": items}])
10511047

10521048
elif cmd == 'LocationChecks':
10531049
register_location_checks(ctx, client.team, client.slot, args["locations"])
@@ -1058,7 +1054,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
10581054
if type(location) is not int or location not in lookup_any_location_id_to_name:
10591055
await ctx.send_msgs(client, [{"cmd": "InvalidArguments", "text": 'LocationScouts'}])
10601056
return
1061-
target_item, target_player = ctx.locations[location, client.slot]
1057+
target_item, target_player = ctx.locations[client.slot][location]
10621058
locs.append(NetworkItem(target_item, location, target_player))
10631059

10641060
await ctx.send_msgs(client, [{'cmd': 'LocationInfo', 'locations': locs}])
@@ -1206,7 +1202,7 @@ def _cmd_send(self, player_name: str, *item_name: str) -> bool:
12061202
if usable:
12071203
for client in self.ctx.endpoints:
12081204
if client.name == seeked_player:
1209-
new_item = NetworkItem(lookup_any_item_name_to_id[item], -1, client.slot)
1205+
new_item = NetworkItem(lookup_any_item_name_to_id[item], -1, 0)
12101206
get_received_items(self.ctx, client.team, client.slot).append(new_item)
12111207
self.ctx.notify_all('Cheat console: sending "' + item + '" to ' +
12121208
self.ctx.get_aliased_name(client.team, client.slot))

Mystery.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,10 @@ def handle_name(name: str, player: int, name_counter: Counter):
303303
name] > 1 else ''),
304304
player=player,
305305
PLAYER=(player if player > 1 else '')))
306-
return new_name.strip().replace(' ', '_')[:16]
306+
new_name = new_name.strip().replace(' ', '_')[:16]
307+
if new_name == "Archipelago":
308+
raise Exception(f"You cannot name yourself \"{new_name}\"")
309+
return new_name
307310

308311

309312
def prefer_int(input_data: str) -> typing.Union[str, int]:

Utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Version(typing.NamedTuple):
1212
minor: int
1313
build: int
1414

15-
__version__ = "0.1.0"
15+
__version__ = "0.1.1"
1616
_version_tuple = tuplize_version(__version__)
1717

1818
import builtins

host.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ server_options:
1919
# Client hint system
2020
# Points given to a player for each acquired item in their world
2121
location_check_points: 1
22-
# Point cost to receive a hint via !hint for players
22+
# Relative point cost to receive a hint via !hint for players
23+
# so for example hint_cost: 20 would mean that for every 20% of available checks, you get the ability to hint, for a total of 5
2324
hint_cost: 1000 # Set to 0 if you want free hints
2425
# Forfeit modes
2526
# "disabled" -> clients can't forfeit,

worlds/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
lookup_any_item_id_to_name = {**alttp, **hk, **Technologies.lookup_id_to_name, **mc}
1414
assert len(alttp) + len(hk) + len(Technologies.lookup_id_to_name) + len(mc) == len(lookup_any_item_id_to_name)
1515
lookup_any_item_name_to_id = {name: id for id, name in lookup_any_item_id_to_name.items()}
16+
# assert len(lookup_any_item_name_to_id) == len(lookup_any_item_id_to_name) # currently broken: Single Arrow
1617

1718
from .alttp import Regions
1819
from .hk import Locations
@@ -24,6 +25,7 @@
2425
len(Technologies.lookup_id_to_name) + len(Advancements.lookup_id_to_name) == \
2526
len(lookup_any_location_id_to_name)
2627
lookup_any_location_name_to_id = {name: id for id, name in lookup_any_location_id_to_name.items()}
28+
assert len(lookup_any_location_name_to_id) == len(lookup_any_location_id_to_name)
2729

2830
network_data_package = {"lookup_any_location_id_to_name": lookup_any_location_id_to_name,
2931
"lookup_any_item_id_to_name": lookup_any_item_id_to_name,

0 commit comments

Comments
 (0)