Skip to content

Commit 8e6026f

Browse files
authored
Merge pull request #2 from Magnemania/wargroove
WG: GUI Client for tracking and selecting commanders
2 parents c833b91 + 5fb0330 commit 8e6026f

File tree

3 files changed

+215
-51
lines changed

3 files changed

+215
-51
lines changed

WargrooveClient.py

+153-19
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,28 @@
33
import sys
44
import asyncio
55
import random
6-
from typing import Tuple, List, Iterable
7-
from worlds.wargroove.Items import faction_table, CommanderData
6+
from typing import Tuple, List, Iterable, Dict
7+
8+
from worlds.wargroove import WargrooveWorld
9+
from worlds.wargroove.Items import item_table, faction_table, CommanderData, ItemData
810

911
import ModuleUpdate
1012
ModuleUpdate.update()
1113

1214
import Utils
1315
import json
16+
import logging
1417

1518
if __name__ == "__main__":
1619
Utils.init_logging("WargrooveClient", exception_logger="Client")
20+
Utils.init_logging("WG", exception_logger="Client")
1721

1822
from NetUtils import NetworkItem, ClientStatus
1923
from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \
2024
CommonContext, server_loop
2125

26+
wg_logger = logging.getLogger("WG")
27+
2228

2329
class WargrooveClientCommandProcessor(ClientCommandProcessor):
2430
def _cmd_resync(self):
@@ -33,23 +39,32 @@ def _cmd_commander(self, *commander_name: Iterable[str]):
3339
else:
3440
if self.ctx.can_choose_commander:
3541
commanders = self.ctx.get_commanders()
36-
logger.info('Unlocked commanders: ' +
37-
', '.join((commander.name for commander, unlocked in commanders if unlocked))
38-
)
39-
logger.info('Locked commanders: ' +
40-
', '.join((commander.name for commander, unlocked in commanders if not unlocked))
41-
)
42+
wg_logger.info('Unlocked commanders: ' +
43+
', '.join((commander.name for commander, unlocked in commanders if unlocked)))
44+
wg_logger.info('Locked commanders: ' +
45+
', '.join((commander.name for commander, unlocked in commanders if not unlocked)))
4246
else:
43-
logger.error('Cannot set commanders in this game mode.')
47+
wg_logger.error('Cannot set commanders in this game mode.')
4448

4549

4650
class WargrooveContext(CommonContext):
4751
command_processor: int = WargrooveClientCommandProcessor
4852
game = "Wargroove"
4953
items_handling = 0b111 # full remote
5054
current_commander: CommanderData = faction_table["Starter"][0]
51-
can_choose_commander: bool
55+
can_choose_commander: bool = False
56+
commander_defense_boost_multiplier: int = 0
57+
income_boost_multiplier: int = 0
5258
starting_groove_multiplier: float
59+
faction_item_ids = {
60+
'Starter': 0,
61+
'Cherrystone': 52025,
62+
'Felheim': 52026,
63+
'Floran': 52027,
64+
'Heavensong': 52028,
65+
'Requiem': 52029,
66+
'Outlaw': 52030
67+
}
5368

5469
def __init__(self, server_address, password):
5570
super(WargrooveContext, self).__init__(server_address, password)
@@ -108,13 +123,17 @@ def on_package(self, cmd: str, args: dict):
108123
slot_data = args["slot_data"]
109124
json.dump(args["slot_data"], f)
110125
self.can_choose_commander = slot_data["can_choose_commander"]
126+
print('can choose commander:', self.can_choose_commander)
111127
self.starting_groove_multiplier = slot_data["starting_groove_multiplier"]
128+
self.income_boost_multiplier = slot_data["income_boost"]
129+
self.commander_defense_boost_multiplier = slot_data["commander_defense_boost"]
112130
f.close()
113131
for ss in self.checked_locations:
114132
filename = f"send{ss}"
115133
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
116134
f.close()
117135
self.update_commander_data()
136+
self.ui.update_tracker()
118137

119138
random.seed(self.seed_name + str(self.slot))
120139
# Our indexes start at 1 and we have 23 levels
@@ -168,6 +187,7 @@ def on_package(self, cmd: str, args: dict):
168187
self.player_names[network_item.player])
169188
f.close()
170189
self.update_commander_data()
190+
self.ui.update_tracker()
171191

172192
if cmd in {"RoomUpdate"}:
173193
if "checked_locations" in args:
@@ -178,15 +198,128 @@ def on_package(self, cmd: str, args: dict):
178198

179199
def run_gui(self):
180200
"""Import kivy UI system and start running it as self.ui_task."""
181-
from kvui import GameManager
201+
from kvui import GameManager, HoverBehavior, ServerToolTip
202+
from kivy.uix.tabbedpanel import TabbedPanelItem
203+
from kivy.lang import Builder
204+
from kivy.uix.button import Button
205+
from kivy.uix.togglebutton import ToggleButton
206+
from kivy.uix.boxlayout import BoxLayout
207+
from kivy.uix.gridlayout import GridLayout
208+
from kivy.uix.image import AsyncImage, Image
209+
from kivy.uix.stacklayout import StackLayout
210+
from kivy.uix.label import Label
211+
from kivy.properties import ColorProperty
212+
from kivy.uix.image import Image
213+
import pkgutil
214+
215+
class TrackerLayout(BoxLayout):
216+
pass
217+
218+
class CommanderSelect(BoxLayout):
219+
pass
220+
221+
class CommanderButton(ToggleButton):
222+
pass
223+
224+
class FactionBox(BoxLayout):
225+
pass
226+
227+
class CommanderGroup(BoxLayout):
228+
pass
229+
230+
class ItemTracker(BoxLayout):
231+
pass
232+
233+
class ItemLabel(Label):
234+
pass
182235

183236
class WargrooveManager(GameManager):
184237
logging_pairs = [
185-
("Client", "Archipelago")
238+
("Client", "Archipelago"),
239+
("WG", "WG Console"),
186240
]
187241
base_title = "Archipelago Wargroove Client"
242+
ctx: WargrooveContext
243+
unit_tracker: ItemTracker
244+
trigger_tracker: BoxLayout
245+
boost_tracker: BoxLayout
246+
commander_buttons: Dict[int, List[CommanderButton]]
247+
tracker_items = {
248+
"Swordsman": ItemData(None, "Unit", False),
249+
"Dog": ItemData(None, "Unit", False),
250+
**item_table
251+
}
252+
253+
def build(self):
254+
container = super().build()
255+
panel = TabbedPanelItem(text="Wargroove")
256+
panel.content = self.build_tracker()
257+
self.tabs.add_widget(panel)
258+
return container
259+
260+
def build_tracker(self) -> TrackerLayout:
261+
try:
262+
tracker = TrackerLayout(orientation="horizontal")
263+
commander_select = CommanderSelect(orientation="vertical")
264+
self.commander_buttons = {}
265+
266+
for faction, commanders in faction_table.items():
267+
faction_box = FactionBox(size_hint=(None, None), width=100 * len(commanders), height=70)
268+
commander_group = CommanderGroup()
269+
commander_buttons = []
270+
for commander in commanders:
271+
commander_button = CommanderButton(text=commander.name, group="commanders")
272+
if faction == "Starter":
273+
commander_button.disabled = False
274+
commander_button.bind(on_press=lambda instance: self.ctx.set_commander(instance.text))
275+
commander_buttons.append(commander_button)
276+
commander_group.add_widget(commander_button)
277+
self.commander_buttons[faction] = commander_buttons
278+
faction_box.add_widget(Label(text=faction, size_hint_x=None, pos_hint={'left': 1}, size_hint_y=None, height=10))
279+
faction_box.add_widget(commander_group)
280+
commander_select.add_widget(faction_box)
281+
item_tracker = ItemTracker(padding=[0,20])
282+
self.unit_tracker = BoxLayout(orientation="vertical")
283+
other_tracker = BoxLayout(orientation="vertical")
284+
self.trigger_tracker = BoxLayout(orientation="vertical")
285+
self.boost_tracker = BoxLayout(orientation="vertical")
286+
other_tracker.add_widget(self.trigger_tracker)
287+
other_tracker.add_widget(self.boost_tracker)
288+
item_tracker.add_widget(self.unit_tracker)
289+
item_tracker.add_widget(other_tracker)
290+
tracker.add_widget(commander_select)
291+
tracker.add_widget(item_tracker)
292+
self.update_tracker()
293+
return tracker
294+
except Exception as e:
295+
print(e)
296+
297+
def update_tracker(self):
298+
received_ids = [item.item for item in self.ctx.items_received]
299+
for faction, item_id in self.ctx.faction_item_ids.items():
300+
for commander_button in self.commander_buttons[faction]:
301+
commander_button.disabled = not (faction == "Starter" or item_id in received_ids)
302+
self.unit_tracker.clear_widgets()
303+
self.trigger_tracker.clear_widgets()
304+
for name, item in self.tracker_items.items():
305+
if item.type in ("Unit", "Trigger"):
306+
status_color = (1, 1, 1, 1) if item.code is None or item.code in received_ids else (0.6, 0.2, 0.2, 1)
307+
label = ItemLabel(text=name, color=status_color)
308+
if item.type == "Unit":
309+
self.unit_tracker.add_widget(label)
310+
else:
311+
self.trigger_tracker.add_widget(label)
312+
self.boost_tracker.clear_widgets()
313+
extra_income = received_ids.count(52023) * self.ctx.income_boost_multiplier
314+
extra_defense = received_ids.count(52024) * self.ctx.commander_defense_boost_multiplier
315+
income_boost = ItemLabel(text="Extra Income: " + str(extra_income))
316+
defense_boost = ItemLabel(text="Comm Defense: " + str(100 + extra_defense))
317+
self.boost_tracker.add_widget(income_boost)
318+
self.boost_tracker.add_widget(defense_boost)
188319

189320
self.ui = WargrooveManager(self)
321+
data = pkgutil.get_data(WargrooveWorld.__module__, "Wargroove.kv").decode()
322+
Builder.load_string(data)
190323
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
191324

192325
def update_commander_data(self):
@@ -211,34 +344,35 @@ def update_commander_data(self):
211344
filename = 'commander.json'
212345
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
213346
json.dump(data, f)
347+
if self.ui:
348+
self.ui.update_tracker()
214349

215350
def set_commander(self, commander_name: str) -> bool:
216351
"""Sets the current commander to the given one, if possible"""
217352
if not self.can_choose_commander:
218-
logger.error("Cannot set commanders in this game mode.")
353+
wg_logger.error("Cannot set commanders in this game mode.")
219354
return
220355
match_name = commander_name.lower()
221356
for commander, unlocked in self.get_commanders():
222357
if commander.name.lower() == match_name or commander.alt_name and commander.alt_name.lower() == match_name:
223358
if unlocked:
224359
self.current_commander = commander
225360
self.syncing = True
226-
logger.info(f"Commander set to {commander.name}.")
361+
wg_logger.info(f"Commander set to {commander.name}.")
227362
self.update_commander_data()
228363
return True
229364
else:
230-
logger.error(f"Commander {commander.name} has not been unlocked.")
365+
wg_logger.error(f"Commander {commander.name} has not been unlocked.")
231366
return False
232367
else:
233-
logger.error(f"{commander_name} is not a recognized Wargroove commander.")
368+
wg_logger.error(f"{commander_name} is not a recognized Wargroove commander.")
234369

235370
def get_commanders(self) -> List[Tuple[CommanderData, bool]]:
236371
"""Gets a list of commanders with their unlocked status"""
237-
received_item_names = (self.item_names[network_item.item] for network_item in self.items_received)
238-
received_factions = {item_name[:-11] for item_name in received_item_names if item_name.endswith(' Commanders')}
239372
commanders = []
373+
received_ids = [item.item for item in self.items_received]
240374
for faction in faction_table.keys():
241-
unlocked = faction == 'Starter' or str(faction) in received_factions
375+
unlocked = faction == 'Starter' or self.faction_item_ids[faction] in received_ids
242376
commanders += [(commander, unlocked) for commander in faction_table[faction]]
243377
return commanders
244378

worlds/wargroove/Items.py

+33-32
Original file line numberDiff line numberDiff line change
@@ -6,53 +6,54 @@
66

77
class ItemData(typing.NamedTuple):
88
code: typing.Optional[int]
9+
type: str
910
progression: bool
1011
filler: bool = False
1112

1213

1314
item_table: Dict[str, ItemData] = {
1415
# Units
15-
'Spearman': ItemData(52000, True),
16-
'Wagon': ItemData(52001, False),
17-
'Mage': ItemData(52002, True),
18-
'Archer': ItemData(52003, True),
19-
'Knight': ItemData(52004, True),
20-
'Ballista': ItemData(52005, True),
21-
'Golem': ItemData(52006, False),
22-
'Harpy': ItemData(52007, True),
23-
'Witch': ItemData(52008, False),
24-
'Dragon': ItemData(52009, False),
25-
'Balloon': ItemData(52010, False),
26-
'Barge': ItemData(52011, True),
27-
'Merfolk': ItemData(52012, True),
28-
'Turtle': ItemData(52013, True),
29-
'Harpoon Ship': ItemData(52014, True),
30-
'Warship': ItemData(52015, True),
31-
'Thief': ItemData(52016, True),
32-
'Rifleman': ItemData(52017, False),
16+
'Spearman': ItemData(52000, 'Unit', True),
17+
'Wagon': ItemData(52001, 'Unit', False),
18+
'Mage': ItemData(52002, 'Unit', True),
19+
'Archer': ItemData(52003, 'Unit', True),
20+
'Knight': ItemData(52004, 'Unit', True),
21+
'Ballista': ItemData(52005, 'Unit', True),
22+
'Golem': ItemData(52006, 'Unit', False),
23+
'Harpy': ItemData(52007, 'Unit', True),
24+
'Witch': ItemData(52008, 'Unit', False),
25+
'Dragon': ItemData(52009, 'Unit', False),
26+
'Balloon': ItemData(52010, 'Unit', False),
27+
'Barge': ItemData(52011, 'Unit', True),
28+
'Merfolk': ItemData(52012, 'Unit', True),
29+
'Turtle': ItemData(52013, 'Unit', True),
30+
'Harpoon Ship': ItemData(52014, 'Unit', True),
31+
'Warship': ItemData(52015, 'Unit', True),
32+
'Thief': ItemData(52016, 'Unit', True),
33+
'Rifleman': ItemData(52017, 'Unit', False),
3334

3435
# Map Triggers
35-
'Eastern Bridges': ItemData(52018, True),
36-
'Southern Walls': ItemData(52019, True),
37-
'Final Bridges': ItemData(52020, True),
38-
'Final Walls': ItemData(52021, True),
39-
'Final Sickle': ItemData(52022, True),
36+
'Eastern Bridges': ItemData(52018, 'Trigger', True),
37+
'Southern Walls': ItemData(52019, 'Trigger', True),
38+
'Final Bridges': ItemData(52020, 'Trigger', True),
39+
'Final Walls': ItemData(52021, 'Trigger', True),
40+
'Final Sickle': ItemData(52022, 'Trigger', True),
4041

4142
# Player Buffs
42-
'Income Boost': ItemData(52023, False, True),
43+
'Income Boost': ItemData(52023, 'Boost', False, True),
4344

44-
'Commander Defense Boost': ItemData(52024, False, True),
45+
'Commander Defense Boost': ItemData(52024, 'Boost', False, True),
4546

4647
# Factions
47-
'Cherrystone Commanders': ItemData(52025, False),
48-
'Felheim Commanders': ItemData(52026, False),
49-
'Floran Commanders': ItemData(52027, False),
50-
'Heavensong Commanders': ItemData(52028, False),
51-
'Requiem Commanders': ItemData(52029, False),
52-
'Outlaw Commanders': ItemData(52030, False),
48+
'Cherrystone Commanders': ItemData(52025, 'Faction', False),
49+
'Felheim Commanders': ItemData(52026, 'Faction', False),
50+
'Floran Commanders': ItemData(52027, 'Faction', False),
51+
'Heavensong Commanders': ItemData(52028, 'Faction', False),
52+
'Requiem Commanders': ItemData(52029, 'Faction', False),
53+
'Outlaw Commanders': ItemData(52030, 'Faction', False),
5354

5455
# Event Items
55-
'Wargroove Victory': ItemData(None, True, True)
56+
'Wargroove Victory': ItemData(None, 'Goal', True)
5657

5758
}
5859

worlds/wargroove/Wargroove.kv

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<FactionBox>:
2+
orientation: 'vertical'
3+
padding: [10,5,10,5]
4+
5+
<CommanderGroup>:
6+
orientation: 'horizontal'
7+
8+
<CommanderButton>:
9+
text_size: self.size
10+
size_hint: (None, None)
11+
height: 40
12+
width: 100
13+
markup: True
14+
halign: 'center'
15+
valign: 'middle'
16+
padding_x: 5
17+
markup: True
18+
outline_width: 1
19+
disabled: True
20+
on_release: setattr(self, 'state', 'down')
21+
22+
<ItemTracker>:
23+
orientation: 'horizontal'
24+
padding_y: 5
25+
26+
<ItemLabel>:
27+
size_hint_x: None
28+
size: self.texture_size
29+
pos_hint: {'left': 1}

0 commit comments

Comments
 (0)