| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | import os | 
					
						
							|  |  |  | import logging | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  | import typing | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | os.environ["KIVY_NO_CONSOLELOG"] = "1" | 
					
						
							|  |  |  | os.environ["KIVY_NO_FILELOG"] = "1" | 
					
						
							|  |  |  | os.environ["KIVY_NO_ARGS"] = "1" | 
					
						
							| 
									
										
										
										
											2021-11-08 18:57:03 +01:00
										 |  |  | os.environ["KIVY_LOG_ENABLE"] = "0" | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-14 08:55:57 +02:00
										 |  |  | import Utils | 
					
						
							|  |  |  | if Utils.is_frozen(): | 
					
						
							|  |  |  |     os.environ["KIVY_DATA_DIR"] = Utils.local_path("data") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-03 13:38:55 +02:00
										 |  |  | from kivy.config import Config | 
					
						
							| 
									
										
										
										
											2022-02-24 04:47:01 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-10 09:29:59 +01:00
										 |  |  | Config.set("input", "mouse", "mouse,disable_multitouch") | 
					
						
							|  |  |  | Config.set('kivy', 'exit_on_escape', '0') | 
					
						
							|  |  |  | Config.set('graphics', 'multisamples', '0')  # multisamples crash old intel drivers | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | from kivy.app import App | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  | from kivy.core.window import Window | 
					
						
							| 
									
										
										
										
											2021-11-19 21:25:01 +01:00
										 |  |  | from kivy.core.clipboard import Clipboard | 
					
						
							| 
									
										
										
										
											2021-11-21 23:45:15 +01:00
										 |  |  | from kivy.core.text.markup import MarkupLabel | 
					
						
							| 
									
										
										
										
											2022-06-03 13:38:55 +02:00
										 |  |  | from kivy.base import ExceptionHandler, ExceptionManager | 
					
						
							|  |  |  | from kivy.clock import Clock | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  | from kivy.factory import Factory | 
					
						
							|  |  |  | from kivy.properties import BooleanProperty, ObjectProperty | 
					
						
							| 
									
										
										
										
											2023-02-13 01:55:43 +01:00
										 |  |  | from kivy.uix.widget import Widget | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  | from kivy.uix.button import Button | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | from kivy.uix.gridlayout import GridLayout | 
					
						
							| 
									
										
										
										
											2022-10-27 02:30:22 -07:00
										 |  |  | from kivy.uix.layout import Layout | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | from kivy.uix.textinput import TextInput | 
					
						
							|  |  |  | from kivy.uix.recycleview import RecycleView | 
					
						
							|  |  |  | from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  | from kivy.uix.boxlayout import BoxLayout | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  | from kivy.uix.floatlayout import FloatLayout | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  | from kivy.uix.label import Label | 
					
						
							| 
									
										
										
										
											2021-10-22 05:25:09 +02:00
										 |  |  | from kivy.uix.progressbar import ProgressBar | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | from kivy.utils import escape_markup | 
					
						
							|  |  |  | from kivy.lang import Builder | 
					
						
							| 
									
										
										
										
											2021-11-19 21:25:01 +01:00
										 |  |  | from kivy.uix.recycleview.views import RecycleDataViewBehavior | 
					
						
							|  |  |  | from kivy.uix.behaviors import FocusBehavior | 
					
						
							|  |  |  | from kivy.uix.recycleboxlayout import RecycleBoxLayout | 
					
						
							|  |  |  | from kivy.uix.recycleview.layout import LayoutSelectionBehavior | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  | from kivy.animation import Animation | 
					
						
							| 
									
										
										
										
											2022-06-04 17:02:02 +02:00
										 |  |  | from kivy.uix.popup import Popup | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | fade_in_animation = Animation(opacity=0, duration=0) + Animation(opacity=1, duration=0.25) | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-14 08:55:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  | from NetUtils import JSONtoTextParser, JSONMessagePart, SlotType | 
					
						
							| 
									
										
										
										
											2022-11-02 07:51:35 -07:00
										 |  |  | from Utils import async_start | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  | if typing.TYPE_CHECKING: | 
					
						
							|  |  |  |     import CommonClient | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     context_type = CommonClient.CommonContext | 
					
						
							|  |  |  | else: | 
					
						
							|  |  |  |     context_type = object | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-30 09:09:21 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  | # I was surprised to find this didn't already exist in kivy :( | 
					
						
							|  |  |  | class HoverBehavior(object): | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |     """originally from https://stackoverflow.com/a/605348110""" | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |     hovered = BooleanProperty(False) | 
					
						
							|  |  |  |     border_point = ObjectProperty(None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, **kwargs): | 
					
						
							|  |  |  |         self.register_event_type('on_enter') | 
					
						
							|  |  |  |         self.register_event_type('on_leave') | 
					
						
							|  |  |  |         Window.bind(mouse_pos=self.on_mouse_pos) | 
					
						
							| 
									
										
										
										
											2021-10-31 08:07:37 -07:00
										 |  |  |         Window.bind(on_cursor_leave=self.on_cursor_leave) | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         super(HoverBehavior, self).__init__(**kwargs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |     def on_mouse_pos(self, window, pos): | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         if not self.get_root_window(): | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |             return  # Abort if not displayed | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # to_widget translates window pos to within widget pos | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         inside = self.collide_point(*self.to_widget(*pos)) | 
					
						
							|  |  |  |         if self.hovered == inside: | 
					
						
							|  |  |  |             return  # We have already done what was needed | 
					
						
							|  |  |  |         self.border_point = pos | 
					
						
							|  |  |  |         self.hovered = inside | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if inside: | 
					
						
							|  |  |  |             self.dispatch("on_enter") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.dispatch("on_leave") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-31 08:07:37 -07:00
										 |  |  |     def on_cursor_leave(self, *args): | 
					
						
							|  |  |  |         # if the mouse left the window, it is obviously no longer inside the hover label. | 
					
						
							|  |  |  |         self.hovered = BooleanProperty(False) | 
					
						
							|  |  |  |         self.border_point = ObjectProperty(None) | 
					
						
							|  |  |  |         self.dispatch("on_leave") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | Factory.register('HoverBehavior', HoverBehavior) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  | class ToolTip(Label): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ServerToolTip(ToolTip): | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 06:17:39 +01:00
										 |  |  | class HovererableLabel(HoverBehavior, Label): | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ServerLabel(HovererableLabel): | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |     def __init__(self, *args, **kwargs): | 
					
						
							| 
									
										
										
										
											2022-02-24 06:17:39 +01:00
										 |  |  |         super(HovererableLabel, self).__init__(*args, **kwargs) | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         self.layout = FloatLayout() | 
					
						
							|  |  |  |         self.popuplabel = ServerToolTip(text="Test") | 
					
						
							|  |  |  |         self.layout.add_widget(self.popuplabel) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def on_enter(self): | 
					
						
							|  |  |  |         self.popuplabel.text = self.get_text() | 
					
						
							|  |  |  |         App.get_running_app().root.add_widget(self.layout) | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |         fade_in_animation.start(self.layout) | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def on_leave(self): | 
					
						
							|  |  |  |         App.get_running_app().root.remove_widget(self.layout) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 06:17:39 +01:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def ctx(self) -> context_type: | 
					
						
							|  |  |  |         return App.get_running_app().ctx | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |     def get_text(self): | 
					
						
							|  |  |  |         if self.ctx.server: | 
					
						
							|  |  |  |             ctx = self.ctx | 
					
						
							|  |  |  |             text = f"Connected to: {ctx.server_address}." | 
					
						
							|  |  |  |             if ctx.slot is not None: | 
					
						
							| 
									
										
										
										
											2021-10-30 07:52:03 +02:00
										 |  |  |                 text += f"\nYou are Slot Number {ctx.slot} in Team Number {ctx.team}, " \ | 
					
						
							|  |  |  |                         f"named {ctx.player_names[ctx.slot]}." | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |                 if ctx.items_received: | 
					
						
							|  |  |  |                     text += f"\nYou have received {len(ctx.items_received)} items. " \ | 
					
						
							|  |  |  |                             f"You can list them in order with /received." | 
					
						
							|  |  |  |                 if ctx.total_locations: | 
					
						
							|  |  |  |                     text += f"\nYou have checked {len(ctx.checked_locations)} " \ | 
					
						
							|  |  |  |                             f"out of {ctx.total_locations} locations. " \ | 
					
						
							|  |  |  |                             f"You can get more info on missing checks with /missing." | 
					
						
							|  |  |  |                 if ctx.permissions: | 
					
						
							|  |  |  |                     text += "\nPermissions:" | 
					
						
							|  |  |  |                     for permission_name, permission_data in ctx.permissions.items(): | 
					
						
							|  |  |  |                         text += f"\n    {permission_name}: {permission_data}" | 
					
						
							| 
									
										
										
										
											2021-11-07 14:42:05 +01:00
										 |  |  |                 if ctx.hint_cost is not None and ctx.total_locations: | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |                     text += f"\nA new !hint <itemname> costs {ctx.hint_cost}% of checks made. " \ | 
					
						
							| 
									
										
										
										
											2021-10-30 07:52:03 +02:00
										 |  |  |                             f"For you this means every {max(0, int(ctx.hint_cost * 0.01 * ctx.total_locations))} " \ | 
					
						
							|  |  |  |                             "location checks." | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |                 elif ctx.hint_cost == 0: | 
					
						
							|  |  |  |                     text += "\n!hint is free to use." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 text += f"\nYou are not authenticated yet." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return text | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return "No current server connection. \nPlease connect to an Archipelago server." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MainLayout(GridLayout): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ContainerLayout(FloatLayout): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-19 21:25:01 +01:00
										 |  |  | class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior, | 
					
						
							|  |  |  |                                  RecycleBoxLayout): | 
					
						
							|  |  |  |     """ Adds selection and focus behaviour to the view. """ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  | class SelectableLabel(RecycleDataViewBehavior, HovererableLabel): | 
					
						
							| 
									
										
										
										
											2021-11-19 21:25:01 +01:00
										 |  |  |     """ Add selection support to the Label """ | 
					
						
							|  |  |  |     index = None | 
					
						
							|  |  |  |     selected = BooleanProperty(False) | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |     tooltip = None | 
					
						
							| 
									
										
										
										
											2021-11-19 21:25:01 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def refresh_view_attrs(self, rv, index, data): | 
					
						
							|  |  |  |         """ Catch and handle the view changes """ | 
					
						
							|  |  |  |         self.index = index | 
					
						
							|  |  |  |         return super(SelectableLabel, self).refresh_view_attrs( | 
					
						
							|  |  |  |             rv, index, data) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |     def create_tooltip(self, text, x, y): | 
					
						
							| 
									
										
										
										
											2022-05-25 19:05:34 +02:00
										 |  |  |         text = text.replace("<br>", "\n").replace('&', '&').replace('&bl;', '[').replace('&br;', ']') | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |         if self.tooltip: | 
					
						
							|  |  |  |             # update | 
					
						
							|  |  |  |             self.tooltip.children[0].text = text | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.tooltip = FloatLayout() | 
					
						
							|  |  |  |             tooltip_label = ToolTip(text=text) | 
					
						
							|  |  |  |             self.tooltip.add_widget(tooltip_label) | 
					
						
							|  |  |  |             fade_in_animation.start(self.tooltip) | 
					
						
							|  |  |  |             App.get_running_app().root.add_widget(self.tooltip) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # handle left-side boundary to not render off-screen | 
					
						
							|  |  |  |         x = max(x, 3+self.tooltip.children[0].texture_size[0] / 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # position float layout | 
					
						
							|  |  |  |         self.tooltip.x = x - self.tooltip.width / 2 | 
					
						
							|  |  |  |         self.tooltip.y = y - self.tooltip.height / 2 + 48 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def remove_tooltip(self): | 
					
						
							|  |  |  |         if self.tooltip: | 
					
						
							|  |  |  |             App.get_running_app().root.remove_widget(self.tooltip) | 
					
						
							|  |  |  |             self.tooltip = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def on_mouse_pos(self, window, pos): | 
					
						
							|  |  |  |         if not self.get_root_window(): | 
					
						
							|  |  |  |             return  # Abort if not displayed | 
					
						
							|  |  |  |         super().on_mouse_pos(window, pos) | 
					
						
							|  |  |  |         if self.refs and self.hovered: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             tx, ty = self.to_widget(*pos, relative=True) | 
					
						
							|  |  |  |             # Why TF is Y flipped *within* the texture? | 
					
						
							|  |  |  |             ty = self.texture_size[1] - ty | 
					
						
							|  |  |  |             hit = False | 
					
						
							|  |  |  |             for uid, zones in self.refs.items(): | 
					
						
							|  |  |  |                 for zone in zones: | 
					
						
							|  |  |  |                     x, y, w, h = zone | 
					
						
							|  |  |  |                     if x <= tx <= w and y <= ty <= h: | 
					
						
							|  |  |  |                         self.create_tooltip(uid.split("|", 1)[1], *pos) | 
					
						
							|  |  |  |                         hit = True | 
					
						
							|  |  |  |                         break | 
					
						
							|  |  |  |             if not hit: | 
					
						
							|  |  |  |                 self.remove_tooltip() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def on_enter(self): | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def on_leave(self): | 
					
						
							|  |  |  |         self.remove_tooltip() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-19 21:25:01 +01:00
										 |  |  |     def on_touch_down(self, touch): | 
					
						
							|  |  |  |         """ Add selection on touch down """ | 
					
						
							|  |  |  |         if super(SelectableLabel, self).on_touch_down(touch): | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         if self.collide_point(*touch.pos): | 
					
						
							| 
									
										
										
										
											2021-11-22 17:44:14 +01:00
										 |  |  |             if self.selected: | 
					
						
							|  |  |  |                 self.parent.clear_selection() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 # Not a fan of the following few lines, but they work. | 
					
						
							|  |  |  |                 temp = MarkupLabel(text=self.text).markup | 
					
						
							| 
									
										
										
										
											2022-05-25 19:05:34 +02:00
										 |  |  |                 text = "".join(part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]"))) | 
					
						
							| 
									
										
										
										
											2021-11-22 17:44:14 +01:00
										 |  |  |                 cmdinput = App.get_running_app().textinput | 
					
						
							| 
									
										
										
										
											2021-11-28 01:51:13 +01:00
										 |  |  |                 if not cmdinput.text and " did you mean " in text: | 
					
						
							|  |  |  |                     for question in ("Didn't find something that closely matches, did you mean ", | 
					
						
							|  |  |  |                                      "Too many close matches, did you mean "): | 
					
						
							|  |  |  |                         if text.startswith(question): | 
					
						
							|  |  |  |                             name = Utils.get_text_between(text, question, | 
					
						
							|  |  |  |                                                           "? (") | 
					
						
							| 
									
										
										
										
											2021-11-29 21:35:06 +01:00
										 |  |  |                             cmdinput.text = f"!{App.get_running_app().last_autofillable_command} {name}" | 
					
						
							| 
									
										
										
										
											2021-11-28 01:51:13 +01:00
										 |  |  |                             break | 
					
						
							| 
									
										
										
										
											2022-02-08 14:29:24 -08:00
										 |  |  |                 elif not cmdinput.text and text.startswith("Missing: "): | 
					
						
							|  |  |  |                     cmdinput.text = text.replace("Missing: ", "!hint_location ") | 
					
						
							| 
									
										
										
										
											2021-11-28 01:51:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-25 19:05:34 +02:00
										 |  |  |                 Clipboard.copy(text.replace('&', '&').replace('&bl;', '[').replace('&br;', ']')) | 
					
						
							| 
									
										
										
										
											2021-11-22 17:44:14 +01:00
										 |  |  |                 return self.parent.select_with_touch(self.index, touch) | 
					
						
							| 
									
										
										
										
											2021-11-19 21:25:01 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def apply_selection(self, rv, index, is_selected): | 
					
						
							|  |  |  |         """ Respond to the selection of items in the view. """ | 
					
						
							|  |  |  |         self.selected = is_selected | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-28 18:25:07 -08:00
										 |  |  | class ConnectBarTextInput(TextInput): | 
					
						
							|  |  |  |     def insert_text(self, substring, from_undo=False): | 
					
						
							|  |  |  |         s = substring.replace('\n', '').replace('\r', '') | 
					
						
							|  |  |  |         return super(ConnectBarTextInput, self).insert_text(s, from_undo=from_undo) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-04 17:02:02 +02:00
										 |  |  | class MessageBox(Popup): | 
					
						
							|  |  |  |     class MessageBoxLabel(Label): | 
					
						
							|  |  |  |         def __init__(self, **kwargs): | 
					
						
							|  |  |  |             super().__init__(**kwargs) | 
					
						
							|  |  |  |             self._label.refresh() | 
					
						
							|  |  |  |             self.size = self._label.texture.size | 
					
						
							|  |  |  |             if self.width + 50 > Window.width: | 
					
						
							|  |  |  |                 self.text_size[0] = Window.width - 50 | 
					
						
							|  |  |  |                 self._label.refresh() | 
					
						
							|  |  |  |                 self.size = self._label.texture.size | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, title, text, error=False, **kwargs): | 
					
						
							|  |  |  |         label = MessageBox.MessageBoxLabel(text=text) | 
					
						
							|  |  |  |         separator_color = [217 / 255, 129 / 255, 122 / 255, 1.] if error else [47 / 255., 167 / 255., 212 / 255, 1.] | 
					
						
							|  |  |  |         super().__init__(title=title, content=label, size_hint=(None, None), width=max(100, int(label.width)+40), | 
					
						
							|  |  |  |                          separator_color=separator_color, **kwargs) | 
					
						
							|  |  |  |         self.height += max(0, label.height - 18) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | class GameManager(App): | 
					
						
							|  |  |  |     logging_pairs = [ | 
					
						
							|  |  |  |         ("Client", "Archipelago"), | 
					
						
							|  |  |  |     ] | 
					
						
							| 
									
										
										
										
											2021-11-29 21:35:06 +01:00
										 |  |  |     base_title: str = "Archipelago Client" | 
					
						
							|  |  |  |     last_autofillable_command: str | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 02:30:22 -07:00
										 |  |  |     main_area_container: GridLayout | 
					
						
							|  |  |  |     """ subclasses can add more columns beside the tabs """ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |     def __init__(self, ctx: context_type): | 
					
						
							|  |  |  |         self.title = self.base_title | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         self.ctx = ctx | 
					
						
							|  |  |  |         self.commandprocessor = ctx.command_processor(ctx) | 
					
						
							|  |  |  |         self.icon = r"data/icon.png" | 
					
						
							|  |  |  |         self.json_to_kivy_parser = KivyJSONtoTextParser(ctx) | 
					
						
							|  |  |  |         self.log_panels = {} | 
					
						
							| 
									
										
										
										
											2021-11-29 21:35:06 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # keep track of last used command to autofill on click | 
					
						
							|  |  |  |         self.last_autofillable_command = "hint" | 
					
						
							| 
									
										
										
										
											2021-12-02 03:14:26 +01:00
										 |  |  |         autofillable_commands = ("hint_location", "hint", "getitem") | 
					
						
							| 
									
										
										
										
											2021-11-29 21:35:06 +01:00
										 |  |  |         original_say = ctx.on_user_say | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def intercept_say(text): | 
					
						
							|  |  |  |             text = original_say(text) | 
					
						
							|  |  |  |             if text: | 
					
						
							|  |  |  |                 for command in autofillable_commands: | 
					
						
							| 
									
										
										
										
											2022-02-24 04:47:01 +01:00
										 |  |  |                     if text.startswith("!" + command): | 
					
						
							| 
									
										
										
										
											2021-11-29 21:35:06 +01:00
										 |  |  |                         self.last_autofillable_command = command | 
					
						
							|  |  |  |                         break | 
					
						
							|  |  |  |             return text | 
					
						
							| 
									
										
										
										
											2022-02-24 04:47:01 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 21:35:06 +01:00
										 |  |  |         ctx.on_user_say = intercept_say | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         super(GameManager, self).__init__() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-24 03:42:34 +01:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def tab_count(self): | 
					
						
							|  |  |  |         if hasattr(self, "tabs"): | 
					
						
							|  |  |  |             return max(1, len(self.tabs.tab_list)) | 
					
						
							|  |  |  |         return 1 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 02:30:22 -07:00
										 |  |  |     def build(self) -> Layout: | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         self.container = ContainerLayout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.grid = MainLayout() | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         self.grid.cols = 1 | 
					
						
							| 
									
										
										
										
											2022-02-24 04:47:01 +01:00
										 |  |  |         self.connect_layout = BoxLayout(orientation="horizontal", size_hint_y=None, height=30) | 
					
						
							| 
									
										
										
										
											2021-10-22 05:25:09 +02:00
										 |  |  |         # top part | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         server_label = ServerLabel() | 
					
						
							| 
									
										
										
										
											2022-02-24 04:47:01 +01:00
										 |  |  |         self.connect_layout.add_widget(server_label) | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  |         self.server_connect_bar = ConnectBarTextInput(text=self.ctx.suggested_address or "archipelago.gg:", size_hint_y=None, | 
					
						
							| 
									
										
										
										
											2022-06-27 03:10:41 -07:00
										 |  |  |                                                       height=30, multiline=False, write_tab=False) | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  |         def connect_bar_validate(sender): | 
					
						
							|  |  |  |             if not self.ctx.server: | 
					
						
							|  |  |  |                 self.connect_button_action(sender) | 
					
						
							|  |  |  |         self.server_connect_bar.bind(on_text_validate=connect_bar_validate) | 
					
						
							| 
									
										
										
										
											2022-02-24 04:47:01 +01:00
										 |  |  |         self.connect_layout.add_widget(self.server_connect_bar) | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |         self.server_connect_button = Button(text="Connect", size=(100, 30), size_hint_y=None, size_hint_x=None) | 
					
						
							|  |  |  |         self.server_connect_button.bind(on_press=self.connect_button_action) | 
					
						
							| 
									
										
										
										
											2022-02-24 04:47:01 +01:00
										 |  |  |         self.connect_layout.add_widget(self.server_connect_button) | 
					
						
							|  |  |  |         self.grid.add_widget(self.connect_layout) | 
					
						
							| 
									
										
										
										
											2021-10-22 05:25:09 +02:00
										 |  |  |         self.progressbar = ProgressBar(size_hint_y=None, height=3) | 
					
						
							|  |  |  |         self.grid.add_widget(self.progressbar) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # middle part | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |         self.tabs = TabbedPanel(size_hint_y=1) | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         self.tabs.default_tab_text = "All" | 
					
						
							|  |  |  |         self.log_panels["All"] = self.tabs.default_tab_content = UILog(*(logging.getLogger(logger_name) | 
					
						
							|  |  |  |                                                                          for logger_name, name in | 
					
						
							|  |  |  |                                                                          self.logging_pairs)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for logger_name, display_name in self.logging_pairs: | 
					
						
							|  |  |  |             bridge_logger = logging.getLogger(logger_name) | 
					
						
							|  |  |  |             panel = TabbedPanelItem(text=display_name) | 
					
						
							|  |  |  |             self.log_panels[display_name] = panel.content = UILog(bridge_logger) | 
					
						
							|  |  |  |             self.tabs.add_widget(panel) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 02:30:22 -07:00
										 |  |  |         self.main_area_container = GridLayout(size_hint_y=1, rows=1) | 
					
						
							|  |  |  |         self.main_area_container.add_widget(self.tabs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.grid.add_widget(self.main_area_container) | 
					
						
							| 
									
										
										
										
											2021-10-22 00:37:20 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if len(self.logging_pairs) == 1: | 
					
						
							|  |  |  |             # Hide Tab selection if only one tab | 
					
						
							|  |  |  |             self.tabs.clear_tabs() | 
					
						
							|  |  |  |             self.tabs.do_default_tab = False | 
					
						
							|  |  |  |             self.tabs.current_tab.height = 0 | 
					
						
							|  |  |  |             self.tabs.tab_height = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-22 05:25:09 +02:00
										 |  |  |         # bottom part | 
					
						
							|  |  |  |         bottom_layout = BoxLayout(orientation="horizontal", size_hint_y=None, height=30) | 
					
						
							|  |  |  |         info_button = Button(height=30, text="Command:", size_hint_x=None) | 
					
						
							| 
									
										
										
										
											2021-10-24 23:22:06 +02:00
										 |  |  |         info_button.bind(on_release=self.command_button_action) | 
					
						
							| 
									
										
										
										
											2021-10-22 05:25:09 +02:00
										 |  |  |         bottom_layout.add_widget(info_button) | 
					
						
							| 
									
										
										
										
											2021-11-22 17:44:14 +01:00
										 |  |  |         self.textinput = TextInput(size_hint_y=None, height=30, multiline=False, write_tab=False) | 
					
						
							|  |  |  |         self.textinput.bind(on_text_validate=self.on_message) | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  |         self.textinput.text_validate_unfocus = False | 
					
						
							| 
									
										
										
										
											2021-11-22 17:44:14 +01:00
										 |  |  |         bottom_layout.add_widget(self.textinput) | 
					
						
							| 
									
										
										
										
											2021-10-22 05:25:09 +02:00
										 |  |  |         self.grid.add_widget(bottom_layout) | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         self.commandprocessor("/help") | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |         Clock.schedule_interval(self.update_texts, 1 / 30) | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         self.container.add_widget(self.grid) | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-23 04:59:51 +01:00
										 |  |  |         # If the address contains a port, select it; otherwise, select the host. | 
					
						
							|  |  |  |         s = self.server_connect_bar.text | 
					
						
							|  |  |  |         host_start = s.find("@") + 1 | 
					
						
							|  |  |  |         ipv6_end = s.find("]", host_start) + 1 | 
					
						
							|  |  |  |         port_start = s.find(":", ipv6_end if ipv6_end > 0 else host_start) + 1 | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  |         self.server_connect_bar.focus = True | 
					
						
							| 
									
										
										
										
											2023-01-23 04:59:51 +01:00
										 |  |  |         self.server_connect_bar.select_text(port_start if port_start > 0 else host_start, len(s)) | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         return self.container | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |     def update_texts(self, dt): | 
					
						
							| 
									
										
										
										
											2022-05-30 01:11:01 -04:00
										 |  |  |         if hasattr(self.tabs.content.children[0], 'fix_heights'): | 
					
						
							|  |  |  |             self.tabs.content.children[0].fix_heights()  # TODO: remove this when Kivy fixes this upstream | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |         if self.ctx.server: | 
					
						
							| 
									
										
										
										
											2021-10-29 15:18:58 +02:00
										 |  |  |             self.title = self.base_title + " " + Utils.__version__ + \ | 
					
						
							|  |  |  |                          f" | Connected to: {self.ctx.server_address} " \ | 
					
						
							|  |  |  |                          f"{'.'.join(str(e) for e in self.ctx.server_version)}" | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |             self.server_connect_button.text = "Disconnect" | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  |             self.server_connect_bar.readonly = True | 
					
						
							| 
									
										
										
										
											2021-10-22 05:25:09 +02:00
										 |  |  |             self.progressbar.max = len(self.ctx.checked_locations) + len(self.ctx.missing_locations) | 
					
						
							|  |  |  |             self.progressbar.value = len(self.ctx.checked_locations) | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |         else: | 
					
						
							|  |  |  |             self.server_connect_button.text = "Connect" | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  |             self.server_connect_bar.readonly = False | 
					
						
							| 
									
										
										
										
											2021-10-29 15:18:58 +02:00
										 |  |  |             self.title = self.base_title + " " + Utils.__version__ | 
					
						
							| 
									
										
										
										
											2021-10-22 05:25:09 +02:00
										 |  |  |             self.progressbar.value = 0 | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-24 23:22:06 +02:00
										 |  |  |     def command_button_action(self, button): | 
					
						
							| 
									
										
										
										
											2021-11-19 21:25:01 +01:00
										 |  |  |         if self.ctx.server: | 
					
						
							|  |  |  |             logging.getLogger("Client").info("/help for client commands and !help for server commands.") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             logging.getLogger("Client").info("/help for client commands and once you are connected, " | 
					
						
							|  |  |  |                                              "!help for server commands.") | 
					
						
							| 
									
										
										
										
											2021-10-24 23:22:06 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |     def connect_button_action(self, button): | 
					
						
							|  |  |  |         if self.ctx.server: | 
					
						
							| 
									
										
										
										
											2022-06-27 03:10:41 -07:00
										 |  |  |             self.ctx.username = None | 
					
						
							| 
									
										
										
										
											2022-11-02 07:51:35 -07:00
										 |  |  |             async_start(self.ctx.disconnect()) | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2022-11-02 07:51:35 -07:00
										 |  |  |             async_start(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", ""))) | 
					
						
							| 
									
										
										
										
											2021-10-19 05:38:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |     def on_stop(self): | 
					
						
							| 
									
										
										
										
											2021-10-29 10:03:15 +02:00
										 |  |  |         # "kill" input tasks | 
					
						
							|  |  |  |         for x in range(self.ctx.input_requests): | 
					
						
							|  |  |  |             self.ctx.input_queue.put_nowait("") | 
					
						
							|  |  |  |         self.ctx.input_requests = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         self.ctx.exit_event.set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def on_message(self, textinput: TextInput): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             input_text = textinput.text.strip() | 
					
						
							|  |  |  |             textinput.text = "" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if self.ctx.input_requests > 0: | 
					
						
							|  |  |  |                 self.ctx.input_requests -= 1 | 
					
						
							|  |  |  |                 self.ctx.input_queue.put_nowait(input_text) | 
					
						
							|  |  |  |             elif input_text: | 
					
						
							|  |  |  |                 self.commandprocessor(input_text) | 
					
						
							| 
									
										
										
										
											2021-11-21 05:47:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         except Exception as e: | 
					
						
							|  |  |  |             logging.getLogger("Client").exception(e) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-18 05:52:29 +01:00
										 |  |  |     def print_json(self, data: typing.List[JSONMessagePart]): | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         text = self.json_to_kivy_parser(data) | 
					
						
							|  |  |  |         self.log_panels["Archipelago"].on_message_markup(text) | 
					
						
							|  |  |  |         self.log_panels["All"].on_message_markup(text) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-01 06:54:40 +01:00
										 |  |  |     def focus_textinput(self): | 
					
						
							|  |  |  |         if hasattr(self, "textinput"): | 
					
						
							|  |  |  |             self.textinput.focus = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-27 03:10:41 -07:00
										 |  |  |     def update_address_bar(self, text: str): | 
					
						
							|  |  |  |         if hasattr(self, "server_connect_bar"): | 
					
						
							|  |  |  |             self.server_connect_bar.text = text | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             logging.getLogger("Client").info("Could not update address bar as the GUI is not yet initialized.") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-24 04:47:01 +01:00
										 |  |  |     def enable_energy_link(self): | 
					
						
							|  |  |  |         if not hasattr(self, "energy_link_label"): | 
					
						
							|  |  |  |             self.energy_link_label = Label(text="Energy Link: Standby", | 
					
						
							|  |  |  |                                            size_hint_x=None, width=150) | 
					
						
							|  |  |  |             self.connect_layout.add_widget(self.energy_link_label) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def set_new_energy_link_value(self): | 
					
						
							|  |  |  |         if hasattr(self, "energy_link_label"): | 
					
						
							|  |  |  |             self.energy_link_label.text = f"EL: {Utils.format_SI_prefix(self.ctx.current_energy_link_value)}J" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class LogtoUI(logging.Handler): | 
					
						
							|  |  |  |     def __init__(self, on_log): | 
					
						
							| 
									
										
										
										
											2021-11-17 22:46:32 +01:00
										 |  |  |         super(LogtoUI, self).__init__(logging.INFO) | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |         self.on_log = on_log | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-07 00:15:08 +02:00
										 |  |  |     @staticmethod | 
					
						
							|  |  |  |     def format_compact(record: logging.LogRecord) -> str: | 
					
						
							|  |  |  |         if isinstance(record.msg, Exception): | 
					
						
							|  |  |  |             return str(record.msg) | 
					
						
							|  |  |  |         return (f'{record.exc_info[1]}\n' if record.exc_info else '') + str(record.msg).split("\n")[0] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  |     def handle(self, record: logging.LogRecord) -> None: | 
					
						
							| 
									
										
										
										
											2022-06-07 00:15:08 +02:00
										 |  |  |         if getattr(record, 'skip_gui', False): | 
					
						
							|  |  |  |             pass  # skip output | 
					
						
							|  |  |  |         elif getattr(record, 'compact_gui', False): | 
					
						
							|  |  |  |             self.on_log(self.format_compact(record)) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.on_log(self.format(record)) | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class UILog(RecycleView): | 
					
						
							| 
									
										
										
										
											2023-02-13 03:50:05 +01:00
										 |  |  |     messages: typing.ClassVar[int]  # comes from kv file | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, *loggers_to_handle, **kwargs): | 
					
						
							|  |  |  |         super(UILog, self).__init__(**kwargs) | 
					
						
							|  |  |  |         self.data = [] | 
					
						
							|  |  |  |         for logger in loggers_to_handle: | 
					
						
							|  |  |  |             logger.addHandler(LogtoUI(self.on_log)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 06:40:29 +01:00
										 |  |  |     def on_log(self, record: str) -> None: | 
					
						
							|  |  |  |         self.data.append({"text": escape_markup(record)}) | 
					
						
							| 
									
										
										
										
											2023-02-13 03:50:05 +01:00
										 |  |  |         self.clean_old() | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def on_message_markup(self, text): | 
					
						
							|  |  |  |         self.data.append({"text": text}) | 
					
						
							| 
									
										
										
										
											2023-02-13 03:50:05 +01:00
										 |  |  |         self.clean_old() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def clean_old(self): | 
					
						
							|  |  |  |         if len(self.data) > self.messages: | 
					
						
							|  |  |  |             self.data.pop(0) | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-23 23:31:49 +01:00
										 |  |  |     def fix_heights(self): | 
					
						
							|  |  |  |         """Workaround fix for divergent texture and layout heights""" | 
					
						
							|  |  |  |         for element in self.children[0].children: | 
					
						
							|  |  |  |             if element.height != element.texture_size[1]: | 
					
						
							|  |  |  |                 element.height = element.texture_size[1] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class E(ExceptionHandler): | 
					
						
							|  |  |  |     logger = logging.getLogger("Client") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_exception(self, inst): | 
					
						
							| 
									
										
										
										
											2021-11-01 06:40:29 +01:00
										 |  |  |         self.logger.exception("Uncaught Exception:", exc_info=inst) | 
					
						
							|  |  |  |         return ExceptionManager.PASS | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class KivyJSONtoTextParser(JSONtoTextParser): | 
					
						
							| 
									
										
										
										
											2023-02-13 01:55:43 +01:00
										 |  |  |     # dummy class to absorb kvlang definitions | 
					
						
							|  |  |  |     class TextColors(Widget): | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, *args, **kwargs): | 
					
						
							|  |  |  |         # we grab the color definitions from the .kv file, then overwrite the JSONtoTextParser default entries | 
					
						
							|  |  |  |         colors = self.TextColors() | 
					
						
							|  |  |  |         color_codes = self.color_codes.copy() | 
					
						
							|  |  |  |         for name, code in color_codes.items(): | 
					
						
							|  |  |  |             color_codes[name] = getattr(colors, name, code) | 
					
						
							|  |  |  |         self.color_codes = color_codes | 
					
						
							|  |  |  |         super().__init__(*args, **kwargs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |     def __call__(self, *args, **kwargs): | 
					
						
							|  |  |  |         self.ref_count = 0 | 
					
						
							|  |  |  |         return super(KivyJSONtoTextParser, self).__call__(*args, **kwargs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _handle_item_name(self, node: JSONMessagePart): | 
					
						
							|  |  |  |         flags = node.get("flags", 0) | 
					
						
							|  |  |  |         if flags & 0b001:  # advancement | 
					
						
							|  |  |  |             itemtype = "progression" | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  |         elif flags & 0b010:  # useful | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |             itemtype = "useful" | 
					
						
							|  |  |  |         elif flags & 0b100:  # trap | 
					
						
							|  |  |  |             itemtype = "trap" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             itemtype = "normal" | 
					
						
							|  |  |  |         node.setdefault("refs", []).append("Item Class: " + itemtype) | 
					
						
							|  |  |  |         return super(KivyJSONtoTextParser, self)._handle_item_name(node) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _handle_player_id(self, node: JSONMessagePart): | 
					
						
							|  |  |  |         player = int(node["text"]) | 
					
						
							|  |  |  |         slot_info = self.ctx.slot_info.get(player, None) | 
					
						
							|  |  |  |         if slot_info: | 
					
						
							|  |  |  |             text = f"Game: {slot_info.game}<br>" \ | 
					
						
							|  |  |  |                    f"Type: {SlotType(slot_info.type).name}" | 
					
						
							|  |  |  |             if slot_info.group_members: | 
					
						
							|  |  |  |                 text += f"<br>Members:<br> " + \ | 
					
						
							|  |  |  |                         '<br> '.join(self.ctx.player_names[player] for player in slot_info.group_members) | 
					
						
							|  |  |  |             node.setdefault("refs", []).append(text) | 
					
						
							|  |  |  |         return super(KivyJSONtoTextParser, self)._handle_player_id(node) | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def _handle_color(self, node: JSONMessagePart): | 
					
						
							|  |  |  |         colors = node["color"].split(";") | 
					
						
							|  |  |  |         node["text"] = escape_markup(node["text"]) | 
					
						
							|  |  |  |         for color in colors: | 
					
						
							|  |  |  |             color_code = self.color_codes.get(color, None) | 
					
						
							|  |  |  |             if color_code: | 
					
						
							|  |  |  |                 node["text"] = f"[color={color_code}]{node['text']}[/color]" | 
					
						
							|  |  |  |                 return self._handle_text(node) | 
					
						
							|  |  |  |         return self._handle_text(node) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-24 00:20:02 +02:00
										 |  |  |     def _handle_text(self, node: JSONMessagePart): | 
					
						
							|  |  |  |         for ref in node.get("refs", []): | 
					
						
							|  |  |  |             node["text"] = f"[ref={self.ref_count}|{ref}]{node['text']}[/ref]" | 
					
						
							|  |  |  |             self.ref_count += 1 | 
					
						
							|  |  |  |         return super(KivyJSONtoTextParser, self)._handle_text(node) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-30 20:18:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | ExceptionManager.add_handler(E()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Builder.load_file(Utils.local_path("data", "client.kv")) | 
					
						
							| 
									
										
										
										
											2023-02-13 01:55:43 +01:00
										 |  |  | user_file = Utils.local_path("data", "user.kv") | 
					
						
							|  |  |  | if os.path.exists(user_file): | 
					
						
							|  |  |  |     logging.info("Loading user.kv into builder.") | 
					
						
							|  |  |  |     Builder.load_file(user_file) | 
					
						
							|  |  |  | 
 |