zombie-escape 1.7.1__py3-none-any.whl → 1.10.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.
- zombie_escape/__about__.py +1 -1
- zombie_escape/colors.py +41 -8
- zombie_escape/entities.py +376 -306
- zombie_escape/entities_constants.py +6 -0
- zombie_escape/gameplay/__init__.py +2 -2
- zombie_escape/gameplay/constants.py +1 -7
- zombie_escape/gameplay/footprints.py +2 -2
- zombie_escape/gameplay/interactions.py +4 -10
- zombie_escape/gameplay/layout.py +43 -4
- zombie_escape/gameplay/movement.py +45 -7
- zombie_escape/gameplay/spawn.py +283 -43
- zombie_escape/gameplay/state.py +19 -16
- zombie_escape/gameplay/survivors.py +47 -15
- zombie_escape/gameplay/utils.py +19 -1
- zombie_escape/input_utils.py +167 -0
- zombie_escape/level_blueprints.py +28 -0
- zombie_escape/locales/ui.en.json +55 -11
- zombie_escape/locales/ui.ja.json +54 -10
- zombie_escape/localization.py +28 -0
- zombie_escape/models.py +54 -7
- zombie_escape/render.py +704 -267
- zombie_escape/render_constants.py +12 -0
- zombie_escape/screens/__init__.py +1 -0
- zombie_escape/screens/game_over.py +8 -4
- zombie_escape/screens/gameplay.py +88 -41
- zombie_escape/screens/settings.py +124 -13
- zombie_escape/screens/title.py +111 -0
- zombie_escape/stage_constants.py +116 -3
- zombie_escape/world_grid.py +134 -0
- zombie_escape/zombie_escape.py +68 -61
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.10.0.dist-info}/METADATA +11 -3
- zombie_escape-1.10.0.dist-info/RECORD +47 -0
- zombie_escape-1.7.1.dist-info/RECORD +0 -45
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.10.0.dist-info}/WHEEL +0 -0
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.10.0.dist-info}/entry_points.txt +0 -0
- {zombie_escape-1.7.1.dist-info → zombie_escape-1.10.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -11,6 +11,9 @@ HUMANOID_OUTLINE_COLOR = (0, 80, 200)
|
|
|
11
11
|
HUMANOID_OUTLINE_WIDTH = 1
|
|
12
12
|
BUDDY_COLOR = (0, 180, 63)
|
|
13
13
|
SURVIVOR_COLOR = (198, 198, 198)
|
|
14
|
+
FALLING_ZOMBIE_COLOR = (45, 45, 45)
|
|
15
|
+
FALLING_WHIRLWIND_COLOR = (200, 200, 200, 120)
|
|
16
|
+
FALLING_DUST_COLOR = (70, 70, 70, 130)
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
@dataclass(frozen=True)
|
|
@@ -35,11 +38,14 @@ class RenderAssets:
|
|
|
35
38
|
footprint_min_fade: float
|
|
36
39
|
internal_wall_grid_snap: int
|
|
37
40
|
flashlight_bonus_step: float
|
|
41
|
+
flashlight_hatch_extra_scale: float
|
|
42
|
+
|
|
38
43
|
|
|
39
44
|
FOG_RADIUS_SCALE = 1.2
|
|
40
45
|
FOG_HATCH_PIXEL_SCALE = 2
|
|
41
46
|
|
|
42
47
|
FLASHLIGHT_FOG_SCALE_STEP = 0.3
|
|
48
|
+
FLASHLIGHT_HATCH_EXTRA_SCALE = 0.12
|
|
43
49
|
|
|
44
50
|
FOOTPRINT_RADIUS = 2
|
|
45
51
|
FOOTPRINT_OVERVIEW_RADIUS = 3
|
|
@@ -55,6 +61,7 @@ FOG_RINGS = [
|
|
|
55
61
|
FogRing(radius_factor=0.968, thickness=12),
|
|
56
62
|
]
|
|
57
63
|
|
|
64
|
+
|
|
58
65
|
def build_render_assets(cell_size: int) -> RenderAssets:
|
|
59
66
|
return RenderAssets(
|
|
60
67
|
screen_width=SCREEN_WIDTH,
|
|
@@ -71,11 +78,15 @@ def build_render_assets(cell_size: int) -> RenderAssets:
|
|
|
71
78
|
footprint_min_fade=FOOTPRINT_MIN_FADE,
|
|
72
79
|
internal_wall_grid_snap=cell_size,
|
|
73
80
|
flashlight_bonus_step=FLASHLIGHT_FOG_SCALE_STEP,
|
|
81
|
+
flashlight_hatch_extra_scale=FLASHLIGHT_HATCH_EXTRA_SCALE,
|
|
74
82
|
)
|
|
75
83
|
|
|
76
84
|
|
|
77
85
|
__all__ = [
|
|
78
86
|
"BUDDY_COLOR",
|
|
87
|
+
"FALLING_ZOMBIE_COLOR",
|
|
88
|
+
"FALLING_WHIRLWIND_COLOR",
|
|
89
|
+
"FALLING_DUST_COLOR",
|
|
79
90
|
"HUMANOID_OUTLINE_COLOR",
|
|
80
91
|
"HUMANOID_OUTLINE_WIDTH",
|
|
81
92
|
"SURVIVOR_COLOR",
|
|
@@ -83,5 +94,6 @@ __all__ = [
|
|
|
83
94
|
"RenderAssets",
|
|
84
95
|
"FOG_RADIUS_SCALE",
|
|
85
96
|
"FLASHLIGHT_FOG_SCALE_STEP",
|
|
97
|
+
"FLASHLIGHT_HATCH_EXTRA_SCALE",
|
|
86
98
|
"build_render_assets",
|
|
87
99
|
]
|
|
@@ -159,6 +159,7 @@ def toggle_fullscreen(
|
|
|
159
159
|
window_width, window_height = _fetch_window_size(window)
|
|
160
160
|
_update_window_caption(window_width, window_height)
|
|
161
161
|
_update_window_size((window_width, window_height), source="toggle_fullscreen")
|
|
162
|
+
pygame.mouse.set_visible(not current_maximized)
|
|
162
163
|
if game_data is not None:
|
|
163
164
|
game_data.state.overview_created = False
|
|
164
165
|
return window
|
|
@@ -14,6 +14,7 @@ from ..render import (
|
|
|
14
14
|
_draw_status_bar,
|
|
15
15
|
show_message,
|
|
16
16
|
)
|
|
17
|
+
from ..input_utils import is_confirm_event, is_select_event
|
|
17
18
|
from ..screens import (
|
|
18
19
|
ScreenID,
|
|
19
20
|
ScreenTransition,
|
|
@@ -129,16 +130,16 @@ def game_over_screen(
|
|
|
129
130
|
LIGHT_GRAY,
|
|
130
131
|
(screen_width // 2, summary_y),
|
|
131
132
|
)
|
|
132
|
-
elif stage and stage.
|
|
133
|
-
elapsed_ms = max(0, state.
|
|
134
|
-
goal_ms = max(0, state.
|
|
133
|
+
elif stage and stage.endurance_stage:
|
|
134
|
+
elapsed_ms = max(0, state.endurance_elapsed_ms)
|
|
135
|
+
goal_ms = max(0, state.endurance_goal_ms)
|
|
135
136
|
if goal_ms:
|
|
136
137
|
elapsed_ms = min(elapsed_ms, goal_ms)
|
|
137
138
|
display_ms = int(elapsed_ms * SURVIVAL_FAKE_CLOCK_RATIO)
|
|
138
139
|
hours = display_ms // 3_600_000
|
|
139
140
|
minutes = (display_ms % 3_600_000) // 60_000
|
|
140
141
|
time_label = f"{int(hours):02d}:{int(minutes):02d}"
|
|
141
|
-
msg = tr("game_over.
|
|
142
|
+
msg = tr("game_over.endurance_duration", time=time_label)
|
|
142
143
|
show_message(
|
|
143
144
|
screen,
|
|
144
145
|
msg,
|
|
@@ -181,3 +182,6 @@ def game_over_screen(
|
|
|
181
182
|
stage=stage,
|
|
182
183
|
seed=state.seed,
|
|
183
184
|
)
|
|
185
|
+
if event.type in (pygame.CONTROLLERBUTTONDOWN, pygame.JOYBUTTONDOWN):
|
|
186
|
+
if is_select_event(event) or is_confirm_event(event):
|
|
187
|
+
return ScreenTransition(ScreenID.TITLE)
|
|
@@ -8,7 +8,6 @@ from pygame import surface, time
|
|
|
8
8
|
from ..colors import LIGHT_GRAY, RED, WHITE, YELLOW
|
|
9
9
|
from ..gameplay_constants import (
|
|
10
10
|
CAR_HINT_DELAY_MS_DEFAULT,
|
|
11
|
-
DEFAULT_FLASHLIGHT_SPAWN_COUNT,
|
|
12
11
|
SURVIVAL_TIME_ACCEL_SUBSTEPS,
|
|
13
12
|
SURVIVAL_TIME_ACCEL_MAX_SUBSTEP,
|
|
14
13
|
)
|
|
@@ -29,10 +28,21 @@ from ..gameplay import (
|
|
|
29
28
|
sync_ambient_palette_with_flashlights,
|
|
30
29
|
update_entities,
|
|
31
30
|
update_footprints,
|
|
32
|
-
|
|
31
|
+
update_endurance_timer,
|
|
32
|
+
)
|
|
33
|
+
from ..input_utils import (
|
|
34
|
+
CONTROLLER_BUTTON_DOWN,
|
|
35
|
+
CONTROLLER_DEVICE_ADDED,
|
|
36
|
+
CONTROLLER_DEVICE_REMOVED,
|
|
37
|
+
init_first_controller,
|
|
38
|
+
init_first_joystick,
|
|
39
|
+
is_accel_active,
|
|
40
|
+
is_select_event,
|
|
41
|
+
is_start_event,
|
|
42
|
+
read_gamepad_move,
|
|
33
43
|
)
|
|
34
44
|
from ..gameplay.spawn import _alive_waiting_cars
|
|
35
|
-
from ..
|
|
45
|
+
from ..world_grid import build_wall_index
|
|
36
46
|
from ..localization import translate as tr
|
|
37
47
|
from ..models import Stage
|
|
38
48
|
from ..render import draw, prewarm_fog_overlays, show_message
|
|
@@ -72,11 +82,11 @@ def gameplay_screen(
|
|
|
72
82
|
game_data = initialize_game_state(config, stage)
|
|
73
83
|
game_data.state.seed = applied_seed
|
|
74
84
|
game_data.state.debug_mode = debug_mode
|
|
75
|
-
if debug_mode and stage.
|
|
76
|
-
goal_ms = max(0, stage.
|
|
85
|
+
if debug_mode and stage.endurance_stage:
|
|
86
|
+
goal_ms = max(0, stage.endurance_goal_ms)
|
|
77
87
|
if goal_ms > 0:
|
|
78
88
|
remaining = 3 * 60 * 1000 # 3 minutes in ms
|
|
79
|
-
game_data.state.
|
|
89
|
+
game_data.state.endurance_elapsed_ms = max(0, goal_ms - remaining)
|
|
80
90
|
game_data.state.dawn_ready = False
|
|
81
91
|
game_data.state.dawn_prompt_at = None
|
|
82
92
|
game_data.state.dawn_carbonized = False
|
|
@@ -88,7 +98,8 @@ def gameplay_screen(
|
|
|
88
98
|
paused_manual = False
|
|
89
99
|
paused_focus = False
|
|
90
100
|
ignore_focus_loss_until = 0
|
|
91
|
-
|
|
101
|
+
controller = init_first_controller()
|
|
102
|
+
joystick = init_first_joystick() if controller is None else None
|
|
92
103
|
|
|
93
104
|
layout_data = generate_level_from_blueprint(game_data, config)
|
|
94
105
|
sync_ambient_palette_with_flashlights(game_data, force=True)
|
|
@@ -118,19 +129,18 @@ def gameplay_screen(
|
|
|
118
129
|
if fuel_can:
|
|
119
130
|
game_data.fuel = fuel_can
|
|
120
131
|
game_data.groups.all_sprites.add(fuel_can, layer=1)
|
|
132
|
+
flashlight_count = stage.initial_flashlight_count
|
|
121
133
|
flashlights = place_flashlights(
|
|
122
134
|
layout_data["walkable_cells"],
|
|
123
135
|
player,
|
|
124
136
|
cars=game_data.waiting_cars,
|
|
125
|
-
count=max(
|
|
137
|
+
count=max(0, flashlight_count),
|
|
126
138
|
)
|
|
127
139
|
game_data.flashlights = flashlights
|
|
128
140
|
game_data.groups.all_sprites.add(flashlights, layer=1)
|
|
129
141
|
|
|
130
142
|
spawn_initial_zombies(game_data, player, layout_data, config)
|
|
131
143
|
update_footprints(game_data, config)
|
|
132
|
-
last_fov_target = player
|
|
133
|
-
|
|
134
144
|
while True:
|
|
135
145
|
dt = clock.tick(fps) / 1000.0
|
|
136
146
|
if game_data.state.game_over or game_data.state.game_won:
|
|
@@ -144,7 +154,6 @@ def gameplay_screen(
|
|
|
144
154
|
render_assets,
|
|
145
155
|
screen,
|
|
146
156
|
game_data,
|
|
147
|
-
last_fov_target,
|
|
148
157
|
config=config,
|
|
149
158
|
hint_color=None,
|
|
150
159
|
present_fn=present,
|
|
@@ -180,7 +189,22 @@ def gameplay_screen(
|
|
|
180
189
|
paused_focus = False
|
|
181
190
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
182
191
|
paused_focus = False
|
|
183
|
-
|
|
192
|
+
if event.type == pygame.JOYDEVICEADDED or (
|
|
193
|
+
CONTROLLER_DEVICE_ADDED is not None
|
|
194
|
+
and event.type == CONTROLLER_DEVICE_ADDED
|
|
195
|
+
):
|
|
196
|
+
if controller is None:
|
|
197
|
+
controller = init_first_controller()
|
|
198
|
+
if controller is None:
|
|
199
|
+
joystick = init_first_joystick()
|
|
200
|
+
if event.type == pygame.JOYDEVICEREMOVED or (
|
|
201
|
+
CONTROLLER_DEVICE_REMOVED is not None
|
|
202
|
+
and event.type == CONTROLLER_DEVICE_REMOVED
|
|
203
|
+
):
|
|
204
|
+
if controller and not controller.get_init():
|
|
205
|
+
controller = None
|
|
206
|
+
if joystick and not joystick.get_init():
|
|
207
|
+
joystick = None
|
|
184
208
|
if event.type == pygame.KEYDOWN:
|
|
185
209
|
if event.key == pygame.K_s and (
|
|
186
210
|
pygame.key.get_mods() & pygame.KMOD_CTRL
|
|
@@ -192,10 +216,40 @@ def gameplay_screen(
|
|
|
192
216
|
}
|
|
193
217
|
print("STATE DEBUG:", state_snapshot)
|
|
194
218
|
continue
|
|
195
|
-
if
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
if debug_mode:
|
|
220
|
+
if event.key == pygame.K_ESCAPE:
|
|
221
|
+
return ScreenTransition(ScreenID.TITLE)
|
|
222
|
+
if event.key == pygame.K_p:
|
|
223
|
+
paused_manual = not paused_manual
|
|
224
|
+
continue
|
|
225
|
+
if paused_manual:
|
|
226
|
+
if event.key == pygame.K_ESCAPE:
|
|
227
|
+
return ScreenTransition(ScreenID.TITLE)
|
|
228
|
+
if event.key == pygame.K_p:
|
|
229
|
+
paused_manual = False
|
|
230
|
+
continue
|
|
231
|
+
if event.key in (pygame.K_ESCAPE, pygame.K_p):
|
|
232
|
+
paused_manual = True
|
|
233
|
+
continue
|
|
234
|
+
if event.type == pygame.JOYBUTTONDOWN or (
|
|
235
|
+
CONTROLLER_BUTTON_DOWN is not None
|
|
236
|
+
and event.type == CONTROLLER_BUTTON_DOWN
|
|
237
|
+
):
|
|
238
|
+
if debug_mode:
|
|
239
|
+
if is_select_event(event):
|
|
240
|
+
return ScreenTransition(ScreenID.TITLE)
|
|
241
|
+
if is_start_event(event):
|
|
242
|
+
paused_manual = not paused_manual
|
|
243
|
+
continue
|
|
244
|
+
if paused_manual:
|
|
245
|
+
if is_select_event(event):
|
|
246
|
+
return ScreenTransition(ScreenID.TITLE)
|
|
247
|
+
if is_start_event(event):
|
|
248
|
+
paused_manual = False
|
|
249
|
+
continue
|
|
250
|
+
if is_select_event(event) or is_start_event(event):
|
|
251
|
+
paused_manual = True
|
|
252
|
+
continue
|
|
199
253
|
|
|
200
254
|
paused = paused_manual or paused_focus
|
|
201
255
|
if paused:
|
|
@@ -203,7 +257,6 @@ def gameplay_screen(
|
|
|
203
257
|
render_assets,
|
|
204
258
|
screen,
|
|
205
259
|
game_data,
|
|
206
|
-
last_fov_target,
|
|
207
260
|
config=config,
|
|
208
261
|
do_flip=not show_pause_overlay,
|
|
209
262
|
present_fn=present,
|
|
@@ -211,17 +264,19 @@ def gameplay_screen(
|
|
|
211
264
|
if show_pause_overlay:
|
|
212
265
|
overlay = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA)
|
|
213
266
|
overlay.fill((0, 0, 0, 150))
|
|
267
|
+
pause_radius = 53
|
|
268
|
+
cx = screen_width // 2
|
|
269
|
+
cy = screen_height // 2 - 18
|
|
214
270
|
pygame.draw.circle(
|
|
215
271
|
overlay,
|
|
216
272
|
LIGHT_GRAY,
|
|
217
|
-
(
|
|
218
|
-
|
|
273
|
+
(cx, cy),
|
|
274
|
+
pause_radius,
|
|
219
275
|
width=3,
|
|
220
276
|
)
|
|
221
|
-
bar_width =
|
|
222
|
-
bar_height =
|
|
223
|
-
gap =
|
|
224
|
-
cx, cy = screen_width // 2, screen_height // 2
|
|
277
|
+
bar_width = 10
|
|
278
|
+
bar_height = 38
|
|
279
|
+
gap = 12
|
|
225
280
|
pygame.draw.rect(
|
|
226
281
|
overlay,
|
|
227
282
|
LIGHT_GRAY,
|
|
@@ -238,12 +293,12 @@ def gameplay_screen(
|
|
|
238
293
|
tr("hud.paused"),
|
|
239
294
|
18,
|
|
240
295
|
WHITE,
|
|
241
|
-
(screen_width // 2,
|
|
296
|
+
(screen_width // 2, 28),
|
|
242
297
|
)
|
|
243
298
|
show_message(
|
|
244
299
|
screen,
|
|
245
|
-
tr("hud.
|
|
246
|
-
|
|
300
|
+
tr("hud.pause_hint"),
|
|
301
|
+
16,
|
|
247
302
|
LIGHT_GRAY,
|
|
248
303
|
(screen_width // 2, screen_height // 2 + 70),
|
|
249
304
|
)
|
|
@@ -254,8 +309,8 @@ def gameplay_screen(
|
|
|
254
309
|
accel_allowed = not (
|
|
255
310
|
game_data.state.game_over or game_data.state.game_won
|
|
256
311
|
)
|
|
257
|
-
accel_active = accel_allowed and (
|
|
258
|
-
keys
|
|
312
|
+
accel_active = accel_allowed and is_accel_active(
|
|
313
|
+
keys, controller, joystick
|
|
259
314
|
)
|
|
260
315
|
game_data.state.time_accel_active = accel_active
|
|
261
316
|
substeps = SURVIVAL_TIME_ACCEL_SUBSTEPS if accel_active else 1
|
|
@@ -265,14 +320,14 @@ def gameplay_screen(
|
|
|
265
320
|
wall_index = build_wall_index(
|
|
266
321
|
game_data.groups.wall_group, cell_size=game_data.cell_size
|
|
267
322
|
)
|
|
268
|
-
frame_fov_target = None
|
|
269
323
|
for _ in range(substeps):
|
|
270
324
|
player_ref = game_data.player
|
|
271
325
|
if player_ref is None:
|
|
272
326
|
break
|
|
273
327
|
car_ref = game_data.car
|
|
328
|
+
pad_vector = read_gamepad_move(controller, joystick)
|
|
274
329
|
player_dx, player_dy, car_dx, car_dy = process_player_input(
|
|
275
|
-
keys, player_ref, car_ref
|
|
330
|
+
keys, player_ref, car_ref, pad_input=pad_vector
|
|
276
331
|
)
|
|
277
332
|
update_entities(
|
|
278
333
|
game_data,
|
|
@@ -288,19 +343,12 @@ def gameplay_screen(
|
|
|
288
343
|
if accel_active:
|
|
289
344
|
step_ms = max(1, step_ms)
|
|
290
345
|
game_data.state.elapsed_play_ms += step_ms
|
|
291
|
-
|
|
346
|
+
update_endurance_timer(game_data, step_ms)
|
|
292
347
|
cleanup_survivor_messages(game_data.state)
|
|
293
|
-
|
|
294
|
-
if sub_fov_target:
|
|
295
|
-
frame_fov_target = sub_fov_target
|
|
348
|
+
check_interactions(game_data, config)
|
|
296
349
|
if game_data.state.game_over or game_data.state.game_won:
|
|
297
350
|
break
|
|
298
351
|
|
|
299
|
-
if frame_fov_target:
|
|
300
|
-
last_fov_target = frame_fov_target
|
|
301
|
-
else:
|
|
302
|
-
frame_fov_target = last_fov_target
|
|
303
|
-
|
|
304
352
|
player = game_data.player
|
|
305
353
|
if player is None:
|
|
306
354
|
raise ValueError("Player missing from game data")
|
|
@@ -309,7 +357,7 @@ def gameplay_screen(
|
|
|
309
357
|
hint_delay = car_hint_conf.get("delay_ms", CAR_HINT_DELAY_MS_DEFAULT)
|
|
310
358
|
elapsed_ms = game_data.state.elapsed_play_ms
|
|
311
359
|
has_fuel = game_data.state.has_fuel
|
|
312
|
-
hint_enabled = car_hint_conf.get("enabled", True) and not stage.
|
|
360
|
+
hint_enabled = car_hint_conf.get("enabled", True) and not stage.endurance_stage
|
|
313
361
|
hint_target = None
|
|
314
362
|
hint_color = YELLOW
|
|
315
363
|
hint_expires_at = game_data.state.hint_expires_at
|
|
@@ -356,7 +404,6 @@ def gameplay_screen(
|
|
|
356
404
|
render_assets,
|
|
357
405
|
screen,
|
|
358
406
|
game_data,
|
|
359
|
-
frame_fov_target,
|
|
360
407
|
config=config,
|
|
361
408
|
hint_target=hint_target,
|
|
362
409
|
hint_color=hint_color,
|
|
@@ -10,6 +10,19 @@ from pygame import surface, time
|
|
|
10
10
|
from ..colors import BLACK, GREEN, LIGHT_GRAY, WHITE
|
|
11
11
|
from ..config import DEFAULT_CONFIG, save_config
|
|
12
12
|
from ..font_utils import load_font
|
|
13
|
+
from ..input_utils import (
|
|
14
|
+
CONTROLLER_BUTTON_DOWN,
|
|
15
|
+
CONTROLLER_BUTTON_DPAD_DOWN,
|
|
16
|
+
CONTROLLER_BUTTON_DPAD_LEFT,
|
|
17
|
+
CONTROLLER_BUTTON_DPAD_RIGHT,
|
|
18
|
+
CONTROLLER_BUTTON_DPAD_UP,
|
|
19
|
+
CONTROLLER_DEVICE_ADDED,
|
|
20
|
+
CONTROLLER_DEVICE_REMOVED,
|
|
21
|
+
init_first_controller,
|
|
22
|
+
init_first_joystick,
|
|
23
|
+
is_confirm_event,
|
|
24
|
+
is_select_event,
|
|
25
|
+
)
|
|
13
26
|
from ..localization import (
|
|
14
27
|
get_font_settings,
|
|
15
28
|
get_language,
|
|
@@ -95,6 +108,8 @@ def settings_screen(
|
|
|
95
108
|
selected = 0
|
|
96
109
|
languages = language_options()
|
|
97
110
|
language_codes = [lang.code for lang in languages]
|
|
111
|
+
controller = init_first_controller()
|
|
112
|
+
joystick = init_first_joystick() if controller is None else None
|
|
98
113
|
|
|
99
114
|
def _ensure_parent(path: tuple[str, ...]) -> tuple[dict, str]:
|
|
100
115
|
node = working
|
|
@@ -143,6 +158,15 @@ def settings_screen(
|
|
|
143
158
|
|
|
144
159
|
def build_sections() -> list[dict]:
|
|
145
160
|
return [
|
|
161
|
+
{
|
|
162
|
+
"label": tr("settings.sections.menu"),
|
|
163
|
+
"rows": [
|
|
164
|
+
{
|
|
165
|
+
"type": "action",
|
|
166
|
+
"label": tr("settings.rows.return_to_title"),
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
},
|
|
146
170
|
{
|
|
147
171
|
"label": tr("settings.sections.localization"),
|
|
148
172
|
"rows": [
|
|
@@ -210,6 +234,10 @@ def settings_screen(
|
|
|
210
234
|
row_count = len(rows)
|
|
211
235
|
last_language = get_language()
|
|
212
236
|
|
|
237
|
+
def _exit_settings() -> dict[str, Any]:
|
|
238
|
+
save_config(working, config_path)
|
|
239
|
+
return working
|
|
240
|
+
|
|
213
241
|
while True:
|
|
214
242
|
for event in pygame.event.get():
|
|
215
243
|
if event.type == pygame.QUIT:
|
|
@@ -217,6 +245,24 @@ def settings_screen(
|
|
|
217
245
|
if event.type in (pygame.WINDOWSIZECHANGED, pygame.VIDEORESIZE):
|
|
218
246
|
sync_window_size(event)
|
|
219
247
|
continue
|
|
248
|
+
if event.type == pygame.JOYDEVICEADDED or (
|
|
249
|
+
CONTROLLER_DEVICE_ADDED is not None
|
|
250
|
+
and event.type == CONTROLLER_DEVICE_ADDED
|
|
251
|
+
):
|
|
252
|
+
if controller is None:
|
|
253
|
+
controller = init_first_controller()
|
|
254
|
+
if controller is None:
|
|
255
|
+
joystick = init_first_joystick()
|
|
256
|
+
if event.type == pygame.JOYDEVICEREMOVED or (
|
|
257
|
+
CONTROLLER_DEVICE_REMOVED is not None
|
|
258
|
+
and event.type == CONTROLLER_DEVICE_REMOVED
|
|
259
|
+
):
|
|
260
|
+
if controller and not controller.get_init():
|
|
261
|
+
controller = None
|
|
262
|
+
if joystick and not joystick.get_init():
|
|
263
|
+
joystick = None
|
|
264
|
+
if is_select_event(event):
|
|
265
|
+
return _exit_settings()
|
|
220
266
|
if event.type == pygame.KEYDOWN:
|
|
221
267
|
if event.key == pygame.K_LEFTBRACKET:
|
|
222
268
|
nudge_window_scale(0.5)
|
|
@@ -228,8 +274,7 @@ def settings_screen(
|
|
|
228
274
|
toggle_fullscreen()
|
|
229
275
|
continue
|
|
230
276
|
if event.key in (pygame.K_ESCAPE, pygame.K_BACKSPACE):
|
|
231
|
-
|
|
232
|
-
return working
|
|
277
|
+
return _exit_settings()
|
|
233
278
|
if event.key in (pygame.K_UP, pygame.K_w):
|
|
234
279
|
selected = (selected - 1) % row_count
|
|
235
280
|
if event.key in (pygame.K_DOWN, pygame.K_s):
|
|
@@ -237,16 +282,18 @@ def settings_screen(
|
|
|
237
282
|
current_row = rows[selected]
|
|
238
283
|
row_type = current_row.get("type", "toggle")
|
|
239
284
|
if event.key in (pygame.K_SPACE, pygame.K_RETURN):
|
|
285
|
+
if row_type == "action":
|
|
286
|
+
return _exit_settings()
|
|
240
287
|
if row_type == "toggle":
|
|
241
288
|
toggle_row(current_row)
|
|
242
289
|
elif row_type == "choice":
|
|
243
290
|
cycle_choice(current_row, 1)
|
|
244
|
-
if event.key == pygame.K_LEFT:
|
|
291
|
+
if event.key == pygame.K_LEFT and row_type != "action":
|
|
245
292
|
if row_type == "toggle":
|
|
246
293
|
set_easy_value(current_row, True)
|
|
247
294
|
elif row_type == "choice":
|
|
248
295
|
cycle_choice(current_row, -1)
|
|
249
|
-
if event.key == pygame.K_RIGHT:
|
|
296
|
+
if event.key == pygame.K_RIGHT and row_type != "action":
|
|
250
297
|
if row_type == "toggle":
|
|
251
298
|
set_easy_value(current_row, False)
|
|
252
299
|
elif row_type == "choice":
|
|
@@ -254,6 +301,66 @@ def settings_screen(
|
|
|
254
301
|
if event.key == pygame.K_r:
|
|
255
302
|
working = copy.deepcopy(DEFAULT_CONFIG)
|
|
256
303
|
set_language(working.get("language"))
|
|
304
|
+
if event.type == pygame.JOYBUTTONDOWN or (
|
|
305
|
+
CONTROLLER_BUTTON_DOWN is not None
|
|
306
|
+
and event.type == CONTROLLER_BUTTON_DOWN
|
|
307
|
+
):
|
|
308
|
+
current_row = rows[selected]
|
|
309
|
+
row_type = current_row.get("type", "toggle")
|
|
310
|
+
if is_confirm_event(event):
|
|
311
|
+
if row_type == "action":
|
|
312
|
+
return _exit_settings()
|
|
313
|
+
if row_type == "toggle":
|
|
314
|
+
toggle_row(current_row)
|
|
315
|
+
elif row_type == "choice":
|
|
316
|
+
cycle_choice(current_row, 1)
|
|
317
|
+
if CONTROLLER_BUTTON_DOWN is not None and event.type == CONTROLLER_BUTTON_DOWN:
|
|
318
|
+
if (
|
|
319
|
+
CONTROLLER_BUTTON_DPAD_UP is not None
|
|
320
|
+
and event.button == CONTROLLER_BUTTON_DPAD_UP
|
|
321
|
+
):
|
|
322
|
+
selected = (selected - 1) % row_count
|
|
323
|
+
if (
|
|
324
|
+
CONTROLLER_BUTTON_DPAD_DOWN is not None
|
|
325
|
+
and event.button == CONTROLLER_BUTTON_DPAD_DOWN
|
|
326
|
+
):
|
|
327
|
+
selected = (selected + 1) % row_count
|
|
328
|
+
if (
|
|
329
|
+
CONTROLLER_BUTTON_DPAD_LEFT is not None
|
|
330
|
+
and event.button == CONTROLLER_BUTTON_DPAD_LEFT
|
|
331
|
+
and row_type != "action"
|
|
332
|
+
):
|
|
333
|
+
if row_type == "toggle":
|
|
334
|
+
set_easy_value(current_row, True)
|
|
335
|
+
elif row_type == "choice":
|
|
336
|
+
cycle_choice(current_row, -1)
|
|
337
|
+
if (
|
|
338
|
+
CONTROLLER_BUTTON_DPAD_RIGHT is not None
|
|
339
|
+
and event.button == CONTROLLER_BUTTON_DPAD_RIGHT
|
|
340
|
+
and row_type != "action"
|
|
341
|
+
):
|
|
342
|
+
if row_type == "toggle":
|
|
343
|
+
set_easy_value(current_row, False)
|
|
344
|
+
elif row_type == "choice":
|
|
345
|
+
cycle_choice(current_row, 1)
|
|
346
|
+
if event.type == pygame.JOYHATMOTION:
|
|
347
|
+
current_row = rows[selected]
|
|
348
|
+
row_type = current_row.get("type", "toggle")
|
|
349
|
+
hat_x, hat_y = event.value
|
|
350
|
+
if hat_y == 1:
|
|
351
|
+
selected = (selected - 1) % row_count
|
|
352
|
+
elif hat_y == -1:
|
|
353
|
+
selected = (selected + 1) % row_count
|
|
354
|
+
if hat_x == -1 and row_type != "action":
|
|
355
|
+
if row_type == "toggle":
|
|
356
|
+
set_easy_value(current_row, True)
|
|
357
|
+
elif row_type == "choice":
|
|
358
|
+
cycle_choice(current_row, -1)
|
|
359
|
+
elif hat_x == 1 and row_type != "action":
|
|
360
|
+
if row_type == "toggle":
|
|
361
|
+
set_easy_value(current_row, False)
|
|
362
|
+
elif row_type == "choice":
|
|
363
|
+
cycle_choice(current_row, 1)
|
|
257
364
|
|
|
258
365
|
current_language = get_language()
|
|
259
366
|
if current_language != last_language:
|
|
@@ -284,8 +391,8 @@ def settings_screen(
|
|
|
284
391
|
)
|
|
285
392
|
highlight_color = (70, 70, 70)
|
|
286
393
|
|
|
287
|
-
row_height =
|
|
288
|
-
start_y =
|
|
394
|
+
row_height = 20
|
|
395
|
+
start_y = 46
|
|
289
396
|
|
|
290
397
|
segment_width = 30
|
|
291
398
|
segment_height = 18
|
|
@@ -294,7 +401,7 @@ def settings_screen(
|
|
|
294
401
|
|
|
295
402
|
column_margin = 24
|
|
296
403
|
column_width = screen_width // 2 - column_margin * 2
|
|
297
|
-
section_spacing =
|
|
404
|
+
section_spacing = 4
|
|
298
405
|
row_indent = 12
|
|
299
406
|
value_padding = 20
|
|
300
407
|
|
|
@@ -305,7 +412,7 @@ def settings_screen(
|
|
|
305
412
|
section["label"], False, LIGHT_GRAY
|
|
306
413
|
)
|
|
307
414
|
section_states[section["label"]] = {
|
|
308
|
-
"next_y": y_cursor + header_surface.get_height() +
|
|
415
|
+
"next_y": y_cursor + header_surface.get_height() + 4,
|
|
309
416
|
"header_surface": header_surface,
|
|
310
417
|
"header_pos": (column_margin, y_cursor),
|
|
311
418
|
}
|
|
@@ -324,9 +431,13 @@ def settings_screen(
|
|
|
324
431
|
state = section_states[section_label]
|
|
325
432
|
col_x = column_margin + row_indent
|
|
326
433
|
row_width = column_width - row_indent + value_padding
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
434
|
+
row_type = row.get("type", "toggle")
|
|
435
|
+
value = None
|
|
436
|
+
if row_type != "action":
|
|
437
|
+
value = _get_value(
|
|
438
|
+
row["path"],
|
|
439
|
+
row.get("easy_value", row.get("choices", [None])[0]),
|
|
440
|
+
)
|
|
330
441
|
row_y_current = state["next_y"]
|
|
331
442
|
state["next_y"] += row_height
|
|
332
443
|
|
|
@@ -344,7 +455,7 @@ def settings_screen(
|
|
|
344
455
|
)
|
|
345
456
|
)
|
|
346
457
|
screen.blit(label_surface, label_rect)
|
|
347
|
-
if
|
|
458
|
+
if row_type == "choice":
|
|
348
459
|
display_fn = row.get("get_display")
|
|
349
460
|
display_text = (
|
|
350
461
|
display_fn(value)
|
|
@@ -359,7 +470,7 @@ def settings_screen(
|
|
|
359
470
|
)
|
|
360
471
|
)
|
|
361
472
|
screen.blit(value_surface, value_rect)
|
|
362
|
-
|
|
473
|
+
elif row_type == "toggle":
|
|
363
474
|
slider_y = row_y_current + (row_height - segment_height) // 2 - 2
|
|
364
475
|
slider_x = col_x + row_width - segment_total_width
|
|
365
476
|
left_rect = pygame.Rect(
|