bastd.ui.creditslist

Provides a window to display game credits.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides a window to display game credits."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING
  8
  9import _ba
 10import ba
 11
 12if TYPE_CHECKING:
 13    from typing import Sequence
 14
 15
 16class CreditsListWindow(ba.Window):
 17    """Window for displaying game credits."""
 18
 19    def __init__(self, origin_widget: ba.Widget | None = None):
 20        # pylint: disable=too-many-locals
 21        # pylint: disable=too-many-statements
 22        import json
 23        ba.set_analytics_screen('Credits Window')
 24
 25        # if they provided an origin-widget, scale up from that
 26        scale_origin: tuple[float, float] | None
 27        if origin_widget is not None:
 28            self._transition_out = 'out_scale'
 29            scale_origin = origin_widget.get_screen_space_center()
 30            transition = 'in_scale'
 31        else:
 32            self._transition_out = 'out_right'
 33            scale_origin = None
 34            transition = 'in_right'
 35
 36        uiscale = ba.app.ui.uiscale
 37        width = 870 if uiscale is ba.UIScale.SMALL else 670
 38        x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
 39        height = 398 if uiscale is ba.UIScale.SMALL else 500
 40
 41        self._r = 'creditsWindow'
 42        super().__init__(root_widget=ba.containerwidget(
 43            size=(width, height),
 44            transition=transition,
 45            toolbar_visibility='menu_minimal',
 46            scale_origin_stack_offset=scale_origin,
 47            scale=(2.0 if uiscale is ba.UIScale.SMALL else
 48                   1.3 if uiscale is ba.UIScale.MEDIUM else 1.0),
 49            stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0)))
 50
 51        if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
 52            ba.containerwidget(edit=self._root_widget,
 53                               on_cancel_call=self._back)
 54        else:
 55            btn = ba.buttonwidget(
 56                parent=self._root_widget,
 57                position=(40 + x_inset, height -
 58                          (68 if uiscale is ba.UIScale.SMALL else 62)),
 59                size=(140, 60),
 60                scale=0.8,
 61                label=ba.Lstr(resource='backText'),
 62                button_type='back',
 63                on_activate_call=self._back,
 64                autoselect=True)
 65            ba.containerwidget(edit=self._root_widget, cancel_button=btn)
 66
 67            ba.buttonwidget(
 68                edit=btn,
 69                button_type='backSmall',
 70                position=(40 + x_inset, height -
 71                          (68 if uiscale is ba.UIScale.SMALL else 62) + 5),
 72                size=(60, 48),
 73                label=ba.charstr(ba.SpecialChar.BACK))
 74
 75        ba.textwidget(parent=self._root_widget,
 76                      position=(0, height -
 77                                (59 if uiscale is ba.UIScale.SMALL else 54)),
 78                      size=(width, 30),
 79                      text=ba.Lstr(resource=self._r + '.titleText',
 80                                   subs=[('${APP_NAME}',
 81                                          ba.Lstr(resource='titleText'))]),
 82                      h_align='center',
 83                      color=ba.app.ui.title_color,
 84                      maxwidth=330,
 85                      v_align='center')
 86
 87        scroll = ba.scrollwidget(parent=self._root_widget,
 88                                 position=(40 + x_inset, 35),
 89                                 size=(width - (80 + 2 * x_inset),
 90                                       height - 100),
 91                                 capture_arrows=True)
 92
 93        if ba.app.ui.use_toolbars:
 94            ba.widget(edit=scroll,
 95                      right_widget=_ba.get_special_widget('party_button'))
 96            if uiscale is ba.UIScale.SMALL:
 97                ba.widget(edit=scroll,
 98                          left_widget=_ba.get_special_widget('back_button'))
 99
100        def _format_names(names2: Sequence[str], inset: float) -> str:
101            sval = ''
102            # measure a series since there's overlaps and stuff..
103            space_width = _ba.get_string_width(' ' * 10,
104                                               suppress_warning=True) / 10.0
105            spacing = 330.0
106            col1 = inset
107            col2 = col1 + spacing
108            col3 = col2 + spacing
109            line_width = 0.0
110            nline = ''
111            for name in names2:
112                # move to the next column (or row) and print
113                if line_width > col3:
114                    sval += nline + '\n'
115                    nline = ''
116                    line_width = 0
117
118                if line_width > col2:
119                    target = col3
120                elif line_width > col1:
121                    target = col2
122                else:
123                    target = col1
124                spacingstr = ' ' * int((target - line_width) / space_width)
125                nline += spacingstr
126                nline += name
127                line_width = _ba.get_string_width(nline, suppress_warning=True)
128            if nline != '':
129                sval += nline + '\n'
130            return sval
131
132        sound_and_music = ba.Lstr(resource=self._r +
133                                  '.songCreditText').evaluate()
134        sound_and_music = sound_and_music.replace(
135            '${TITLE}', "'William Tell (Trumpet Entry)'")
136        sound_and_music = sound_and_music.replace(
137            '${PERFORMER}', 'The Apollo Symphony Orchestra')
138        sound_and_music = sound_and_music.replace(
139            '${PERFORMER}', 'The Apollo Symphony Orchestra')
140        sound_and_music = sound_and_music.replace('${COMPOSER}',
141                                                  'Gioacchino Rossini')
142        sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth')
143        sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI')
144        sound_and_music = sound_and_music.replace('${SOURCE}',
145                                                  'www.AudioSparx.com')
146        spc = '     '
147        sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc)
148        names = [
149            'HubOfTheUniverseProd', 'Jovica', 'LG', 'Leady', 'Percy Duke',
150            'PhreaKsAccount', 'Pogotron', 'Rock Savage', 'anamorphosis',
151            'benboncan', 'cdrk', 'chipfork', 'guitarguy1985', 'jascha',
152            'joedeshon', 'loofa', 'm_O_m', 'mich3d', 'sandyrb', 'shakaharu',
153            'sirplus', 'stickman', 'thanvannispen', 'virotic', 'zimbot'
154        ]
155        names.sort(key=lambda x: x.lower())
156        freesound_names = _format_names(names, 90)
157
158        try:
159            with open('ba_data/data/langdata.json',
160                      encoding='utf-8') as infile:
161                translation_contributors = (json.loads(
162                    infile.read())['translation_contributors'])
163        except Exception:
164            ba.print_exception('Error reading translation contributors.')
165            translation_contributors = []
166
167        translation_names = _format_names(translation_contributors, 60)
168
169        # Need to bake this out and chop it up since we're passing our
170        # 65535 vertex limit for meshes..
171        # We can remove that limit once we drop support for GL ES2.. :-/
172        # (or add mesh splitting under the hood)
173        credits_text = (
174            '  ' + ba.Lstr(resource=self._r +
175                           '.codingGraphicsAudioText').evaluate().replace(
176                               '${NAME}', 'Eric Froemling') + '\n'
177            '\n'
178            '  ' + ba.Lstr(resource=self._r +
179                           '.additionalAudioArtIdeasText').evaluate().replace(
180                               '${NAME}', 'Raphael Suter') + '\n'
181            '\n'
182            '  ' +
183            ba.Lstr(resource=self._r + '.soundAndMusicText').evaluate() + '\n'
184            '\n' + sound_and_music + '\n'
185            '\n'
186            '     ' + ba.Lstr(resource=self._r +
187                              '.publicDomainMusicViaText').evaluate().replace(
188                                  '${NAME}', 'Musopen.com') + '\n'
189            '        ' +
190            ba.Lstr(resource=self._r +
191                    '.thanksEspeciallyToText').evaluate().replace(
192                        '${NAME}', 'the US Army, Navy, and Marine Bands') +
193            '\n'
194            '\n'
195            '     ' + ba.Lstr(resource=self._r +
196                              '.additionalMusicFromText').evaluate().replace(
197                                  '${NAME}', 'The YouTube Audio Library') +
198            '\n'
199            '\n'
200            '     ' +
201            ba.Lstr(resource=self._r + '.soundsText').evaluate().replace(
202                '${SOURCE}', 'Freesound.org') + '\n'
203            '\n' + freesound_names + '\n'
204            '\n'
205            '  ' + ba.Lstr(resource=self._r +
206                           '.languageTranslationsText').evaluate() + '\n'
207            '\n' + '\n'.join(translation_names.splitlines()[:146]) +
208            '\n'.join(translation_names.splitlines()[146:]) + '\n'
209            '\n'
210            '  Shout Out to Awesome Mods / Modders / Contributors:\n\n'
211            '     BombDash ModPack\n'
212            '     TheMikirog & SoK - BombSquad Joyride Modpack\n'
213            '     Mrmaxmeier - BombSquad-Community-Mod-Manager\n'
214            '     Ritiek Malhotra \n'
215            '     Dliwk\n'
216            '     vishal332008\n'
217            '     itsre3\n'
218            '     Drooopyyy\n'
219            '\n'
220            '  Holiday theme vector art designed by Freepik\n'
221            '\n'
222            '  ' +
223            ba.Lstr(resource=self._r + '.specialThanksText').evaluate() + '\n'
224            '\n'
225            '     Todd, Laura, and Robert Froemling\n'
226            '     ' +
227            ba.Lstr(resource=self._r + '.allMyFamilyText').evaluate().replace(
228                '\n', '\n     ') + '\n'
229            '     ' + ba.Lstr(resource=self._r +
230                              '.whoeverInventedCoffeeText').evaluate() + '\n'
231            '\n'
232            '  ' + ba.Lstr(resource=self._r + '.legalText').evaluate() + '\n'
233            '\n'
234            '     ' + ba.Lstr(resource=self._r +
235                              '.softwareBasedOnText').evaluate().replace(
236                                  '${NAME}', 'the Khronos Group') + '\n'
237            '\n'
238            '                                       '
239            '                      www.froemling.net\n')
240
241        txt = credits_text
242        lines = txt.splitlines()
243        line_height = 20
244
245        scale = 0.55
246        self._sub_width = width - 80
247        self._sub_height = line_height * len(lines) + 40
248
249        container = self._subcontainer = ba.containerwidget(
250            parent=scroll,
251            size=(self._sub_width, self._sub_height),
252            background=False,
253            claims_left_right=False,
254            claims_tab=False)
255
256        voffs = 0
257        for line in lines:
258            ba.textwidget(parent=container,
259                          padding=4,
260                          color=(0.7, 0.9, 0.7, 1.0),
261                          scale=scale,
262                          flatness=1.0,
263                          size=(0, 0),
264                          position=(0, self._sub_height - 20 + voffs),
265                          h_align='left',
266                          v_align='top',
267                          text=ba.Lstr(value=line))
268            voffs -= line_height
269
270    def _back(self) -> None:
271        from bastd.ui.mainmenu import MainMenuWindow
272        ba.containerwidget(edit=self._root_widget,
273                           transition=self._transition_out)
274        ba.app.ui.set_main_menu_window(
275            MainMenuWindow(transition='in_left').get_root_widget())
class CreditsListWindow(ba.ui.Window):
 17class CreditsListWindow(ba.Window):
 18    """Window for displaying game credits."""
 19
 20    def __init__(self, origin_widget: ba.Widget | None = None):
 21        # pylint: disable=too-many-locals
 22        # pylint: disable=too-many-statements
 23        import json
 24        ba.set_analytics_screen('Credits Window')
 25
 26        # if they provided an origin-widget, scale up from that
 27        scale_origin: tuple[float, float] | None
 28        if origin_widget is not None:
 29            self._transition_out = 'out_scale'
 30            scale_origin = origin_widget.get_screen_space_center()
 31            transition = 'in_scale'
 32        else:
 33            self._transition_out = 'out_right'
 34            scale_origin = None
 35            transition = 'in_right'
 36
 37        uiscale = ba.app.ui.uiscale
 38        width = 870 if uiscale is ba.UIScale.SMALL else 670
 39        x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
 40        height = 398 if uiscale is ba.UIScale.SMALL else 500
 41
 42        self._r = 'creditsWindow'
 43        super().__init__(root_widget=ba.containerwidget(
 44            size=(width, height),
 45            transition=transition,
 46            toolbar_visibility='menu_minimal',
 47            scale_origin_stack_offset=scale_origin,
 48            scale=(2.0 if uiscale is ba.UIScale.SMALL else
 49                   1.3 if uiscale is ba.UIScale.MEDIUM else 1.0),
 50            stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0)))
 51
 52        if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
 53            ba.containerwidget(edit=self._root_widget,
 54                               on_cancel_call=self._back)
 55        else:
 56            btn = ba.buttonwidget(
 57                parent=self._root_widget,
 58                position=(40 + x_inset, height -
 59                          (68 if uiscale is ba.UIScale.SMALL else 62)),
 60                size=(140, 60),
 61                scale=0.8,
 62                label=ba.Lstr(resource='backText'),
 63                button_type='back',
 64                on_activate_call=self._back,
 65                autoselect=True)
 66            ba.containerwidget(edit=self._root_widget, cancel_button=btn)
 67
 68            ba.buttonwidget(
 69                edit=btn,
 70                button_type='backSmall',
 71                position=(40 + x_inset, height -
 72                          (68 if uiscale is ba.UIScale.SMALL else 62) + 5),
 73                size=(60, 48),
 74                label=ba.charstr(ba.SpecialChar.BACK))
 75
 76        ba.textwidget(parent=self._root_widget,
 77                      position=(0, height -
 78                                (59 if uiscale is ba.UIScale.SMALL else 54)),
 79                      size=(width, 30),
 80                      text=ba.Lstr(resource=self._r + '.titleText',
 81                                   subs=[('${APP_NAME}',
 82                                          ba.Lstr(resource='titleText'))]),
 83                      h_align='center',
 84                      color=ba.app.ui.title_color,
 85                      maxwidth=330,
 86                      v_align='center')
 87
 88        scroll = ba.scrollwidget(parent=self._root_widget,
 89                                 position=(40 + x_inset, 35),
 90                                 size=(width - (80 + 2 * x_inset),
 91                                       height - 100),
 92                                 capture_arrows=True)
 93
 94        if ba.app.ui.use_toolbars:
 95            ba.widget(edit=scroll,
 96                      right_widget=_ba.get_special_widget('party_button'))
 97            if uiscale is ba.UIScale.SMALL:
 98                ba.widget(edit=scroll,
 99                          left_widget=_ba.get_special_widget('back_button'))
100
101        def _format_names(names2: Sequence[str], inset: float) -> str:
102            sval = ''
103            # measure a series since there's overlaps and stuff..
104            space_width = _ba.get_string_width(' ' * 10,
105                                               suppress_warning=True) / 10.0
106            spacing = 330.0
107            col1 = inset
108            col2 = col1 + spacing
109            col3 = col2 + spacing
110            line_width = 0.0
111            nline = ''
112            for name in names2:
113                # move to the next column (or row) and print
114                if line_width > col3:
115                    sval += nline + '\n'
116                    nline = ''
117                    line_width = 0
118
119                if line_width > col2:
120                    target = col3
121                elif line_width > col1:
122                    target = col2
123                else:
124                    target = col1
125                spacingstr = ' ' * int((target - line_width) / space_width)
126                nline += spacingstr
127                nline += name
128                line_width = _ba.get_string_width(nline, suppress_warning=True)
129            if nline != '':
130                sval += nline + '\n'
131            return sval
132
133        sound_and_music = ba.Lstr(resource=self._r +
134                                  '.songCreditText').evaluate()
135        sound_and_music = sound_and_music.replace(
136            '${TITLE}', "'William Tell (Trumpet Entry)'")
137        sound_and_music = sound_and_music.replace(
138            '${PERFORMER}', 'The Apollo Symphony Orchestra')
139        sound_and_music = sound_and_music.replace(
140            '${PERFORMER}', 'The Apollo Symphony Orchestra')
141        sound_and_music = sound_and_music.replace('${COMPOSER}',
142                                                  'Gioacchino Rossini')
143        sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth')
144        sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI')
145        sound_and_music = sound_and_music.replace('${SOURCE}',
146                                                  'www.AudioSparx.com')
147        spc = '     '
148        sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc)
149        names = [
150            'HubOfTheUniverseProd', 'Jovica', 'LG', 'Leady', 'Percy Duke',
151            'PhreaKsAccount', 'Pogotron', 'Rock Savage', 'anamorphosis',
152            'benboncan', 'cdrk', 'chipfork', 'guitarguy1985', 'jascha',
153            'joedeshon', 'loofa', 'm_O_m', 'mich3d', 'sandyrb', 'shakaharu',
154            'sirplus', 'stickman', 'thanvannispen', 'virotic', 'zimbot'
155        ]
156        names.sort(key=lambda x: x.lower())
157        freesound_names = _format_names(names, 90)
158
159        try:
160            with open('ba_data/data/langdata.json',
161                      encoding='utf-8') as infile:
162                translation_contributors = (json.loads(
163                    infile.read())['translation_contributors'])
164        except Exception:
165            ba.print_exception('Error reading translation contributors.')
166            translation_contributors = []
167
168        translation_names = _format_names(translation_contributors, 60)
169
170        # Need to bake this out and chop it up since we're passing our
171        # 65535 vertex limit for meshes..
172        # We can remove that limit once we drop support for GL ES2.. :-/
173        # (or add mesh splitting under the hood)
174        credits_text = (
175            '  ' + ba.Lstr(resource=self._r +
176                           '.codingGraphicsAudioText').evaluate().replace(
177                               '${NAME}', 'Eric Froemling') + '\n'
178            '\n'
179            '  ' + ba.Lstr(resource=self._r +
180                           '.additionalAudioArtIdeasText').evaluate().replace(
181                               '${NAME}', 'Raphael Suter') + '\n'
182            '\n'
183            '  ' +
184            ba.Lstr(resource=self._r + '.soundAndMusicText').evaluate() + '\n'
185            '\n' + sound_and_music + '\n'
186            '\n'
187            '     ' + ba.Lstr(resource=self._r +
188                              '.publicDomainMusicViaText').evaluate().replace(
189                                  '${NAME}', 'Musopen.com') + '\n'
190            '        ' +
191            ba.Lstr(resource=self._r +
192                    '.thanksEspeciallyToText').evaluate().replace(
193                        '${NAME}', 'the US Army, Navy, and Marine Bands') +
194            '\n'
195            '\n'
196            '     ' + ba.Lstr(resource=self._r +
197                              '.additionalMusicFromText').evaluate().replace(
198                                  '${NAME}', 'The YouTube Audio Library') +
199            '\n'
200            '\n'
201            '     ' +
202            ba.Lstr(resource=self._r + '.soundsText').evaluate().replace(
203                '${SOURCE}', 'Freesound.org') + '\n'
204            '\n' + freesound_names + '\n'
205            '\n'
206            '  ' + ba.Lstr(resource=self._r +
207                           '.languageTranslationsText').evaluate() + '\n'
208            '\n' + '\n'.join(translation_names.splitlines()[:146]) +
209            '\n'.join(translation_names.splitlines()[146:]) + '\n'
210            '\n'
211            '  Shout Out to Awesome Mods / Modders / Contributors:\n\n'
212            '     BombDash ModPack\n'
213            '     TheMikirog & SoK - BombSquad Joyride Modpack\n'
214            '     Mrmaxmeier - BombSquad-Community-Mod-Manager\n'
215            '     Ritiek Malhotra \n'
216            '     Dliwk\n'
217            '     vishal332008\n'
218            '     itsre3\n'
219            '     Drooopyyy\n'
220            '\n'
221            '  Holiday theme vector art designed by Freepik\n'
222            '\n'
223            '  ' +
224            ba.Lstr(resource=self._r + '.specialThanksText').evaluate() + '\n'
225            '\n'
226            '     Todd, Laura, and Robert Froemling\n'
227            '     ' +
228            ba.Lstr(resource=self._r + '.allMyFamilyText').evaluate().replace(
229                '\n', '\n     ') + '\n'
230            '     ' + ba.Lstr(resource=self._r +
231                              '.whoeverInventedCoffeeText').evaluate() + '\n'
232            '\n'
233            '  ' + ba.Lstr(resource=self._r + '.legalText').evaluate() + '\n'
234            '\n'
235            '     ' + ba.Lstr(resource=self._r +
236                              '.softwareBasedOnText').evaluate().replace(
237                                  '${NAME}', 'the Khronos Group') + '\n'
238            '\n'
239            '                                       '
240            '                      www.froemling.net\n')
241
242        txt = credits_text
243        lines = txt.splitlines()
244        line_height = 20
245
246        scale = 0.55
247        self._sub_width = width - 80
248        self._sub_height = line_height * len(lines) + 40
249
250        container = self._subcontainer = ba.containerwidget(
251            parent=scroll,
252            size=(self._sub_width, self._sub_height),
253            background=False,
254            claims_left_right=False,
255            claims_tab=False)
256
257        voffs = 0
258        for line in lines:
259            ba.textwidget(parent=container,
260                          padding=4,
261                          color=(0.7, 0.9, 0.7, 1.0),
262                          scale=scale,
263                          flatness=1.0,
264                          size=(0, 0),
265                          position=(0, self._sub_height - 20 + voffs),
266                          h_align='left',
267                          v_align='top',
268                          text=ba.Lstr(value=line))
269            voffs -= line_height
270
271    def _back(self) -> None:
272        from bastd.ui.mainmenu import MainMenuWindow
273        ba.containerwidget(edit=self._root_widget,
274                           transition=self._transition_out)
275        ba.app.ui.set_main_menu_window(
276            MainMenuWindow(transition='in_left').get_root_widget())

Window for displaying game credits.

CreditsListWindow(origin_widget: _ba.Widget | None = None)
 20    def __init__(self, origin_widget: ba.Widget | None = None):
 21        # pylint: disable=too-many-locals
 22        # pylint: disable=too-many-statements
 23        import json
 24        ba.set_analytics_screen('Credits Window')
 25
 26        # if they provided an origin-widget, scale up from that
 27        scale_origin: tuple[float, float] | None
 28        if origin_widget is not None:
 29            self._transition_out = 'out_scale'
 30            scale_origin = origin_widget.get_screen_space_center()
 31            transition = 'in_scale'
 32        else:
 33            self._transition_out = 'out_right'
 34            scale_origin = None
 35            transition = 'in_right'
 36
 37        uiscale = ba.app.ui.uiscale
 38        width = 870 if uiscale is ba.UIScale.SMALL else 670
 39        x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
 40        height = 398 if uiscale is ba.UIScale.SMALL else 500
 41
 42        self._r = 'creditsWindow'
 43        super().__init__(root_widget=ba.containerwidget(
 44            size=(width, height),
 45            transition=transition,
 46            toolbar_visibility='menu_minimal',
 47            scale_origin_stack_offset=scale_origin,
 48            scale=(2.0 if uiscale is ba.UIScale.SMALL else
 49                   1.3 if uiscale is ba.UIScale.MEDIUM else 1.0),
 50            stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0)))
 51
 52        if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
 53            ba.containerwidget(edit=self._root_widget,
 54                               on_cancel_call=self._back)
 55        else:
 56            btn = ba.buttonwidget(
 57                parent=self._root_widget,
 58                position=(40 + x_inset, height -
 59                          (68 if uiscale is ba.UIScale.SMALL else 62)),
 60                size=(140, 60),
 61                scale=0.8,
 62                label=ba.Lstr(resource='backText'),
 63                button_type='back',
 64                on_activate_call=self._back,
 65                autoselect=True)
 66            ba.containerwidget(edit=self._root_widget, cancel_button=btn)
 67
 68            ba.buttonwidget(
 69                edit=btn,
 70                button_type='backSmall',
 71                position=(40 + x_inset, height -
 72                          (68 if uiscale is ba.UIScale.SMALL else 62) + 5),
 73                size=(60, 48),
 74                label=ba.charstr(ba.SpecialChar.BACK))
 75
 76        ba.textwidget(parent=self._root_widget,
 77                      position=(0, height -
 78                                (59 if uiscale is ba.UIScale.SMALL else 54)),
 79                      size=(width, 30),
 80                      text=ba.Lstr(resource=self._r + '.titleText',
 81                                   subs=[('${APP_NAME}',
 82                                          ba.Lstr(resource='titleText'))]),
 83                      h_align='center',
 84                      color=ba.app.ui.title_color,
 85                      maxwidth=330,
 86                      v_align='center')
 87
 88        scroll = ba.scrollwidget(parent=self._root_widget,
 89                                 position=(40 + x_inset, 35),
 90                                 size=(width - (80 + 2 * x_inset),
 91                                       height - 100),
 92                                 capture_arrows=True)
 93
 94        if ba.app.ui.use_toolbars:
 95            ba.widget(edit=scroll,
 96                      right_widget=_ba.get_special_widget('party_button'))
 97            if uiscale is ba.UIScale.SMALL:
 98                ba.widget(edit=scroll,
 99                          left_widget=_ba.get_special_widget('back_button'))
100
101        def _format_names(names2: Sequence[str], inset: float) -> str:
102            sval = ''
103            # measure a series since there's overlaps and stuff..
104            space_width = _ba.get_string_width(' ' * 10,
105                                               suppress_warning=True) / 10.0
106            spacing = 330.0
107            col1 = inset
108            col2 = col1 + spacing
109            col3 = col2 + spacing
110            line_width = 0.0
111            nline = ''
112            for name in names2:
113                # move to the next column (or row) and print
114                if line_width > col3:
115                    sval += nline + '\n'
116                    nline = ''
117                    line_width = 0
118
119                if line_width > col2:
120                    target = col3
121                elif line_width > col1:
122                    target = col2
123                else:
124                    target = col1
125                spacingstr = ' ' * int((target - line_width) / space_width)
126                nline += spacingstr
127                nline += name
128                line_width = _ba.get_string_width(nline, suppress_warning=True)
129            if nline != '':
130                sval += nline + '\n'
131            return sval
132
133        sound_and_music = ba.Lstr(resource=self._r +
134                                  '.songCreditText').evaluate()
135        sound_and_music = sound_and_music.replace(
136            '${TITLE}', "'William Tell (Trumpet Entry)'")
137        sound_and_music = sound_and_music.replace(
138            '${PERFORMER}', 'The Apollo Symphony Orchestra')
139        sound_and_music = sound_and_music.replace(
140            '${PERFORMER}', 'The Apollo Symphony Orchestra')
141        sound_and_music = sound_and_music.replace('${COMPOSER}',
142                                                  'Gioacchino Rossini')
143        sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth')
144        sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI')
145        sound_and_music = sound_and_music.replace('${SOURCE}',
146                                                  'www.AudioSparx.com')
147        spc = '     '
148        sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc)
149        names = [
150            'HubOfTheUniverseProd', 'Jovica', 'LG', 'Leady', 'Percy Duke',
151            'PhreaKsAccount', 'Pogotron', 'Rock Savage', 'anamorphosis',
152            'benboncan', 'cdrk', 'chipfork', 'guitarguy1985', 'jascha',
153            'joedeshon', 'loofa', 'm_O_m', 'mich3d', 'sandyrb', 'shakaharu',
154            'sirplus', 'stickman', 'thanvannispen', 'virotic', 'zimbot'
155        ]
156        names.sort(key=lambda x: x.lower())
157        freesound_names = _format_names(names, 90)
158
159        try:
160            with open('ba_data/data/langdata.json',
161                      encoding='utf-8') as infile:
162                translation_contributors = (json.loads(
163                    infile.read())['translation_contributors'])
164        except Exception:
165            ba.print_exception('Error reading translation contributors.')
166            translation_contributors = []
167
168        translation_names = _format_names(translation_contributors, 60)
169
170        # Need to bake this out and chop it up since we're passing our
171        # 65535 vertex limit for meshes..
172        # We can remove that limit once we drop support for GL ES2.. :-/
173        # (or add mesh splitting under the hood)
174        credits_text = (
175            '  ' + ba.Lstr(resource=self._r +
176                           '.codingGraphicsAudioText').evaluate().replace(
177                               '${NAME}', 'Eric Froemling') + '\n'
178            '\n'
179            '  ' + ba.Lstr(resource=self._r +
180                           '.additionalAudioArtIdeasText').evaluate().replace(
181                               '${NAME}', 'Raphael Suter') + '\n'
182            '\n'
183            '  ' +
184            ba.Lstr(resource=self._r + '.soundAndMusicText').evaluate() + '\n'
185            '\n' + sound_and_music + '\n'
186            '\n'
187            '     ' + ba.Lstr(resource=self._r +
188                              '.publicDomainMusicViaText').evaluate().replace(
189                                  '${NAME}', 'Musopen.com') + '\n'
190            '        ' +
191            ba.Lstr(resource=self._r +
192                    '.thanksEspeciallyToText').evaluate().replace(
193                        '${NAME}', 'the US Army, Navy, and Marine Bands') +
194            '\n'
195            '\n'
196            '     ' + ba.Lstr(resource=self._r +
197                              '.additionalMusicFromText').evaluate().replace(
198                                  '${NAME}', 'The YouTube Audio Library') +
199            '\n'
200            '\n'
201            '     ' +
202            ba.Lstr(resource=self._r + '.soundsText').evaluate().replace(
203                '${SOURCE}', 'Freesound.org') + '\n'
204            '\n' + freesound_names + '\n'
205            '\n'
206            '  ' + ba.Lstr(resource=self._r +
207                           '.languageTranslationsText').evaluate() + '\n'
208            '\n' + '\n'.join(translation_names.splitlines()[:146]) +
209            '\n'.join(translation_names.splitlines()[146:]) + '\n'
210            '\n'
211            '  Shout Out to Awesome Mods / Modders / Contributors:\n\n'
212            '     BombDash ModPack\n'
213            '     TheMikirog & SoK - BombSquad Joyride Modpack\n'
214            '     Mrmaxmeier - BombSquad-Community-Mod-Manager\n'
215            '     Ritiek Malhotra \n'
216            '     Dliwk\n'
217            '     vishal332008\n'
218            '     itsre3\n'
219            '     Drooopyyy\n'
220            '\n'
221            '  Holiday theme vector art designed by Freepik\n'
222            '\n'
223            '  ' +
224            ba.Lstr(resource=self._r + '.specialThanksText').evaluate() + '\n'
225            '\n'
226            '     Todd, Laura, and Robert Froemling\n'
227            '     ' +
228            ba.Lstr(resource=self._r + '.allMyFamilyText').evaluate().replace(
229                '\n', '\n     ') + '\n'
230            '     ' + ba.Lstr(resource=self._r +
231                              '.whoeverInventedCoffeeText').evaluate() + '\n'
232            '\n'
233            '  ' + ba.Lstr(resource=self._r + '.legalText').evaluate() + '\n'
234            '\n'
235            '     ' + ba.Lstr(resource=self._r +
236                              '.softwareBasedOnText').evaluate().replace(
237                                  '${NAME}', 'the Khronos Group') + '\n'
238            '\n'
239            '                                       '
240            '                      www.froemling.net\n')
241
242        txt = credits_text
243        lines = txt.splitlines()
244        line_height = 20
245
246        scale = 0.55
247        self._sub_width = width - 80
248        self._sub_height = line_height * len(lines) + 40
249
250        container = self._subcontainer = ba.containerwidget(
251            parent=scroll,
252            size=(self._sub_width, self._sub_height),
253            background=False,
254            claims_left_right=False,
255            claims_tab=False)
256
257        voffs = 0
258        for line in lines:
259            ba.textwidget(parent=container,
260                          padding=4,
261                          color=(0.7, 0.9, 0.7, 1.0),
262                          scale=scale,
263                          flatness=1.0,
264                          size=(0, 0),
265                          position=(0, self._sub_height - 20 + voffs),
266                          h_align='left',
267                          v_align='top',
268                          text=ba.Lstr(value=line))
269            voffs -= line_height
Inherited Members
ba.ui.Window
get_root_widget