bastd.ui.profile.upgrade

UI for player profile upgrades.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""UI for player profile upgrades."""
  4
  5from __future__ import annotations
  6
  7import time
  8import weakref
  9from typing import TYPE_CHECKING
 10
 11import ba
 12import ba.internal
 13
 14if TYPE_CHECKING:
 15    from typing import Any
 16    from bastd.ui.profile.edit import EditProfileWindow
 17
 18
 19class ProfileUpgradeWindow(ba.Window):
 20    """Window for player profile upgrades to global."""
 21
 22    def __init__(
 23        self,
 24        edit_profile_window: EditProfileWindow,
 25        transition: str = 'in_right',
 26    ):
 27        from ba.internal import master_server_get
 28
 29        self._r = 'editProfileWindow'
 30
 31        self._width = 680
 32        self._height = 350
 33        uiscale = ba.app.ui.uiscale
 34        self._base_scale = (
 35            2.05
 36            if uiscale is ba.UIScale.SMALL
 37            else 1.5
 38            if uiscale is ba.UIScale.MEDIUM
 39            else 1.2
 40        )
 41        self._upgrade_start_time: float | None = None
 42        self._name = edit_profile_window.getname()
 43        self._edit_profile_window = weakref.ref(edit_profile_window)
 44
 45        top_extra = 15 if uiscale is ba.UIScale.SMALL else 15
 46        super().__init__(
 47            root_widget=ba.containerwidget(
 48                size=(self._width, self._height + top_extra),
 49                toolbar_visibility='menu_currency',
 50                transition=transition,
 51                scale=self._base_scale,
 52                stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0),
 53            )
 54        )
 55        cancel_button = ba.buttonwidget(
 56            parent=self._root_widget,
 57            position=(52, 30),
 58            size=(155, 60),
 59            scale=0.8,
 60            autoselect=True,
 61            label=ba.Lstr(resource='cancelText'),
 62            on_activate_call=self._cancel,
 63        )
 64        self._upgrade_button = ba.buttonwidget(
 65            parent=self._root_widget,
 66            position=(self._width - 190, 30),
 67            size=(155, 60),
 68            scale=0.8,
 69            autoselect=True,
 70            label=ba.Lstr(resource='upgradeText'),
 71            on_activate_call=self._on_upgrade_press,
 72        )
 73        ba.containerwidget(
 74            edit=self._root_widget,
 75            cancel_button=cancel_button,
 76            start_button=self._upgrade_button,
 77            selected_child=self._upgrade_button,
 78        )
 79
 80        ba.textwidget(
 81            parent=self._root_widget,
 82            position=(self._width * 0.5, self._height - 38),
 83            size=(0, 0),
 84            text=ba.Lstr(resource=self._r + '.upgradeToGlobalProfileText'),
 85            color=ba.app.ui.title_color,
 86            maxwidth=self._width * 0.45,
 87            scale=1.0,
 88            h_align='center',
 89            v_align='center',
 90        )
 91
 92        ba.textwidget(
 93            parent=self._root_widget,
 94            position=(self._width * 0.5, self._height - 100),
 95            size=(0, 0),
 96            text=ba.Lstr(resource=self._r + '.upgradeProfileInfoText'),
 97            color=ba.app.ui.infotextcolor,
 98            maxwidth=self._width * 0.8,
 99            scale=0.7,
100            h_align='center',
101            v_align='center',
102        )
103
104        self._status_text = ba.textwidget(
105            parent=self._root_widget,
106            position=(self._width * 0.5, self._height - 160),
107            size=(0, 0),
108            text=ba.Lstr(
109                resource=self._r + '.checkingAvailabilityText',
110                subs=[('${NAME}', self._name)],
111            ),
112            color=(0.8, 0.4, 0.0),
113            maxwidth=self._width * 0.8,
114            scale=0.65,
115            h_align='center',
116            v_align='center',
117        )
118
119        self._price_text = ba.textwidget(
120            parent=self._root_widget,
121            position=(self._width * 0.5, self._height - 230),
122            size=(0, 0),
123            text='',
124            color=(0.2, 1, 0.2),
125            maxwidth=self._width * 0.8,
126            scale=1.5,
127            h_align='center',
128            v_align='center',
129        )
130
131        self._tickets_text: ba.Widget | None
132        if not ba.app.ui.use_toolbars:
133            self._tickets_text = ba.textwidget(
134                parent=self._root_widget,
135                position=(self._width * 0.9 - 5, self._height - 30),
136                size=(0, 0),
137                text=ba.charstr(ba.SpecialChar.TICKET) + '123',
138                color=(0.2, 1, 0.2),
139                maxwidth=100,
140                scale=0.5,
141                h_align='right',
142                v_align='center',
143            )
144        else:
145            self._tickets_text = None
146
147        master_server_get(
148            'bsGlobalProfileCheck',
149            {'name': self._name, 'b': ba.app.build_number},
150            callback=ba.WeakCall(self._profile_check_result),
151        )
152        self._cost = ba.internal.get_v1_account_misc_read_val(
153            'price.global_profile', 500
154        )
155        self._status: str | None = 'waiting'
156        self._update_timer = ba.Timer(
157            1.0,
158            ba.WeakCall(self._update),
159            timetype=ba.TimeType.REAL,
160            repeat=True,
161        )
162        self._update()
163
164    def _profile_check_result(self, result: dict[str, Any] | None) -> None:
165        if result is None:
166            ba.textwidget(
167                edit=self._status_text,
168                text=ba.Lstr(resource='internal.unavailableNoConnectionText'),
169                color=(1, 0, 0),
170            )
171            self._status = 'error'
172            ba.buttonwidget(
173                edit=self._upgrade_button,
174                color=(0.4, 0.4, 0.4),
175                textcolor=(0.5, 0.5, 0.5),
176            )
177        else:
178            if result['available']:
179                ba.textwidget(
180                    edit=self._status_text,
181                    text=ba.Lstr(
182                        resource=self._r + '.availableText',
183                        subs=[('${NAME}', self._name)],
184                    ),
185                    color=(0, 1, 0),
186                )
187                ba.textwidget(
188                    edit=self._price_text,
189                    text=ba.charstr(ba.SpecialChar.TICKET) + str(self._cost),
190                )
191                self._status = None
192            else:
193                ba.textwidget(
194                    edit=self._status_text,
195                    text=ba.Lstr(
196                        resource=self._r + '.unavailableText',
197                        subs=[('${NAME}', self._name)],
198                    ),
199                    color=(1, 0, 0),
200                )
201                self._status = 'unavailable'
202                ba.buttonwidget(
203                    edit=self._upgrade_button,
204                    color=(0.4, 0.4, 0.4),
205                    textcolor=(0.5, 0.5, 0.5),
206                )
207
208    def _on_upgrade_press(self) -> None:
209        from bastd.ui import getcurrency
210
211        if self._status is None:
212            # If it appears we don't have enough tickets, offer to buy more.
213            tickets = ba.internal.get_v1_account_ticket_count()
214            if tickets < self._cost:
215                ba.playsound(ba.getsound('error'))
216                getcurrency.show_get_tickets_prompt()
217                return
218            ba.screenmessage(
219                ba.Lstr(resource='purchasingText'), color=(0, 1, 0)
220            )
221            self._status = 'pre_upgrading'
222
223            # Now we tell the original editor to save the profile, add an
224            # upgrade transaction, and then sit and wait for everything to
225            # go through.
226            edit_profile_window = self._edit_profile_window()
227            if edit_profile_window is None:
228                print('profile upgrade: original edit window gone')
229                return
230            success = edit_profile_window.save(transition_out=False)
231            if not success:
232                print('profile upgrade: error occurred saving profile')
233                ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0))
234                ba.playsound(ba.getsound('error'))
235                return
236            ba.internal.add_transaction(
237                {'type': 'UPGRADE_PROFILE', 'name': self._name}
238            )
239            ba.internal.run_transactions()
240            self._status = 'upgrading'
241            self._upgrade_start_time = time.time()
242        else:
243            ba.playsound(ba.getsound('error'))
244
245    def _update(self) -> None:
246        try:
247            t_str = str(ba.internal.get_v1_account_ticket_count())
248        except Exception:
249            t_str = '?'
250        if self._tickets_text is not None:
251            ba.textwidget(
252                edit=self._tickets_text,
253                text=ba.Lstr(
254                    resource='getTicketsWindow.youHaveShortText',
255                    subs=[
256                        ('${COUNT}', ba.charstr(ba.SpecialChar.TICKET) + t_str)
257                    ],
258                ),
259            )
260
261        # Once we've kicked off an upgrade attempt and all transactions go
262        # through, we're done.
263        if (
264            self._status == 'upgrading'
265            and not ba.internal.have_outstanding_transactions()
266        ):
267            self._status = 'exiting'
268            ba.containerwidget(edit=self._root_widget, transition='out_right')
269            edit_profile_window = self._edit_profile_window()
270            if edit_profile_window is None:
271                print(
272                    'profile upgrade transition out:'
273                    ' original edit window gone'
274                )
275                return
276            ba.playsound(ba.getsound('gunCocking'))
277            edit_profile_window.reload_window()
278
279    def _cancel(self) -> None:
280        # If we recently sent out an upgrade request, disallow canceling
281        # for a bit.
282        if (
283            self._upgrade_start_time is not None
284            and time.time() - self._upgrade_start_time < 10.0
285        ):
286            ba.playsound(ba.getsound('error'))
287            return
288        ba.containerwidget(edit=self._root_widget, transition='out_right')
class ProfileUpgradeWindow(ba.ui.Window):
 20class ProfileUpgradeWindow(ba.Window):
 21    """Window for player profile upgrades to global."""
 22
 23    def __init__(
 24        self,
 25        edit_profile_window: EditProfileWindow,
 26        transition: str = 'in_right',
 27    ):
 28        from ba.internal import master_server_get
 29
 30        self._r = 'editProfileWindow'
 31
 32        self._width = 680
 33        self._height = 350
 34        uiscale = ba.app.ui.uiscale
 35        self._base_scale = (
 36            2.05
 37            if uiscale is ba.UIScale.SMALL
 38            else 1.5
 39            if uiscale is ba.UIScale.MEDIUM
 40            else 1.2
 41        )
 42        self._upgrade_start_time: float | None = None
 43        self._name = edit_profile_window.getname()
 44        self._edit_profile_window = weakref.ref(edit_profile_window)
 45
 46        top_extra = 15 if uiscale is ba.UIScale.SMALL else 15
 47        super().__init__(
 48            root_widget=ba.containerwidget(
 49                size=(self._width, self._height + top_extra),
 50                toolbar_visibility='menu_currency',
 51                transition=transition,
 52                scale=self._base_scale,
 53                stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0),
 54            )
 55        )
 56        cancel_button = ba.buttonwidget(
 57            parent=self._root_widget,
 58            position=(52, 30),
 59            size=(155, 60),
 60            scale=0.8,
 61            autoselect=True,
 62            label=ba.Lstr(resource='cancelText'),
 63            on_activate_call=self._cancel,
 64        )
 65        self._upgrade_button = ba.buttonwidget(
 66            parent=self._root_widget,
 67            position=(self._width - 190, 30),
 68            size=(155, 60),
 69            scale=0.8,
 70            autoselect=True,
 71            label=ba.Lstr(resource='upgradeText'),
 72            on_activate_call=self._on_upgrade_press,
 73        )
 74        ba.containerwidget(
 75            edit=self._root_widget,
 76            cancel_button=cancel_button,
 77            start_button=self._upgrade_button,
 78            selected_child=self._upgrade_button,
 79        )
 80
 81        ba.textwidget(
 82            parent=self._root_widget,
 83            position=(self._width * 0.5, self._height - 38),
 84            size=(0, 0),
 85            text=ba.Lstr(resource=self._r + '.upgradeToGlobalProfileText'),
 86            color=ba.app.ui.title_color,
 87            maxwidth=self._width * 0.45,
 88            scale=1.0,
 89            h_align='center',
 90            v_align='center',
 91        )
 92
 93        ba.textwidget(
 94            parent=self._root_widget,
 95            position=(self._width * 0.5, self._height - 100),
 96            size=(0, 0),
 97            text=ba.Lstr(resource=self._r + '.upgradeProfileInfoText'),
 98            color=ba.app.ui.infotextcolor,
 99            maxwidth=self._width * 0.8,
100            scale=0.7,
101            h_align='center',
102            v_align='center',
103        )
104
105        self._status_text = ba.textwidget(
106            parent=self._root_widget,
107            position=(self._width * 0.5, self._height - 160),
108            size=(0, 0),
109            text=ba.Lstr(
110                resource=self._r + '.checkingAvailabilityText',
111                subs=[('${NAME}', self._name)],
112            ),
113            color=(0.8, 0.4, 0.0),
114            maxwidth=self._width * 0.8,
115            scale=0.65,
116            h_align='center',
117            v_align='center',
118        )
119
120        self._price_text = ba.textwidget(
121            parent=self._root_widget,
122            position=(self._width * 0.5, self._height - 230),
123            size=(0, 0),
124            text='',
125            color=(0.2, 1, 0.2),
126            maxwidth=self._width * 0.8,
127            scale=1.5,
128            h_align='center',
129            v_align='center',
130        )
131
132        self._tickets_text: ba.Widget | None
133        if not ba.app.ui.use_toolbars:
134            self._tickets_text = ba.textwidget(
135                parent=self._root_widget,
136                position=(self._width * 0.9 - 5, self._height - 30),
137                size=(0, 0),
138                text=ba.charstr(ba.SpecialChar.TICKET) + '123',
139                color=(0.2, 1, 0.2),
140                maxwidth=100,
141                scale=0.5,
142                h_align='right',
143                v_align='center',
144            )
145        else:
146            self._tickets_text = None
147
148        master_server_get(
149            'bsGlobalProfileCheck',
150            {'name': self._name, 'b': ba.app.build_number},
151            callback=ba.WeakCall(self._profile_check_result),
152        )
153        self._cost = ba.internal.get_v1_account_misc_read_val(
154            'price.global_profile', 500
155        )
156        self._status: str | None = 'waiting'
157        self._update_timer = ba.Timer(
158            1.0,
159            ba.WeakCall(self._update),
160            timetype=ba.TimeType.REAL,
161            repeat=True,
162        )
163        self._update()
164
165    def _profile_check_result(self, result: dict[str, Any] | None) -> None:
166        if result is None:
167            ba.textwidget(
168                edit=self._status_text,
169                text=ba.Lstr(resource='internal.unavailableNoConnectionText'),
170                color=(1, 0, 0),
171            )
172            self._status = 'error'
173            ba.buttonwidget(
174                edit=self._upgrade_button,
175                color=(0.4, 0.4, 0.4),
176                textcolor=(0.5, 0.5, 0.5),
177            )
178        else:
179            if result['available']:
180                ba.textwidget(
181                    edit=self._status_text,
182                    text=ba.Lstr(
183                        resource=self._r + '.availableText',
184                        subs=[('${NAME}', self._name)],
185                    ),
186                    color=(0, 1, 0),
187                )
188                ba.textwidget(
189                    edit=self._price_text,
190                    text=ba.charstr(ba.SpecialChar.TICKET) + str(self._cost),
191                )
192                self._status = None
193            else:
194                ba.textwidget(
195                    edit=self._status_text,
196                    text=ba.Lstr(
197                        resource=self._r + '.unavailableText',
198                        subs=[('${NAME}', self._name)],
199                    ),
200                    color=(1, 0, 0),
201                )
202                self._status = 'unavailable'
203                ba.buttonwidget(
204                    edit=self._upgrade_button,
205                    color=(0.4, 0.4, 0.4),
206                    textcolor=(0.5, 0.5, 0.5),
207                )
208
209    def _on_upgrade_press(self) -> None:
210        from bastd.ui import getcurrency
211
212        if self._status is None:
213            # If it appears we don't have enough tickets, offer to buy more.
214            tickets = ba.internal.get_v1_account_ticket_count()
215            if tickets < self._cost:
216                ba.playsound(ba.getsound('error'))
217                getcurrency.show_get_tickets_prompt()
218                return
219            ba.screenmessage(
220                ba.Lstr(resource='purchasingText'), color=(0, 1, 0)
221            )
222            self._status = 'pre_upgrading'
223
224            # Now we tell the original editor to save the profile, add an
225            # upgrade transaction, and then sit and wait for everything to
226            # go through.
227            edit_profile_window = self._edit_profile_window()
228            if edit_profile_window is None:
229                print('profile upgrade: original edit window gone')
230                return
231            success = edit_profile_window.save(transition_out=False)
232            if not success:
233                print('profile upgrade: error occurred saving profile')
234                ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0))
235                ba.playsound(ba.getsound('error'))
236                return
237            ba.internal.add_transaction(
238                {'type': 'UPGRADE_PROFILE', 'name': self._name}
239            )
240            ba.internal.run_transactions()
241            self._status = 'upgrading'
242            self._upgrade_start_time = time.time()
243        else:
244            ba.playsound(ba.getsound('error'))
245
246    def _update(self) -> None:
247        try:
248            t_str = str(ba.internal.get_v1_account_ticket_count())
249        except Exception:
250            t_str = '?'
251        if self._tickets_text is not None:
252            ba.textwidget(
253                edit=self._tickets_text,
254                text=ba.Lstr(
255                    resource='getTicketsWindow.youHaveShortText',
256                    subs=[
257                        ('${COUNT}', ba.charstr(ba.SpecialChar.TICKET) + t_str)
258                    ],
259                ),
260            )
261
262        # Once we've kicked off an upgrade attempt and all transactions go
263        # through, we're done.
264        if (
265            self._status == 'upgrading'
266            and not ba.internal.have_outstanding_transactions()
267        ):
268            self._status = 'exiting'
269            ba.containerwidget(edit=self._root_widget, transition='out_right')
270            edit_profile_window = self._edit_profile_window()
271            if edit_profile_window is None:
272                print(
273                    'profile upgrade transition out:'
274                    ' original edit window gone'
275                )
276                return
277            ba.playsound(ba.getsound('gunCocking'))
278            edit_profile_window.reload_window()
279
280    def _cancel(self) -> None:
281        # If we recently sent out an upgrade request, disallow canceling
282        # for a bit.
283        if (
284            self._upgrade_start_time is not None
285            and time.time() - self._upgrade_start_time < 10.0
286        ):
287            ba.playsound(ba.getsound('error'))
288            return
289        ba.containerwidget(edit=self._root_widget, transition='out_right')

Window for player profile upgrades to global.

ProfileUpgradeWindow( edit_profile_window: bastd.ui.profile.edit.EditProfileWindow, transition: str = 'in_right')
 23    def __init__(
 24        self,
 25        edit_profile_window: EditProfileWindow,
 26        transition: str = 'in_right',
 27    ):
 28        from ba.internal import master_server_get
 29
 30        self._r = 'editProfileWindow'
 31
 32        self._width = 680
 33        self._height = 350
 34        uiscale = ba.app.ui.uiscale
 35        self._base_scale = (
 36            2.05
 37            if uiscale is ba.UIScale.SMALL
 38            else 1.5
 39            if uiscale is ba.UIScale.MEDIUM
 40            else 1.2
 41        )
 42        self._upgrade_start_time: float | None = None
 43        self._name = edit_profile_window.getname()
 44        self._edit_profile_window = weakref.ref(edit_profile_window)
 45
 46        top_extra = 15 if uiscale is ba.UIScale.SMALL else 15
 47        super().__init__(
 48            root_widget=ba.containerwidget(
 49                size=(self._width, self._height + top_extra),
 50                toolbar_visibility='menu_currency',
 51                transition=transition,
 52                scale=self._base_scale,
 53                stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0),
 54            )
 55        )
 56        cancel_button = ba.buttonwidget(
 57            parent=self._root_widget,
 58            position=(52, 30),
 59            size=(155, 60),
 60            scale=0.8,
 61            autoselect=True,
 62            label=ba.Lstr(resource='cancelText'),
 63            on_activate_call=self._cancel,
 64        )
 65        self._upgrade_button = ba.buttonwidget(
 66            parent=self._root_widget,
 67            position=(self._width - 190, 30),
 68            size=(155, 60),
 69            scale=0.8,
 70            autoselect=True,
 71            label=ba.Lstr(resource='upgradeText'),
 72            on_activate_call=self._on_upgrade_press,
 73        )
 74        ba.containerwidget(
 75            edit=self._root_widget,
 76            cancel_button=cancel_button,
 77            start_button=self._upgrade_button,
 78            selected_child=self._upgrade_button,
 79        )
 80
 81        ba.textwidget(
 82            parent=self._root_widget,
 83            position=(self._width * 0.5, self._height - 38),
 84            size=(0, 0),
 85            text=ba.Lstr(resource=self._r + '.upgradeToGlobalProfileText'),
 86            color=ba.app.ui.title_color,
 87            maxwidth=self._width * 0.45,
 88            scale=1.0,
 89            h_align='center',
 90            v_align='center',
 91        )
 92
 93        ba.textwidget(
 94            parent=self._root_widget,
 95            position=(self._width * 0.5, self._height - 100),
 96            size=(0, 0),
 97            text=ba.Lstr(resource=self._r + '.upgradeProfileInfoText'),
 98            color=ba.app.ui.infotextcolor,
 99            maxwidth=self._width * 0.8,
100            scale=0.7,
101            h_align='center',
102            v_align='center',
103        )
104
105        self._status_text = ba.textwidget(
106            parent=self._root_widget,
107            position=(self._width * 0.5, self._height - 160),
108            size=(0, 0),
109            text=ba.Lstr(
110                resource=self._r + '.checkingAvailabilityText',
111                subs=[('${NAME}', self._name)],
112            ),
113            color=(0.8, 0.4, 0.0),
114            maxwidth=self._width * 0.8,
115            scale=0.65,
116            h_align='center',
117            v_align='center',
118        )
119
120        self._price_text = ba.textwidget(
121            parent=self._root_widget,
122            position=(self._width * 0.5, self._height - 230),
123            size=(0, 0),
124            text='',
125            color=(0.2, 1, 0.2),
126            maxwidth=self._width * 0.8,
127            scale=1.5,
128            h_align='center',
129            v_align='center',
130        )
131
132        self._tickets_text: ba.Widget | None
133        if not ba.app.ui.use_toolbars:
134            self._tickets_text = ba.textwidget(
135                parent=self._root_widget,
136                position=(self._width * 0.9 - 5, self._height - 30),
137                size=(0, 0),
138                text=ba.charstr(ba.SpecialChar.TICKET) + '123',
139                color=(0.2, 1, 0.2),
140                maxwidth=100,
141                scale=0.5,
142                h_align='right',
143                v_align='center',
144            )
145        else:
146            self._tickets_text = None
147
148        master_server_get(
149            'bsGlobalProfileCheck',
150            {'name': self._name, 'b': ba.app.build_number},
151            callback=ba.WeakCall(self._profile_check_result),
152        )
153        self._cost = ba.internal.get_v1_account_misc_read_val(
154            'price.global_profile', 500
155        )
156        self._status: str | None = 'waiting'
157        self._update_timer = ba.Timer(
158            1.0,
159            ba.WeakCall(self._update),
160            timetype=ba.TimeType.REAL,
161            repeat=True,
162        )
163        self._update()
Inherited Members
ba.ui.Window
get_root_widget