Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
.vscode/
.vs/
/*.code-workspace
.idea
.idea
build.py
*.apworld
76 changes: 71 additions & 5 deletions src/ManualClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,16 @@ def update_hints(self):
if "(Hinted)" not in location["category"]:
location["category"].append("(Hinted)")
rebuild = True
json_str = [
{"type": "player_id", "text": hint["receiving_player"]},
{"type": "text", "text": "'s "},
{"type": "item_id",
"text": hint["item"],
"flags": hint["item_flags"],
"player": hint["receiving_player"],
},
]
location['hint_text'] = self.json_to_kivy_parser(json_str)

if rebuild:
self.build_tracker_and_locations_table()
Expand Down Expand Up @@ -675,6 +685,9 @@ def build_tracker_and_locations_table(self):
if not self.ctx.location_table and not hasattr(AutoWorldRegister.world_types[self.ctx.game], 'location_name_to_location'):
raise Exception("The apworld for %s is too outdated for this client. Please update it." % (self.ctx.game))

scoutable_locations = set()
hinted_locations = {}

for location_id in self.ctx.missing_locations:
# holy nesting, wow
location_name = self.ctx.location_names.lookup_in_game(location_id)
Expand All @@ -683,6 +696,11 @@ def build_tracker_and_locations_table(self):
if not location:
continue

if location.get("scoutable", False):
scoutable_locations.add(location_id)
if location.get("hint_text", False):
hinted_locations[location_id] = location.get("hint_text", "")

if "category" in location and len(location["category"]) > 0:
for category in location["category"]:
category_settings = self.ctx.category_table.get(category) or getattr(AutoWorldRegister.world_types[self.ctx.game], "category_table", {}).get(category, {})
Expand Down Expand Up @@ -772,16 +790,29 @@ def alphanum_key(i):
)

category_scroll = locations_panel.add_node(TreeViewScrollView(size_hint=(1, None), size=(Window.width / 2, 250)), category_tree)
category_layout = GridLayout(cols=1, size_hint_y=None)
category_layout = GridLayout(cols=2, size_hint_y=None)
category_layout.bind(minimum_height = category_layout.setter('height'))
category_scroll.add_widget(category_layout)

for location_id in self.listed_locations[location_category]:
location_button = TreeViewButton(text=self.ctx.location_names.lookup_in_game(location_id), size_hint=(None, None), height=30, width=400)
has_hint = location_id in hinted_locations or location_id in scoutable_locations

location_button = TreeViewButton(text=self.ctx.location_names.lookup_in_game(location_id), size_hint=(.75 if has_hint else 1, None), height=30)
location_button.bind(on_release=lambda *args, loc_id=location_id: self.location_button_callback(loc_id, *args))
location_button.id = location_id
category_layout.add_widget(location_button)

if location_id in hinted_locations:
category_layout.add_widget(Label(text=hinted_locations[location_id], size_hint=(.25, None), height=30, font_size=10, markup=True))
elif location_id in scoutable_locations:
location_scout = TreeViewButton(text="Scout", size_hint=(.25, None), height=30, font_size=10)
location_scout.bind(on_release=lambda *args, loc_id=location_id: self.location_scout_callback(loc_id, *args))
location_scout.id = location_id
category_layout.add_widget(location_scout)
else:
# Add invisible spacer to maintain 2-column grid structure
category_layout.add_widget(Label(text="", size_hint=(None, None), height=0, width=0, opacity=0))

# if this is the category that Victory is in, display the Victory button
# if ("category" in victory_location_data and location_category in victory_location_data["category"]) or \
# ("category" not in victory_location_data and location_category == "(No Category)"):
Expand Down Expand Up @@ -1017,6 +1048,9 @@ def hide_button_during_search(btn: TreeViewButton):
# Label (for existing item listings)
for location_button in category_grid.children:
if type(location_button) is TreeViewButton:
# Skip scout buttons
if location_button.text == "Scout":
continue
# should only be true for the victory location button, which has different text
if location_button.text not in (self.ctx.location_table or AutoWorldRegister.world_types[self.ctx.game].location_name_to_location):
# if the player is searching for text and the location name doesn't contain it, hide and disable it
Expand Down Expand Up @@ -1059,7 +1093,13 @@ def hide_button_during_search(btn: TreeViewButton):
category_count += 1

for location_button in buttons_to_remove:
location_button.parent.remove_widget(location_button)
parent = location_button.parent
button_index = parent.children.index(location_button)
# Grid cols=2: pairs are at adjacent indices (even with odd, odd with even)
pair_index = button_index - 1 if button_index % 2 == 1 else button_index + 1
if 0 <= pair_index < len(parent.children):
parent.remove_widget(parent.children[pair_index])
parent.remove_widget(location_button)

scrollview_height = 30 * category_count

Expand Down Expand Up @@ -1087,7 +1127,7 @@ def hide_button_during_search(btn: TreeViewButton):

category_scrollview.size=(Window.width / 2, scrollview_height)

def location_button_callback(self, location_id, button):
def location_button_callback(self, location_id: int, button: TreeViewButton):
if button.text not in self.ctx.location_names_to_id:
raise Exception("Locations were not loaded correctly. Please reconnect your client.")

Expand All @@ -1108,11 +1148,34 @@ def location_button_callback(self, location_id, button):
else:
self.ctx.locations_checked.append(location_id)
self.ctx.syncing = True
button.parent.remove_widget(button)
# Remove both the location button and its adjacent scout button/spacer from 2-column grid
parent = button.parent
button_index = parent.children.index(button)
# Grid cols=2: pairs are at adjacent indices (even with odd, odd with even)
pair_index = button_index - 1 if button_index % 2 == 1 else button_index + 1
if 0 <= pair_index < len(parent.children):
parent.remove_widget(parent.children[pair_index])
parent.remove_widget(button)

# message = [{"cmd": 'LocationChecks', "locations": [location_id]}]
# self.ctx.send_msgs(message)

def location_scout_callback(self, location_id: int, button: TreeViewButton) -> None:
# if the mouse is currently hovering over any of the controls/tabs at the top of the client, ignore clicks for location buttons underneath
if self.are_top_controls_at_mouse_pos():
# if there's an obj in the top controls/tab at the current mouse position, click it instead
if hovered_obj := self.get_top_obj_at_mouse_pos():
if hasattr(hovered_obj, 'trigger_action'): # buttons, tabs, etc.
hovered_obj.trigger_action(duration=0)
elif hasattr(hovered_obj, 'focus'): # text inputs
hovered_obj.focus = True

return

if location_id:
self.ctx.locations_scouted.append(location_id)
self.ctx.syncing = True

def victory_button_callback(self, button):
# if the mouse is currently hovering over any of the controls/tabs at the top of the client, ignore clicks for location buttons underneath
if self.are_top_controls_at_mouse_pos():
Expand All @@ -1139,6 +1202,8 @@ async def game_watcher_manual(ctx: ManualContext):
sync_msg = [{'cmd': 'Sync'}]
if ctx.locations_checked:
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
if ctx.locations_scouted:
sync_msg.append({"cmd": "LocationScouts", "locations": list(ctx.locations_scouted), "create_as_hint": 2})
await ctx.send_msgs(sync_msg)
ctx.syncing = False

Expand All @@ -1152,6 +1217,7 @@ async def game_watcher_manual(ctx: ManualContext):

victory = ("__Victory__" in ctx.items_received)
ctx.locations_checked = []
ctx.locations_scouted = []
if not ctx.finished_game and victory:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
Expand Down
3 changes: 2 additions & 1 deletion src/data/locations.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
{
"name": "Beat the Game - Ryu",
"category": ["Starter Team", "Left Side"],
"region": "ExampleRegion"
"region": "ExampleRegion",
"scoutable": true
},
{
"name": "Beat the Game - Chun-Li",
Expand Down
Loading