bcsfe 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. bcsfe/__init__.py +12 -0
  2. bcsfe/__main__.py +64 -0
  3. bcsfe/cli/__init__.py +21 -0
  4. bcsfe/cli/color.py +189 -0
  5. bcsfe/cli/dialog_creator.py +682 -0
  6. bcsfe/cli/edits/__init__.py +23 -0
  7. bcsfe/cli/edits/aku_realm.py +11 -0
  8. bcsfe/cli/edits/basic_items.py +402 -0
  9. bcsfe/cli/edits/cat_editor.py +715 -0
  10. bcsfe/cli/edits/clear_tutorial.py +11 -0
  11. bcsfe/cli/edits/enemy_editor.py +192 -0
  12. bcsfe/cli/edits/event_tickets.py +147 -0
  13. bcsfe/cli/edits/fixes.py +43 -0
  14. bcsfe/cli/edits/map.py +249 -0
  15. bcsfe/cli/edits/rare_ticket_trade.py +45 -0
  16. bcsfe/cli/feature_handler.py +294 -0
  17. bcsfe/cli/file_dialog.py +154 -0
  18. bcsfe/cli/main.py +327 -0
  19. bcsfe/cli/save_management.py +529 -0
  20. bcsfe/cli/server_cli.py +71 -0
  21. bcsfe/core/__init__.py +361 -0
  22. bcsfe/core/country_code.py +104 -0
  23. bcsfe/core/crypto.py +164 -0
  24. bcsfe/core/game/__init__.py +3 -0
  25. bcsfe/core/game/battle/__init__.py +3 -0
  26. bcsfe/core/game/battle/battle_items.py +127 -0
  27. bcsfe/core/game/battle/cleared_slots.py +306 -0
  28. bcsfe/core/game/battle/enemy.py +132 -0
  29. bcsfe/core/game/battle/slots.py +200 -0
  30. bcsfe/core/game/catbase/__init__.py +49 -0
  31. bcsfe/core/game/catbase/beacon_base.py +67 -0
  32. bcsfe/core/game/catbase/cat.py +1243 -0
  33. bcsfe/core/game/catbase/drop_chara.py +74 -0
  34. bcsfe/core/game/catbase/gatya.py +454 -0
  35. bcsfe/core/game/catbase/gatya_item.py +155 -0
  36. bcsfe/core/game/catbase/item_pack.py +190 -0
  37. bcsfe/core/game/catbase/login_bonuses.py +184 -0
  38. bcsfe/core/game/catbase/matatabi.py +69 -0
  39. bcsfe/core/game/catbase/medals.py +171 -0
  40. bcsfe/core/game/catbase/mission.py +321 -0
  41. bcsfe/core/game/catbase/my_sale.py +58 -0
  42. bcsfe/core/game/catbase/nyanko_club.py +254 -0
  43. bcsfe/core/game/catbase/officer_pass.py +79 -0
  44. bcsfe/core/game/catbase/playtime.py +97 -0
  45. bcsfe/core/game/catbase/powerup.py +179 -0
  46. bcsfe/core/game/catbase/scheme_items.py +195 -0
  47. bcsfe/core/game/catbase/special_skill.py +331 -0
  48. bcsfe/core/game/catbase/stamp.py +58 -0
  49. bcsfe/core/game/catbase/talent_orbs.py +710 -0
  50. bcsfe/core/game/catbase/unlock_popups.py +71 -0
  51. bcsfe/core/game/catbase/upgrade.py +190 -0
  52. bcsfe/core/game/catbase/user_rank_rewards.py +276 -0
  53. bcsfe/core/game/gamoto/__init__.py +9 -0
  54. bcsfe/core/game/gamoto/base_materials.py +90 -0
  55. bcsfe/core/game/gamoto/cat_shrine.py +207 -0
  56. bcsfe/core/game/gamoto/catamins.py +59 -0
  57. bcsfe/core/game/gamoto/gamatoto.py +496 -0
  58. bcsfe/core/game/gamoto/ototo.py +660 -0
  59. bcsfe/core/game/localizable.py +31 -0
  60. bcsfe/core/game/map/__init__.py +41 -0
  61. bcsfe/core/game/map/aku.py +229 -0
  62. bcsfe/core/game/map/challenge.py +73 -0
  63. bcsfe/core/game/map/chapters.py +362 -0
  64. bcsfe/core/game/map/dojo.py +360 -0
  65. bcsfe/core/game/map/enigma.py +219 -0
  66. bcsfe/core/game/map/event.py +898 -0
  67. bcsfe/core/game/map/ex_stage.py +102 -0
  68. bcsfe/core/game/map/gauntlets.py +351 -0
  69. bcsfe/core/game/map/item_reward_stage.py +416 -0
  70. bcsfe/core/game/map/legend_quest.py +381 -0
  71. bcsfe/core/game/map/map_names.py +100 -0
  72. bcsfe/core/game/map/map_reset.py +116 -0
  73. bcsfe/core/game/map/outbreaks.py +264 -0
  74. bcsfe/core/game/map/story.py +1166 -0
  75. bcsfe/core/game/map/timed_score.py +193 -0
  76. bcsfe/core/game/map/tower.py +68 -0
  77. bcsfe/core/game/map/uncanny.py +47 -0
  78. bcsfe/core/game/map/zero_legends.py +295 -0
  79. bcsfe/core/game_version.py +244 -0
  80. bcsfe/core/io/__init__.py +31 -0
  81. bcsfe/core/io/adb_handler.py +181 -0
  82. bcsfe/core/io/bc_csv.py +263 -0
  83. bcsfe/core/io/command.py +50 -0
  84. bcsfe/core/io/config.py +449 -0
  85. bcsfe/core/io/data.py +647 -0
  86. bcsfe/core/io/git_handler.py +92 -0
  87. bcsfe/core/io/json_file.py +45 -0
  88. bcsfe/core/io/path.py +269 -0
  89. bcsfe/core/io/root_handler.py +105 -0
  90. bcsfe/core/io/save.py +3730 -0
  91. bcsfe/core/io/thread_helper.py +76 -0
  92. bcsfe/core/io/waydroid.py +120 -0
  93. bcsfe/core/io/yaml.py +61 -0
  94. bcsfe/core/locale_handler.py +713 -0
  95. bcsfe/core/log.py +133 -0
  96. bcsfe/core/max_value_helper.py +35 -0
  97. bcsfe/core/server/__init__.py +21 -0
  98. bcsfe/core/server/client_info.py +34 -0
  99. bcsfe/core/server/event_data.py +377 -0
  100. bcsfe/core/server/game_data_getter.py +217 -0
  101. bcsfe/core/server/headers.py +29 -0
  102. bcsfe/core/server/managed_item.py +203 -0
  103. bcsfe/core/server/request.py +64 -0
  104. bcsfe/core/server/server_handler.py +668 -0
  105. bcsfe/core/server/updater.py +86 -0
  106. bcsfe/core/theme_handler.py +308 -0
  107. bcsfe/files/locales/en/core/config.properties +94 -0
  108. bcsfe/files/locales/en/core/files.properties +9 -0
  109. bcsfe/files/locales/en/core/input.properties +29 -0
  110. bcsfe/files/locales/en/core/locale.properties +33 -0
  111. bcsfe/files/locales/en/core/main.properties +108 -0
  112. bcsfe/files/locales/en/core/save.properties +97 -0
  113. bcsfe/files/locales/en/core/server.properties +47 -0
  114. bcsfe/files/locales/en/core/theme.properties +24 -0
  115. bcsfe/files/locales/en/core/updater.properties +19 -0
  116. bcsfe/files/locales/en/edits/bannable_items.properties +30 -0
  117. bcsfe/files/locales/en/edits/cats.properties +118 -0
  118. bcsfe/files/locales/en/edits/enemy.properties +17 -0
  119. bcsfe/files/locales/en/edits/fixes.properties +13 -0
  120. bcsfe/files/locales/en/edits/gamototo.properties +54 -0
  121. bcsfe/files/locales/en/edits/gatya.properties +5 -0
  122. bcsfe/files/locales/en/edits/gold_pass.properties +6 -0
  123. bcsfe/files/locales/en/edits/items.properties +52 -0
  124. bcsfe/files/locales/en/edits/map.properties +122 -0
  125. bcsfe/files/locales/en/edits/medals.properties +8 -0
  126. bcsfe/files/locales/en/edits/missions.properties +7 -0
  127. bcsfe/files/locales/en/edits/playtime.properties +7 -0
  128. bcsfe/files/locales/en/edits/scheme_items.properties +6 -0
  129. bcsfe/files/locales/en/edits/special_skills.properties +10 -0
  130. bcsfe/files/locales/en/edits/talent_orbs.properties +26 -0
  131. bcsfe/files/locales/en/edits/treasures.properties +24 -0
  132. bcsfe/files/locales/en/edits/user_rank.properties +9 -0
  133. bcsfe/files/locales/vi/core/config.properties +84 -0
  134. bcsfe/files/locales/vi/core/files.properties +9 -0
  135. bcsfe/files/locales/vi/core/input.properties +28 -0
  136. bcsfe/files/locales/vi/core/locale.properties +29 -0
  137. bcsfe/files/locales/vi/core/main.properties +108 -0
  138. bcsfe/files/locales/vi/core/save.properties +99 -0
  139. bcsfe/files/locales/vi/core/server.properties +67 -0
  140. bcsfe/files/locales/vi/core/theme.properties +23 -0
  141. bcsfe/files/locales/vi/core/updater.properties +21 -0
  142. bcsfe/files/locales/vi/edits/bannable_items.properties +30 -0
  143. bcsfe/files/locales/vi/edits/cats.properties +125 -0
  144. bcsfe/files/locales/vi/edits/enemy.properties +17 -0
  145. bcsfe/files/locales/vi/edits/fixes.properties +13 -0
  146. bcsfe/files/locales/vi/edits/gamototo.properties +54 -0
  147. bcsfe/files/locales/vi/edits/gatya.properties +4 -0
  148. bcsfe/files/locales/vi/edits/gold_pass.properties +6 -0
  149. bcsfe/files/locales/vi/edits/items.properties +51 -0
  150. bcsfe/files/locales/vi/edits/map.properties +118 -0
  151. bcsfe/files/locales/vi/edits/medals.properties +8 -0
  152. bcsfe/files/locales/vi/edits/missions.properties +7 -0
  153. bcsfe/files/locales/vi/edits/playtime.properties +7 -0
  154. bcsfe/files/locales/vi/edits/scheme_items.properties +6 -0
  155. bcsfe/files/locales/vi/edits/special_skills.properties +10 -0
  156. bcsfe/files/locales/vi/edits/talent_orbs.properties +25 -0
  157. bcsfe/files/locales/vi/edits/treasures.properties +24 -0
  158. bcsfe/files/locales/vi/edits/user_rank.properties +9 -0
  159. bcsfe/files/locales/vi/metadata.json +4 -0
  160. bcsfe/files/max_values.json +26 -0
  161. bcsfe/files/themes/default.json +16 -0
  162. bcsfe/py.typed +0 -0
  163. bcsfe-3.0.0.dist-info/METADATA +393 -0
  164. bcsfe-3.0.0.dist-info/RECORD +168 -0
  165. bcsfe-3.0.0.dist-info/WHEEL +5 -0
  166. bcsfe-3.0.0.dist-info/entry_points.txt +2 -0
  167. bcsfe-3.0.0.dist-info/licenses/LICENSE +675 -0
  168. bcsfe-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,682 @@
1
+ from __future__ import annotations
2
+ from typing import Any
3
+ from bcsfe import core
4
+ from bcsfe.cli import color
5
+
6
+
7
+ class RangeInput:
8
+ def __init__(self, max: int | None = None, min: int = 0):
9
+ self.max = max
10
+ self.min = min
11
+
12
+ def clamp_value(self, value: int) -> int:
13
+ if self.max is None:
14
+ return max(value, self.min)
15
+ return max(min(value, self.max), self.min)
16
+
17
+ def get_input_locale(
18
+ self,
19
+ dialog: str,
20
+ perameters: dict[str, int | str],
21
+ escape: bool = True,
22
+ ) -> list[int] | None:
23
+ user_input = color.ColoredInput(end="").localize(
24
+ dialog, escape=escape, **perameters
25
+ )
26
+ return self.parse(user_input)
27
+
28
+ def parse(self, user_input: str) -> list[int] | None:
29
+ if user_input == "":
30
+ return []
31
+ if user_input == core.core_data.local_manager.get_key("quit_key"):
32
+ return None
33
+ parts = user_input.split(" ")
34
+ ids: list[int] = []
35
+ all_text = core.core_data.local_manager.get_key("all")
36
+ for part in parts:
37
+ if "-" in part and len(part.split("-")) == 2:
38
+ lower, upper = part.split("-")
39
+ try:
40
+ lower = int(lower)
41
+ upper = int(upper)
42
+ except ValueError:
43
+ continue
44
+ if lower > upper:
45
+ lower, upper = upper, lower
46
+ if self.max is not None:
47
+ lower = max(lower, self.min)
48
+ upper = min(upper, self.max)
49
+ else:
50
+ lower = max(lower, self.min)
51
+ ids.extend(range(lower, upper + 1))
52
+ elif part.lower() == all_text.lower() and self.max is not None:
53
+ ids.extend(range(self.min, self.max + 1))
54
+ else:
55
+ try:
56
+ part = int(part)
57
+ except ValueError:
58
+ continue
59
+ if self.max is not None:
60
+ part = max(part, self.min)
61
+ part = min(part, self.max)
62
+ else:
63
+ part = max(part, self.min)
64
+ ids.append(part)
65
+ return ids
66
+
67
+
68
+ class IntInput:
69
+ def __init__(
70
+ self,
71
+ max: int | None = None,
72
+ min: int = 0,
73
+ default: int | None = None,
74
+ signed: bool = True,
75
+ ):
76
+ self.signed = signed
77
+ self.max = self.get_max_value(max, signed)
78
+ self.min = min
79
+ self.default = default
80
+
81
+ @staticmethod
82
+ def get_max_value(max: int | None, signed: bool = True) -> int:
83
+ disable_maxes = core.core_data.config.get_bool(core.ConfigKey.DISABLE_MAXES)
84
+ if signed:
85
+ max_int = 2**31 - 1
86
+ else:
87
+ max_int = 2**32 - 1
88
+ if disable_maxes or max is None:
89
+ return max_int
90
+ return min(max, max_int)
91
+
92
+ def clamp_value(self, value: int) -> int:
93
+ return max(min(value, self.max), self.min)
94
+
95
+ def get_input(
96
+ self,
97
+ localization_key: str,
98
+ perameters: dict[str, int | str],
99
+ escape: bool = True,
100
+ ) -> tuple[int | None, str]:
101
+ user_input = color.ColoredInput(end="").localize(
102
+ localization_key, escape=escape, **perameters
103
+ )
104
+ if user_input == "" and self.default is not None:
105
+ return self.default, user_input
106
+ try:
107
+ user_input_i = int(user_input)
108
+ except ValueError:
109
+ return None, user_input
110
+
111
+ return self.clamp_value(user_input_i), user_input
112
+
113
+ def get_input_locale_while(
114
+ self, dialog: str, perameters: dict[str, int | str], escape: bool = True
115
+ ) -> int | None:
116
+ while True:
117
+ int_val, user_input = self.get_input(dialog, perameters, escape=escape)
118
+ if int_val is not None:
119
+ return int_val
120
+ if user_input == core.core_data.local_manager.get_key("quit_key"):
121
+ return None
122
+
123
+ def get_input_locale(
124
+ self, localization_key: str | None, perameters: dict[str, int | str]
125
+ ) -> tuple[int | None, str]:
126
+ if localization_key is None:
127
+ if self.default is not None:
128
+ perameters = {
129
+ "min": self.min,
130
+ "max": self.max,
131
+ "default": self.default,
132
+ }
133
+ localization_key = "input_int_default"
134
+ else:
135
+ perameters = {"min": self.min, "max": self.max}
136
+ localization_key = "input_int"
137
+ return self.get_input(localization_key, perameters)
138
+
139
+ def get_basic_input_locale(self, localization_key: str, perameters: dict[str, Any]):
140
+ try:
141
+ user_input = int(
142
+ color.ColoredInput(end="").localize(localization_key, **perameters)
143
+ )
144
+ except ValueError:
145
+ return None
146
+ return user_input
147
+
148
+
149
+ class ListOutput:
150
+ def __init__(
151
+ self,
152
+ strings: list[str],
153
+ ints: list[int],
154
+ dialog: str | None = None,
155
+ perameters: dict[str, Any] | None = None,
156
+ start_index: int = 1,
157
+ localize_elements: bool = True,
158
+ ):
159
+ self.strings = strings
160
+ self.ints = ints
161
+ self.dialog = dialog
162
+ if perameters is None:
163
+ perameters = {}
164
+ self.perameters = perameters
165
+ self.start_index = start_index
166
+ self.localize_elements = localize_elements
167
+
168
+ def get_output(self, dialog: str | None, strings: list[str]) -> str:
169
+ end_string = ""
170
+ if dialog is not None:
171
+ end_string = core.core_data.local_manager.get_key(dialog, **self.perameters)
172
+ end_string += "\n"
173
+ for i, string in enumerate(strings):
174
+ try:
175
+ int_string = str(self.ints[i])
176
+ except IndexError:
177
+ int_string = ""
178
+
179
+ string = string.replace("{int}", int_string)
180
+ end_string += f" <@s>{i + self.start_index}.</> <@t>{string}</>\n"
181
+ end_string = end_string.strip("\n")
182
+ return end_string
183
+
184
+ def display(self, dialog: str | None, strings: list[str]) -> None:
185
+ output = self.get_output(dialog, strings)
186
+ color.ColoredText(output)
187
+
188
+ def display_locale(self, remove_alias: bool = False) -> None:
189
+ dialog = ""
190
+ if self.dialog is not None:
191
+ dialog = core.core_data.local_manager.get_key(self.dialog)
192
+ new_strings: list[str] = []
193
+ for string in self.strings:
194
+ if self.localize_elements:
195
+ string_ = core.core_data.local_manager.get_key(string)
196
+ else:
197
+ string_ = string
198
+ if remove_alias:
199
+ string_ = core.core_data.local_manager.get_all_aliases(string_)[0]
200
+ new_strings.append(string_)
201
+
202
+ self.display(dialog, new_strings)
203
+
204
+ def display_non_locale(self) -> None:
205
+ self.display(self.dialog, self.strings)
206
+
207
+
208
+ class ChoiceInput:
209
+ def __init__(
210
+ self,
211
+ items: list[str],
212
+ strings: list[str],
213
+ ints: list[int],
214
+ perameters: dict[str, int | str],
215
+ dialog: str,
216
+ single_choice: bool = False,
217
+ remove_alias: bool = False,
218
+ display_all_at_once: bool = True,
219
+ start_index: int = 1,
220
+ localize_options: bool = True,
221
+ ):
222
+ self.items = items
223
+ self.strings = strings
224
+ self.ints = ints
225
+ self.perameters = perameters
226
+ self.dialog = dialog
227
+ self.is_single_choice = single_choice
228
+ self.remove_alias = remove_alias
229
+ self.display_all_at_once = display_all_at_once
230
+ self.start_index = start_index
231
+ self.localize_options = localize_options
232
+
233
+ @staticmethod
234
+ def from_reduced(
235
+ items: list[str],
236
+ ints: list[int] | None = None,
237
+ perameters: dict[str, int | str] | None = None,
238
+ dialog: str | None = None,
239
+ single_choice: bool = False,
240
+ remove_alias: bool = False,
241
+ display_all_at_once: bool = True,
242
+ start_index: int = 1,
243
+ localize_options: bool = True,
244
+ ) -> ChoiceInput:
245
+ if perameters is None:
246
+ perameters = {}
247
+ if ints is None:
248
+ ints = []
249
+ if dialog is None:
250
+ dialog = ""
251
+ return ChoiceInput(
252
+ items.copy(),
253
+ items.copy(),
254
+ ints.copy(),
255
+ perameters.copy(),
256
+ dialog,
257
+ single_choice,
258
+ remove_alias,
259
+ display_all_at_once,
260
+ start_index,
261
+ localize_options,
262
+ )
263
+
264
+ def get_input(self) -> tuple[int | None, str]:
265
+ if len(self.strings) == 0:
266
+ return None, ""
267
+ if len(self.strings) == 1:
268
+ return self.get_min_value(), ""
269
+ ListOutput(
270
+ self.strings,
271
+ self.ints,
272
+ start_index=self.start_index,
273
+ localize_elements=self.localize_options,
274
+ ).display_locale(self.remove_alias)
275
+ return IntInput(self.get_max_value(), self.get_min_value()).get_input_locale(
276
+ self.dialog, self.perameters
277
+ )
278
+
279
+ def get_input_while(self) -> int | None:
280
+ if len(self.strings) == 0:
281
+ return None
282
+ while True:
283
+ int_val, user_input = self.get_input()
284
+ if int_val is not None:
285
+ return int_val
286
+ if user_input == core.core_data.local_manager.get_key("quit_key"):
287
+ return None
288
+
289
+ def get_max_value(self) -> int:
290
+ return len(self.strings) + self.start_index - 1
291
+
292
+ def get_min_value(self) -> int:
293
+ return self.start_index
294
+
295
+ def get_input_locale(self, localized: bool = True) -> tuple[list[int] | None, bool]:
296
+ if len(self.strings) == 0:
297
+ return [], False
298
+ if len(self.strings) == 1:
299
+ return [self.get_min_value()], False
300
+ if not self.is_single_choice and self.display_all_at_once:
301
+ if localized:
302
+ self.strings.append("all_at_once")
303
+ else:
304
+ self.strings.append(core.core_data.local_manager.get_key("all_at_once"))
305
+ if localized:
306
+ ListOutput(
307
+ self.strings,
308
+ self.ints,
309
+ start_index=self.start_index,
310
+ localize_elements=self.localize_options,
311
+ ).display_locale()
312
+ else:
313
+ ListOutput(
314
+ self.strings,
315
+ self.ints,
316
+ start_index=self.start_index,
317
+ localize_elements=self.localize_options,
318
+ ).display_non_locale()
319
+ key = "input_many"
320
+ if self.is_single_choice:
321
+ key = "input_single"
322
+ dialog = core.core_data.local_manager.get_key(key).format(
323
+ min=self.get_min_value(), max=self.get_max_value()
324
+ )
325
+ usr_input = color.ColoredInput().get(dialog).split(" ")
326
+ int_vals: list[int] = []
327
+ for inp in usr_input:
328
+ try:
329
+ value = int(inp)
330
+ if value > self.get_max_value() or value < self.get_min_value():
331
+ raise ValueError
332
+ int_vals.append(value)
333
+ except ValueError:
334
+ if inp == core.core_data.local_manager.get_key("quit_key"):
335
+ return None, False
336
+ if inp in self.strings:
337
+ int_vals.append(self.strings.index(inp) + self.start_index)
338
+ continue
339
+ color.ColoredText.localize(
340
+ "invalid_input_int",
341
+ min=self.get_min_value(),
342
+ max=self.get_max_value(),
343
+ )
344
+ if (
345
+ self.get_max_value() in int_vals
346
+ and not self.is_single_choice
347
+ and self.display_all_at_once
348
+ ):
349
+ return list(range(self.get_min_value(), self.get_max_value())), True
350
+
351
+ if self.is_single_choice and len(int_vals) > 1:
352
+ int_vals = [int_vals[0]]
353
+
354
+ return int_vals, False
355
+
356
+ def get_input_locale_while(self) -> list[int] | None:
357
+ if len(self.strings) == 0:
358
+ return []
359
+ if len(self.strings) == 1:
360
+ return [self.get_min_value()]
361
+ while True:
362
+ int_vals, all_at_once = self.get_input_locale()
363
+ if int_vals is None:
364
+ return None
365
+ if all_at_once:
366
+ return int_vals
367
+ if len(int_vals) == 0:
368
+ continue
369
+ if len(int_vals) == 1 and int_vals[0] == 0:
370
+ return []
371
+ return int_vals
372
+
373
+ def multiple_choice(
374
+ self, localized_options: bool = True
375
+ ) -> tuple[list[int] | None, bool]:
376
+ color.ColoredText.localize(self.dialog, True, **self.perameters)
377
+ user_input, all_at_once = self.get_input_locale(localized_options)
378
+ if user_input is None:
379
+ return None, all_at_once
380
+ return [i - self.start_index for i in user_input], all_at_once
381
+
382
+ def single_choice(self) -> int | None:
383
+ return self.get_input_while()
384
+
385
+ def get(self) -> tuple[int | None | list[int], bool]:
386
+ if self.is_single_choice:
387
+ return self.single_choice(), False
388
+ return self.multiple_choice()
389
+
390
+
391
+ class MultiEditor:
392
+ def __init__(
393
+ self,
394
+ group_name: str,
395
+ items: list[str],
396
+ strings: list[str],
397
+ ints: list[int] | None,
398
+ max_values: list[int] | int | None,
399
+ perameters: dict[str, int | str] | None,
400
+ dialog: str,
401
+ single_choice: bool = False,
402
+ signed: bool = True,
403
+ group_name_localized: bool = False,
404
+ cumulative_max: bool = False,
405
+ ):
406
+ self.items = items
407
+ self.strings = strings
408
+ self.ints = ints
409
+ if self.ints is not None:
410
+ total_ints = len(self.ints)
411
+ else:
412
+ total_ints = len(self.strings)
413
+ if max_values is None:
414
+ max_values_ = [None] * total_ints
415
+ elif isinstance(max_values, int):
416
+ max_values_ = [max_values] * total_ints
417
+ else:
418
+ max_values_ = max_values
419
+ self.max_values = max_values_
420
+ if perameters is None:
421
+ perameters = {}
422
+ self.perameters = perameters
423
+ if group_name_localized:
424
+ self.perameters["group_name"] = core.core_data.local_manager.get_key(
425
+ group_name
426
+ )
427
+ else:
428
+ self.perameters["group_name"] = group_name
429
+ self.dialog = dialog
430
+ self.is_single_choice = single_choice
431
+ self.signed = signed
432
+ self.cumulative_max = cumulative_max
433
+
434
+ @staticmethod
435
+ def from_reduced(
436
+ group_name: str,
437
+ items: list[str],
438
+ ints: list[int] | None,
439
+ max_values: list[int] | int | None,
440
+ group_name_localized: bool = False,
441
+ dialog: str = "input",
442
+ cumulative_max: bool = False,
443
+ items_localized: bool = False,
444
+ ):
445
+ if items_localized:
446
+ for i, item in enumerate(items):
447
+ items[i] = core.core_data.local_manager.get_key(item)
448
+ text: list[str] = []
449
+ for item_name in items:
450
+ if ints is not None:
451
+ text.append(f"{item_name} <@q>: {{int}}</>")
452
+ else:
453
+ text.append(f"{item_name}")
454
+ return MultiEditor(
455
+ group_name,
456
+ items,
457
+ text,
458
+ ints,
459
+ max_values,
460
+ None,
461
+ dialog,
462
+ group_name_localized=group_name_localized,
463
+ cumulative_max=cumulative_max,
464
+ )
465
+
466
+ def edit(self) -> list[int]:
467
+ choices, all_at_once = ChoiceInput(
468
+ self.items,
469
+ self.strings,
470
+ self.ints or [],
471
+ self.perameters,
472
+ "select_edit",
473
+ ).get()
474
+ if choices is None:
475
+ return self.ints or []
476
+ if isinstance(choices, int):
477
+ choices = [choices]
478
+ if all_at_once:
479
+ return self.edit_all(choices)
480
+ return self.edit_one(choices)
481
+
482
+ def edit_all(self, choices: list[int]) -> list[int]:
483
+ max_max_value = 0
484
+ for choice in choices:
485
+ if choice >= len(self.max_values):
486
+ max_value = None
487
+ else:
488
+ max_value = self.max_values[choice]
489
+ if max_value is None:
490
+ max_value = IntInput.get_max_value(max_value, self.signed)
491
+ max_max_value = max(max_max_value, max_value)
492
+ if self.cumulative_max:
493
+ max_max_value = max_max_value // len(choices)
494
+ usr_input = IntInput(max_max_value, default=None).get_input_locale_while(
495
+ self.dialog + "_all",
496
+ {
497
+ "name": self.perameters["group_name"],
498
+ "max": max_max_value,
499
+ },
500
+ )
501
+ if usr_input is None:
502
+ return self.ints or []
503
+ ints = self.ints or [0] * len(self.strings)
504
+
505
+ for choice in choices:
506
+ if choice >= len(self.max_values):
507
+ max_value = None
508
+ else:
509
+ max_value = self.max_values[choice]
510
+ max_value = IntInput.get_max_value(max_value, self.signed)
511
+ value = min(usr_input, max_value)
512
+ ints[choice] = value
513
+ if self.ints is not None:
514
+ color.ColoredText.localize(
515
+ "value_changed",
516
+ name=self.items[choice],
517
+ value=value,
518
+ )
519
+
520
+ return ints
521
+
522
+ def edit_one(self, choices: list[int]) -> list[int]:
523
+ ints = self.ints or [0] * len(self.strings)
524
+
525
+ for choice in choices:
526
+ if choice >= len(self.max_values):
527
+ max_value = None
528
+ else:
529
+ max_value = self.max_values[choice]
530
+ if max_value is None:
531
+ max_value = IntInput.get_max_value(max_value, self.signed)
532
+
533
+ if self.cumulative_max:
534
+ max_value -= sum(ints) - ints[choice]
535
+ max_value = max(max_value, 0)
536
+
537
+ item = self.items[choice]
538
+ usr_input = IntInput(
539
+ max_value, default=ints[choice]
540
+ ).get_input_locale_while(
541
+ self.dialog,
542
+ {"name": item, "value": ints[choice], "max": max_value},
543
+ escape=False,
544
+ )
545
+ if usr_input is None:
546
+ continue
547
+ ints[choice] = usr_input
548
+ color.ColoredText.localize(
549
+ "value_changed", name=item, value=ints[choice], escape=False
550
+ )
551
+ return ints
552
+
553
+
554
+ class SingleEditor:
555
+ def __init__(
556
+ self,
557
+ item: str,
558
+ value: int,
559
+ max_value: int | None = None,
560
+ min_value: int = 0,
561
+ signed: bool = True,
562
+ localized_item: bool = False,
563
+ remove_alias: bool = False,
564
+ ):
565
+ if localized_item:
566
+ item = core.core_data.local_manager.get_key(item)
567
+ if remove_alias:
568
+ item = core.core_data.local_manager.get_all_aliases(item)[0]
569
+ self.item = item
570
+ self.value = value
571
+ self.max_value = max_value
572
+ self.min_value = min_value
573
+ self.signed = signed
574
+
575
+ def edit(self, escape_text: bool = True) -> int:
576
+ max_value = self.max_value
577
+ if max_value is None:
578
+ max_value = IntInput.get_max_value(max_value, self.signed)
579
+ if self.max_value is None:
580
+ dialog = "input_non_max"
581
+ elif self.min_value != 0:
582
+ dialog = "input_min"
583
+ else:
584
+ dialog = "input"
585
+ usr_input = IntInput(
586
+ max_value, self.min_value, default=self.value, signed=self.signed
587
+ ).get_input_locale_while(
588
+ dialog,
589
+ {
590
+ "name": self.item,
591
+ "value": self.value,
592
+ "max": max_value,
593
+ "min": self.min_value,
594
+ },
595
+ escape=escape_text,
596
+ )
597
+ if usr_input is None:
598
+ return self.value
599
+ print()
600
+ color.ColoredText.localize(
601
+ "value_changed", name=self.item, value=usr_input, escape=escape_text
602
+ )
603
+ return usr_input
604
+
605
+
606
+ class StringInput:
607
+ def __init__(self, default: str = ""):
608
+ self.default = default
609
+
610
+ def get_input_locale_while(
611
+ self, key: str, perameters: dict[str, Any]
612
+ ) -> str | None:
613
+ while True:
614
+ usr_input = self.get_input_locale(key, perameters)
615
+ if usr_input is None:
616
+ return None
617
+ if usr_input == "":
618
+ return self.default
619
+ if usr_input == " ":
620
+ continue
621
+ return usr_input
622
+
623
+ def get_input_locale(
624
+ self,
625
+ key: str,
626
+ perameters: dict[str, Any] | None = None,
627
+ escape: bool = True,
628
+ ) -> str | None:
629
+ if perameters is None:
630
+ perameters = {}
631
+ usr_input = color.ColoredInput().localize(key, escape, **perameters)
632
+ quit_key = core.core_data.local_manager.get_key("quit_key")
633
+ if usr_input == "" or usr_input == quit_key:
634
+ return None
635
+ if usr_input == f"\\{quit_key}":
636
+ return quit_key
637
+ return usr_input
638
+
639
+
640
+ class StringEditor:
641
+ def __init__(self, item: str, value: str, item_localized: bool = False):
642
+ if item_localized:
643
+ item = core.core_data.local_manager.get_key(item)
644
+ self.item = item
645
+ self.value = value
646
+
647
+ def edit(self) -> str:
648
+ usr_input = StringInput(default=self.value).get_input_locale_while(
649
+ "input_non_max",
650
+ {"name": self.item, "value": self.value},
651
+ )
652
+ if usr_input is None:
653
+ return self.value
654
+ color.ColoredText.localize(
655
+ "value_changed",
656
+ name=self.item,
657
+ value=usr_input,
658
+ )
659
+ return usr_input
660
+
661
+
662
+ class YesNoInput:
663
+ def __init__(self, default: bool = False):
664
+ self.default = default
665
+
666
+ def get_input_once(
667
+ self, key: str, perameters: dict[str, Any] | None = None
668
+ ) -> bool | None:
669
+ if perameters is None:
670
+ perameters = {}
671
+ usr_input = color.ColoredInput().localize(key, **perameters)
672
+ if usr_input == "":
673
+ return self.default
674
+
675
+ if usr_input == core.core_data.local_manager.get_key("quit_key"):
676
+ return None
677
+ return usr_input == core.core_data.local_manager.get_key("yes_key")
678
+
679
+
680
+ class DialogBuilder:
681
+ def __init__(self, dialog_structure: dict[Any, Any]):
682
+ self.dialog_structure = dialog_structure
@@ -0,0 +1,23 @@
1
+ from bcsfe.cli.edits import (
2
+ basic_items,
3
+ cat_editor,
4
+ clear_tutorial,
5
+ rare_ticket_trade,
6
+ fixes,
7
+ enemy_editor,
8
+ aku_realm,
9
+ map,
10
+ event_tickets,
11
+ )
12
+
13
+ __all__ = [
14
+ "basic_items",
15
+ "cat_editor",
16
+ "clear_tutorial",
17
+ "rare_ticket_trade",
18
+ "fixes",
19
+ "enemy_editor",
20
+ "aku_realm",
21
+ "map",
22
+ "event_tickets"
23
+ ]
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+ from bcsfe import core
3
+ from bcsfe.cli import color
4
+
5
+
6
+ def unlock_aku_realm(save_file: core.SaveFile):
7
+ stage_ids = [255, 256, 257, 258, 265, 266, 268]
8
+ for stage_id in stage_ids:
9
+ save_file.event_stages.clear_map(1, stage_id, 0, False)
10
+
11
+ color.ColoredText.localize("aku_realm_unlocked")