zombie-escape 1.7.1__py3-none-any.whl → 1.8.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.
@@ -31,6 +31,17 @@ from ..gameplay import (
31
31
  update_footprints,
32
32
  update_survival_timer,
33
33
  )
34
+ from ..input_utils import (
35
+ CONTROLLER_BUTTON_DOWN,
36
+ CONTROLLER_DEVICE_ADDED,
37
+ CONTROLLER_DEVICE_REMOVED,
38
+ init_first_controller,
39
+ init_first_joystick,
40
+ is_accel_active,
41
+ is_select_event,
42
+ is_start_event,
43
+ read_gamepad_move,
44
+ )
34
45
  from ..gameplay.spawn import _alive_waiting_cars
35
46
  from ..entities import build_wall_index
36
47
  from ..localization import translate as tr
@@ -89,6 +100,8 @@ def gameplay_screen(
89
100
  paused_focus = False
90
101
  ignore_focus_loss_until = 0
91
102
  last_fov_target = None
103
+ controller = init_first_controller()
104
+ joystick = init_first_joystick() if controller is None else None
92
105
 
93
106
  layout_data = generate_level_from_blueprint(game_data, config)
94
107
  sync_ambient_palette_with_flashlights(game_data, force=True)
@@ -180,7 +193,22 @@ def gameplay_screen(
180
193
  paused_focus = False
181
194
  if event.type == pygame.MOUSEBUTTONDOWN:
182
195
  paused_focus = False
183
- paused_manual = False
196
+ if event.type == pygame.JOYDEVICEADDED or (
197
+ CONTROLLER_DEVICE_ADDED is not None
198
+ and event.type == CONTROLLER_DEVICE_ADDED
199
+ ):
200
+ if controller is None:
201
+ controller = init_first_controller()
202
+ if controller is None:
203
+ joystick = init_first_joystick()
204
+ if event.type == pygame.JOYDEVICEREMOVED or (
205
+ CONTROLLER_DEVICE_REMOVED is not None
206
+ and event.type == CONTROLLER_DEVICE_REMOVED
207
+ ):
208
+ if controller and not controller.get_init():
209
+ controller = None
210
+ if joystick and not joystick.get_init():
211
+ joystick = None
184
212
  if event.type == pygame.KEYDOWN:
185
213
  if event.key == pygame.K_s and (
186
214
  pygame.key.get_mods() & pygame.KMOD_CTRL
@@ -192,10 +220,40 @@ def gameplay_screen(
192
220
  }
193
221
  print("STATE DEBUG:", state_snapshot)
194
222
  continue
195
- if event.key == pygame.K_ESCAPE:
196
- return ScreenTransition(ScreenID.TITLE)
197
- if event.key == pygame.K_p:
198
- paused_manual = not paused_manual
223
+ if debug_mode:
224
+ if event.key == pygame.K_ESCAPE:
225
+ return ScreenTransition(ScreenID.TITLE)
226
+ if event.key == pygame.K_p:
227
+ paused_manual = not paused_manual
228
+ continue
229
+ if paused_manual:
230
+ if event.key == pygame.K_ESCAPE:
231
+ return ScreenTransition(ScreenID.TITLE)
232
+ if event.key == pygame.K_p:
233
+ paused_manual = False
234
+ continue
235
+ if event.key in (pygame.K_ESCAPE, pygame.K_p):
236
+ paused_manual = True
237
+ continue
238
+ if event.type == pygame.JOYBUTTONDOWN or (
239
+ CONTROLLER_BUTTON_DOWN is not None
240
+ and event.type == CONTROLLER_BUTTON_DOWN
241
+ ):
242
+ if debug_mode:
243
+ if is_select_event(event):
244
+ return ScreenTransition(ScreenID.TITLE)
245
+ if is_start_event(event):
246
+ paused_manual = not paused_manual
247
+ continue
248
+ if paused_manual:
249
+ if is_select_event(event):
250
+ return ScreenTransition(ScreenID.TITLE)
251
+ if is_start_event(event):
252
+ paused_manual = False
253
+ continue
254
+ if is_select_event(event) or is_start_event(event):
255
+ paused_manual = True
256
+ continue
199
257
 
200
258
  paused = paused_manual or paused_focus
201
259
  if paused:
@@ -211,17 +269,19 @@ def gameplay_screen(
211
269
  if show_pause_overlay:
212
270
  overlay = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA)
213
271
  overlay.fill((0, 0, 0, 150))
272
+ pause_radius = 53
273
+ cx = screen_width // 2
274
+ cy = screen_height // 2 - 18
214
275
  pygame.draw.circle(
215
276
  overlay,
216
277
  LIGHT_GRAY,
217
- (screen_width // 2, screen_height // 2),
218
- 35,
278
+ (cx, cy),
279
+ pause_radius,
219
280
  width=3,
220
281
  )
221
- bar_width = 8
222
- bar_height = 30
223
- gap = 9
224
- cx, cy = screen_width // 2, screen_height // 2
282
+ bar_width = 10
283
+ bar_height = 38
284
+ gap = 12
225
285
  pygame.draw.rect(
226
286
  overlay,
227
287
  LIGHT_GRAY,
@@ -238,12 +298,12 @@ def gameplay_screen(
238
298
  tr("hud.paused"),
239
299
  18,
240
300
  WHITE,
241
- (screen_width // 2, screen_height // 2 + 24),
301
+ (screen_width // 2, 28),
242
302
  )
243
303
  show_message(
244
304
  screen,
245
- tr("hud.resume_hint"),
246
- 18,
305
+ tr("hud.pause_hint"),
306
+ 16,
247
307
  LIGHT_GRAY,
248
308
  (screen_width // 2, screen_height // 2 + 70),
249
309
  )
@@ -254,8 +314,8 @@ def gameplay_screen(
254
314
  accel_allowed = not (
255
315
  game_data.state.game_over or game_data.state.game_won
256
316
  )
257
- accel_active = accel_allowed and (
258
- keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]
317
+ accel_active = accel_allowed and is_accel_active(
318
+ keys, controller, joystick
259
319
  )
260
320
  game_data.state.time_accel_active = accel_active
261
321
  substeps = SURVIVAL_TIME_ACCEL_SUBSTEPS if accel_active else 1
@@ -271,8 +331,9 @@ def gameplay_screen(
271
331
  if player_ref is None:
272
332
  break
273
333
  car_ref = game_data.car
334
+ pad_vector = read_gamepad_move(controller, joystick)
274
335
  player_dx, player_dy, car_dx, car_dy = process_player_input(
275
- keys, player_ref, car_ref
336
+ keys, player_ref, car_ref, pad_input=pad_vector
276
337
  )
277
338
  update_entities(
278
339
  game_data,
@@ -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
- save_config(working, config_path)
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 = 22
288
- start_y = 52
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 = 6
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() + 6,
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
- value = _get_value(
328
- row["path"], row.get("easy_value", row.get("choices", [None])[0])
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 row.get("type", "toggle") == "choice":
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
- else:
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(
@@ -14,6 +14,18 @@ from ..models import Stage
14
14
  from ..progress import load_progress
15
15
  from ..render import show_message
16
16
  from ..rng import generate_seed
17
+ from ..input_utils import (
18
+ CONTROLLER_BUTTON_DOWN,
19
+ CONTROLLER_BUTTON_DPAD_DOWN,
20
+ CONTROLLER_BUTTON_DPAD_LEFT,
21
+ CONTROLLER_BUTTON_DPAD_RIGHT,
22
+ CONTROLLER_BUTTON_DPAD_UP,
23
+ CONTROLLER_DEVICE_ADDED,
24
+ CONTROLLER_DEVICE_REMOVED,
25
+ init_first_controller,
26
+ init_first_joystick,
27
+ is_confirm_event,
28
+ )
17
29
  from ..screens import (
18
30
  ScreenID,
19
31
  ScreenTransition,
@@ -193,6 +205,8 @@ def title_screen(
193
205
  0,
194
206
  )
195
207
  selected = min(selected_stage_index, len(options) - 1)
208
+ controller = init_first_controller()
209
+ joystick = init_first_joystick() if controller is None else None
196
210
 
197
211
  while True:
198
212
  for event in pygame.event.get():
@@ -205,6 +219,22 @@ def title_screen(
205
219
  if event.type in (pygame.WINDOWSIZECHANGED, pygame.VIDEORESIZE):
206
220
  sync_window_size(event)
207
221
  continue
222
+ if event.type == pygame.JOYDEVICEADDED or (
223
+ CONTROLLER_DEVICE_ADDED is not None
224
+ and event.type == CONTROLLER_DEVICE_ADDED
225
+ ):
226
+ if controller is None:
227
+ controller = init_first_controller()
228
+ if controller is None:
229
+ joystick = init_first_joystick()
230
+ if event.type == pygame.JOYDEVICEREMOVED or (
231
+ CONTROLLER_DEVICE_REMOVED is not None
232
+ and event.type == CONTROLLER_DEVICE_REMOVED
233
+ ):
234
+ if controller and not controller.get_init():
235
+ controller = None
236
+ if joystick and not joystick.get_init():
237
+ joystick = None
208
238
  if event.type == pygame.KEYDOWN:
209
239
  if event.key == pygame.K_BACKSPACE:
210
240
  current_seed_text = _generate_auto_seed_text()
@@ -273,6 +303,87 @@ def title_screen(
273
303
  seed_text=current_seed_text,
274
304
  seed_is_auto=current_seed_auto,
275
305
  )
306
+ if event.type == pygame.JOYBUTTONDOWN or (
307
+ CONTROLLER_BUTTON_DOWN is not None
308
+ and event.type == CONTROLLER_BUTTON_DOWN
309
+ ):
310
+ if is_confirm_event(event):
311
+ current = options[selected]
312
+ if current["type"] == "stage" and current.get("available"):
313
+ seed_value = (
314
+ int(current_seed_text) if current_seed_text else None
315
+ )
316
+ return ScreenTransition(
317
+ ScreenID.GAMEPLAY,
318
+ stage=current["stage"],
319
+ seed=seed_value,
320
+ seed_text=current_seed_text,
321
+ seed_is_auto=current_seed_auto,
322
+ )
323
+ if current["type"] == "settings":
324
+ return ScreenTransition(
325
+ ScreenID.SETTINGS,
326
+ seed_text=current_seed_text,
327
+ seed_is_auto=current_seed_auto,
328
+ )
329
+ if current["type"] == "readme":
330
+ _open_readme_link()
331
+ continue
332
+ if current["type"] == "quit":
333
+ return ScreenTransition(
334
+ ScreenID.EXIT,
335
+ seed_text=current_seed_text,
336
+ seed_is_auto=current_seed_auto,
337
+ )
338
+ if CONTROLLER_BUTTON_DOWN is not None and event.type == CONTROLLER_BUTTON_DOWN:
339
+ if (
340
+ CONTROLLER_BUTTON_DPAD_UP is not None
341
+ and event.button == CONTROLLER_BUTTON_DPAD_UP
342
+ ):
343
+ selected = (selected - 1) % len(options)
344
+ if (
345
+ CONTROLLER_BUTTON_DPAD_DOWN is not None
346
+ and event.button == CONTROLLER_BUTTON_DPAD_DOWN
347
+ ):
348
+ selected = (selected + 1) % len(options)
349
+ if (
350
+ CONTROLLER_BUTTON_DPAD_LEFT is not None
351
+ and event.button == CONTROLLER_BUTTON_DPAD_LEFT
352
+ ):
353
+ if current_page > 0:
354
+ current_page -= 1
355
+ options, stage_options = _build_options(current_page)
356
+ selected = 0
357
+ if (
358
+ CONTROLLER_BUTTON_DPAD_RIGHT is not None
359
+ and event.button == CONTROLLER_BUTTON_DPAD_RIGHT
360
+ ):
361
+ if (
362
+ current_page < len(stage_pages) - 1
363
+ and _page_available(current_page + 1)
364
+ ):
365
+ current_page += 1
366
+ options, stage_options = _build_options(current_page)
367
+ selected = 0
368
+ if event.type == pygame.JOYHATMOTION:
369
+ hat_x, hat_y = event.value
370
+ if hat_y == 1:
371
+ selected = (selected - 1) % len(options)
372
+ elif hat_y == -1:
373
+ selected = (selected + 1) % len(options)
374
+ if hat_x == -1:
375
+ if current_page > 0:
376
+ current_page -= 1
377
+ options, stage_options = _build_options(current_page)
378
+ selected = 0
379
+ elif hat_x == 1:
380
+ if (
381
+ current_page < len(stage_pages) - 1
382
+ and _page_available(current_page + 1)
383
+ ):
384
+ current_page += 1
385
+ options, stage_options = _build_options(current_page)
386
+ selected = 0
276
387
 
277
388
  screen.fill(BLACK)
278
389
  title_text = tr("game.title")
@@ -67,6 +67,7 @@ STAGES: list[Stage] = [
67
67
  zombie_normal_ratio=0.4,
68
68
  zombie_tracker_ratio=0.6,
69
69
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
70
+ initial_interior_spawn_rate=0.01,
70
71
  ),
71
72
  Stage(
72
73
  id="stage7",
@@ -82,6 +83,7 @@ STAGES: list[Stage] = [
82
83
  zombie_tracker_ratio=0.3,
83
84
  zombie_wall_follower_ratio=0.3,
84
85
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
86
+ initial_interior_spawn_rate=0.01,
85
87
  ),
86
88
  Stage(
87
89
  id="stage8",
@@ -97,20 +99,43 @@ STAGES: list[Stage] = [
97
99
  zombie_tracker_ratio=0.3,
98
100
  zombie_wall_follower_ratio=0.7,
99
101
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
102
+ initial_interior_spawn_rate=0.01,
100
103
  ),
101
104
  Stage(
102
105
  id="stage9",
103
106
  name_key="stages.stage9.name",
104
107
  description_key="stages.stage9.description",
105
- available=False,
108
+ available=True,
106
109
  rescue_stage=True,
107
110
  tile_size=35,
108
111
  requires_fuel=True,
109
112
  exterior_spawn_weight=0.4,
110
113
  interior_spawn_weight=0.6,
111
114
  waiting_car_target_count=1,
115
+ zombie_normal_ratio=0,
116
+ zombie_tracker_ratio=0.3,
117
+ zombie_wall_follower_ratio=0.7,
112
118
  survivor_spawn_rate=SURVIVOR_SPAWN_RATE,
113
119
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
120
+ initial_interior_spawn_rate=0.01,
121
+ ),
122
+ Stage(
123
+ id="stage10",
124
+ name_key="stages.stage10.name",
125
+ description_key="stages.stage10.description",
126
+ available=True,
127
+ rescue_stage=True,
128
+ tile_size=40,
129
+ wall_algorithm="sparse",
130
+ exterior_spawn_weight=0.7,
131
+ interior_spawn_weight=0.3,
132
+ zombie_normal_ratio=0.4,
133
+ zombie_tracker_ratio=0.4,
134
+ zombie_wall_follower_ratio=0.2,
135
+ zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
136
+ initial_interior_spawn_rate=0.02,
137
+ waiting_car_target_count=1,
138
+ survivor_spawn_rate=0.35,
114
139
  ),
115
140
  ]
116
141
  DEFAULT_STAGE_ID = "stage1"
@@ -83,6 +83,9 @@ def main() -> None:
83
83
  sys.argv = [sys.argv[0]] + remaining
84
84
 
85
85
  pygame.init()
86
+ pygame.joystick.init()
87
+ if hasattr(pygame, "controller"):
88
+ pygame.controller.init()
86
89
  try:
87
90
  pygame.font.init()
88
91
  except pygame.error as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zombie-escape
3
- Version: 1.7.1
3
+ Version: 1.8.0
4
4
  Summary: Top-down zombie survival game built with pygame.
5
5
  Project-URL: Homepage, https://github.com/tos-kamiya/zombie-escape
6
6
  Author-email: Toshihiro Kamiya <kamiya@mbj.nifty.com>
@@ -46,10 +46,11 @@ This game is a simple 2D top-down action game where the player aims to escape by
46
46
 
47
47
  - **Player/Car Movement:** `W` / `↑` (Up), `A` / `←` (Left), `S` / `↓` (Down), `D` / `→` (Right)
48
48
  - **Enter Car:** Overlap the player with the car.
49
- - **Quit Game:** `ESC` key
49
+ - **Pause:** `P`/Start or `ESC`/Select
50
+ - **Quit Game:** `ESC`/Select (from pause)
50
51
  - **Restart:** `R` key (on Game Over/Clear screen)
51
52
  - **Window/Fullscreen (title/settings only):** `[` to shrink, `]` to enlarge, `F` to toggle fullscreen
52
- - **Time Acceleration:** Hold either `Shift` key to run the entire world 4x faster; release to return to normal speed.
53
+ - **Time Acceleration:** Hold either `Shift` key or `R1` to run the entire world 4x faster; release to return to normal speed.
53
54
 
54
55
  ## Title Screen
55
56
 
@@ -0,0 +1,46 @@
1
+ zombie_escape/__about__.py,sha256=LlsY4VV4kb_yglExHx0yDj65RPKm5mVRb6gDT_MXY-U,134
2
+ zombie_escape/__init__.py,sha256=YSQnUghet8jxSvaGmKfzHfXXLlnvWh_xk10WGTDO2HM,173
3
+ zombie_escape/colors.py,sha256=Cmu3wwSQgMqu75bTtsD3G85daQd96zNErnvGfXi3iVM,5771
4
+ zombie_escape/config.py,sha256=Ncvsz6HzBknSjecorkm7CrkrzWUIksD30ykLPueanyw,2008
5
+ zombie_escape/entities.py,sha256=nQCy0NXaGBTvwajoBzJvKE3WpP3C_a9ClXGmhzF9fyU,55604
6
+ zombie_escape/entities_constants.py,sha256=WDQslNovvstSSavZCT0vrD4PESn0rVdKc1L4BqxLY0g,2851
7
+ zombie_escape/font_utils.py,sha256=kkjcSlCTG3jO5zf5XUnirpJ-iL_Eg8ahzjZYGijF2JY,1206
8
+ zombie_escape/gameplay_constants.py,sha256=I5g2xsd4Rck4d5tbWae2bm6Yfwp4ZAgujDLnDMsHxgM,758
9
+ zombie_escape/input_utils.py,sha256=0SHENZi5y-ybSxUX569RHihI_xbQWSI0FQ1q1ZE9U1c,5795
10
+ zombie_escape/level_blueprints.py,sha256=VZieyhG4kWd1l6u7_Owy2FxhYt63pWpvsmLx5yB8GOs,10609
11
+ zombie_escape/level_constants.py,sha256=fSrPXfkuKHlv9XqmaRq6aR9UhjpqZK2iJJgMc-TXGXc,281
12
+ zombie_escape/localization.py,sha256=gp26FN_Od4eOeIK2aY0_QZ-9THw6yENh-cGTwglnMxw,6118
13
+ zombie_escape/models.py,sha256=r8EvhF800Cpk9_87B1ar6RIQeECDB9m7AKvIZRspZ4Y,3757
14
+ zombie_escape/progress.py,sha256=WCFc7JeMY6noBjnTIFyHrXQJSM1j8PwyPA7S8ZQwjTE,1713
15
+ zombie_escape/render.py,sha256=VHiz4woL-qWbeNPX0q7ZPZnaPB5BPqIBzL7AKxx7Us0,30862
16
+ zombie_escape/render_assets.py,sha256=66lRKQZ9fII6930WeQchIJRFeLLu7J3Cr155svpDrUM,16489
17
+ zombie_escape/render_constants.py,sha256=yJ26KA7DF6ndYG3kAFaDkL-ljN1MgWbSEhR6nmWzzWI,2315
18
+ zombie_escape/rng.py,sha256=gMAgpzYoNN1FxRG3aQ9fdXTDNAg48Rqz8YnB1nJ4Fpw,3787
19
+ zombie_escape/screen_constants.py,sha256=MJaTlSWfN4VtN6pMqPQ6LF34XdJm0wqYLuRwa1pQuAU,559
20
+ zombie_escape/stage_constants.py,sha256=0SE8EfzQL6qMQUFs3cygAronkHaNiAK2zSoxKJ8OPYA,4455
21
+ zombie_escape/zombie_escape.py,sha256=lyW-ZF2CTAO7s2yToQlGDGwBxW1WEI6qeOV-ylOxQU4,8939
22
+ zombie_escape/assets/fonts/Silkscreen-Regular.ttf,sha256=SVZ0CGAICeJRR-kiWsTzf0EOLfRadQaWxFAnUx-2Xxs,31960
23
+ zombie_escape/assets/fonts/misaki_gothic.ttf,sha256=CWPhHonV-kCaegSKUujqLWI9dkp5mEiPikKRERYRxOE,1171204
24
+ zombie_escape/gameplay/__init__.py,sha256=hv-37H7R7cQrK0Qr7FkcakwYME26pi31LC4r25s2Dxk,2349
25
+ zombie_escape/gameplay/ambient.py,sha256=hoCOz6ciyejU0nmJwdLqmVfaoo-01CrVSMRLpFMz93w,1446
26
+ zombie_escape/gameplay/constants.py,sha256=ZEBCiuDmGvaky8U34u1nD5rt0Ph2kDMIaYgNQBsLZDw,1009
27
+ zombie_escape/gameplay/footprints.py,sha256=LQ3wZNovzgO54kaGncZT4yrhnmoZVfROkTQ6wL_TVSY,1781
28
+ zombie_escape/gameplay/interactions.py,sha256=QFtjv-5t2G6ek4AC3y5XWl15XQl5q9oK80ODNvd_Zt8,13476
29
+ zombie_escape/gameplay/layout.py,sha256=8hQwELQ35-y-SkcIGuDsmNJEuvbPRhWIZbu1LSxoClI,6868
30
+ zombie_escape/gameplay/movement.py,sha256=Vi1Q6NTvn3HYPyBy_JEUl-nXGPULiamjDZT9seTPymo,9086
31
+ zombie_escape/gameplay/spawn.py,sha256=IH_r5VIq0_7Us1W6WX2zgOUIpRm62i1dXf8paXz8nr8,20079
32
+ zombie_escape/gameplay/state.py,sha256=UngfxpPxUppZKLeuynEuxJBFCH0Qv9mxFoFjq9789c8,4549
33
+ zombie_escape/gameplay/survivors.py,sha256=JRjnlrcuXhHlTaWm-3npnatlLAtoMWIS84-ZJMjLRCM,11920
34
+ zombie_escape/gameplay/utils.py,sha256=2IwsQ1Fv8qVUWMP9JZ77tCV1slEJrokkiXAvQi7u81k,4921
35
+ zombie_escape/locales/ui.en.json,sha256=T7w4CGzbMlVR3NAlGZM23vi3uDruUyBdNL3CJkPOtnw,5805
36
+ zombie_escape/locales/ui.ja.json,sha256=OzN4yz3LVWkuFtWe9U-VaO8T786w10kE2vD0hEZresI,6340
37
+ zombie_escape/screens/__init__.py,sha256=P5aDyqJ3ZXdTVi0AAsxK9scc0lPuydc-XHplNxpDbu0,8335
38
+ zombie_escape/screens/game_over.py,sha256=P33aqm-6lgGMs5kOlGOXMEgPEpJIh4IC1oUxKtRFiwc,6689
39
+ zombie_escape/screens/gameplay.py,sha256=XhIG9oC5iY4mS2Ul04aAuFxDDo21r-b7pbAYifv65zo,15652
40
+ zombie_escape/screens/settings.py,sha256=qgcnq8k-yeRbqweT9GsIefKWKvjOON_Gs2_SCZChY-8,21739
41
+ zombie_escape/screens/title.py,sha256=1LUEMTmFLmJNL0TKTbnP2oRGWb6zUV28d8sQQo8i3Po,23118
42
+ zombie_escape-1.8.0.dist-info/METADATA,sha256=WdW0kIC3rxFHzs5kIrA1GIvOu-wAqWuDUP3Fqa-2e6k,10181
43
+ zombie_escape-1.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
44
+ zombie_escape-1.8.0.dist-info/entry_points.txt,sha256=JprxC-vvkBJgsOp0WJnGBZRJ_ESjjmyS-nsPExeiLHU,49
45
+ zombie_escape-1.8.0.dist-info/licenses/LICENSE.txt,sha256=q-cJYG_K766eXSxQ7txWcWQ6nS2OF6c3HTVLesHbesU,1104
46
+ zombie_escape-1.8.0.dist-info/RECORD,,