zombie-escape 1.10.1__py3-none-any.whl → 1.12.3__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.
@@ -14,6 +14,7 @@ from .spawn import (
14
14
  place_flashlights,
15
15
  place_fuel_can,
16
16
  place_new_car,
17
+ place_shoes,
17
18
  setup_player_and_cars,
18
19
  spawn_exterior_zombie,
19
20
  spawn_initial_zombies,
@@ -46,6 +47,7 @@ __all__ = [
46
47
  "place_new_car",
47
48
  "place_fuel_can",
48
49
  "place_flashlights",
50
+ "place_shoes",
49
51
  "place_buddies",
50
52
  "find_interior_spawn_positions",
51
53
  "find_nearby_offscreen_spawn_position",
@@ -26,6 +26,10 @@ def get_shrunk_sprite(
26
26
 
27
27
  new_sprite = pygame.sprite.Sprite()
28
28
  new_sprite.rect = rect
29
+ if hasattr(sprite_obj, "radius"):
30
+ base_radius = getattr(sprite_obj, "radius", None)
31
+ if base_radius is not None:
32
+ new_sprite.radius = base_radius * min(scale_x, scale_y)
29
33
 
30
34
  return new_sprite
31
35
 
@@ -12,6 +12,8 @@ from ..entities_constants import (
12
12
  FUEL_CAN_HEIGHT,
13
13
  FUEL_CAN_WIDTH,
14
14
  HUMANOID_RADIUS,
15
+ SHOES_HEIGHT,
16
+ SHOES_WIDTH,
15
17
  SURVIVOR_APPROACH_RADIUS,
16
18
  SURVIVOR_MAX_SAFE_PASSENGERS,
17
19
  )
@@ -55,11 +57,13 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
55
57
  survivor_group = game_data.groups.survivor_group
56
58
  state = game_data.state
57
59
  walkable_cells = game_data.layout.walkable_cells
58
- outside_rects = game_data.layout.outside_rects
60
+ outside_cells = game_data.layout.outside_cells
59
61
  fuel = game_data.fuel
60
62
  flashlights = game_data.flashlights or []
63
+ shoes_list = game_data.shoes or []
61
64
  camera = game_data.camera
62
65
  stage = game_data.stage
66
+ cell_size = game_data.cell_size
63
67
  maintain_waiting_car_supply(game_data)
64
68
  active_car = car if car and car.alive() else None
65
69
  waiting_cars = game_data.waiting_cars
@@ -70,6 +74,18 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
70
74
  flashlight_interaction_radius = _interaction_radius(
71
75
  FLASHLIGHT_WIDTH, FLASHLIGHT_HEIGHT
72
76
  )
77
+ shoes_interaction_radius = _interaction_radius(SHOES_WIDTH, SHOES_HEIGHT)
78
+
79
+ def _rect_center_cell(rect: pygame.Rect) -> tuple[int, int] | None:
80
+ if cell_size <= 0:
81
+ return None
82
+ return (int(rect.centerx // cell_size), int(rect.centery // cell_size))
83
+
84
+ def _cell_center(cell: tuple[int, int]) -> tuple[int, int]:
85
+ return (
86
+ int((cell[0] * cell_size) + (cell_size / 2)),
87
+ int((cell[1] * cell_size) + (cell_size / 2)),
88
+ )
73
89
 
74
90
  def _player_near_point(point: tuple[float, float], radius: float) -> bool:
75
91
  dx = point[0] - player.x
@@ -118,6 +134,21 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
118
134
  print("Flashlight acquired!")
119
135
  break
120
136
 
137
+ for shoes in list(shoes_list):
138
+ if not shoes.alive():
139
+ continue
140
+ if _player_near_point(shoes.rect.center, shoes_interaction_radius):
141
+ state.shoes_count += 1
142
+ state.hint_expires_at = 0
143
+ state.hint_target_type = None
144
+ shoes.kill()
145
+ try:
146
+ shoes_list.remove(shoes)
147
+ except ValueError:
148
+ pass
149
+ print("Shoes acquired!")
150
+ break
151
+
121
152
  sync_ambient_palette_with_flashlights(game_data)
122
153
 
123
154
  buddies = [
@@ -170,7 +201,7 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
170
201
  else:
171
202
  if walkable_cells:
172
203
  new_cell = RNG.choice(walkable_cells)
173
- buddy.teleport(new_cell.center)
204
+ buddy.teleport(_cell_center(new_cell))
174
205
  else:
175
206
  buddy.teleport(
176
207
  (game_data.level_width // 2, game_data.level_height // 2)
@@ -322,8 +353,9 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
322
353
  stage.endurance_stage
323
354
  and state.dawn_ready
324
355
  and not player.in_car
325
- and outside_rects
326
- and any(outside.collidepoint(player.rect.center) for outside in outside_rects)
356
+ and outside_cells
357
+ and (player_cell := _rect_center_cell(player.rect)) is not None
358
+ and player_cell in outside_cells
327
359
  ):
328
360
  state.game_won = True
329
361
 
@@ -332,9 +364,8 @@ def check_interactions(game_data: GameData, config: dict[str, Any]) -> None:
332
364
  buddy_ready = True
333
365
  if stage.buddy_required_count > 0:
334
366
  buddy_ready = state.buddy_onboard >= stage.buddy_required_count
335
- if buddy_ready and any(
336
- outside.collidepoint(car.rect.center) for outside in outside_rects
337
- ):
367
+ car_cell = _rect_center_cell(car.rect)
368
+ if buddy_ready and car_cell is not None and car_cell in outside_cells:
338
369
  if stage.buddy_required_count > 0:
339
370
  state.buddy_rescued = min(
340
371
  stage.buddy_required_count, state.buddy_onboard
@@ -10,9 +10,12 @@ from ..entities_constants import INTERNAL_WALL_HEALTH, STEEL_BEAM_HEALTH
10
10
  from .constants import OUTER_WALL_HEALTH
11
11
  from ..level_blueprints import choose_blueprint
12
12
  from ..models import GameData
13
+ from ..rng import get_rng
13
14
 
14
15
  __all__ = ["generate_level_from_blueprint"]
15
16
 
17
+ RNG = get_rng()
18
+
16
19
 
17
20
  def _rect_for_cell(x_idx: int, y_idx: int, cell_size: int) -> pygame.Rect:
18
21
  return pygame.Rect(
@@ -89,11 +92,15 @@ def generate_level_from_blueprint(
89
92
  return True
90
93
  return (nx, ny) in wall_cells
91
94
 
92
- outside_rects: list[pygame.Rect] = []
93
- walkable_cells: list[pygame.Rect] = []
94
- player_cells: list[pygame.Rect] = []
95
- car_cells: list[pygame.Rect] = []
96
- zombie_cells: list[pygame.Rect] = []
95
+ outside_cells: set[tuple[int, int]] = set()
96
+ walkable_cells: list[tuple[int, int]] = []
97
+ player_cells: list[tuple[int, int]] = []
98
+ car_cells: list[tuple[int, int]] = []
99
+ zombie_cells: list[tuple[int, int]] = []
100
+ interior_min_x = 2
101
+ interior_max_x = stage.grid_cols - 3
102
+ interior_min_y = 2
103
+ interior_max_y = stage.grid_rows - 3
97
104
  bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]] = {}
98
105
  palette = get_environment_palette(game_data.state.ambient_palette_key)
99
106
 
@@ -118,7 +125,7 @@ def generate_level_from_blueprint(
118
125
  cell_rect = _rect_for_cell(x, y, cell_size)
119
126
  cell_has_beam = steel_enabled and (x, y) in steel_cells
120
127
  if ch == "O":
121
- outside_rects.append(cell_rect)
128
+ outside_cells.add((x, y))
122
129
  continue
123
130
  if ch == "B":
124
131
  draw_bottom_side = not _has_wall(x, y + 1)
@@ -140,7 +147,7 @@ def generate_level_from_blueprint(
140
147
  continue
141
148
  if ch == "E":
142
149
  if not cell_has_beam:
143
- walkable_cells.append(cell_rect)
150
+ walkable_cells.append((x, y))
144
151
  elif ch == "1":
145
152
  beam = None
146
153
  if cell_has_beam:
@@ -189,14 +196,14 @@ def generate_level_from_blueprint(
189
196
  all_sprites.add(wall, layer=0)
190
197
  else:
191
198
  if not cell_has_beam:
192
- walkable_cells.append(cell_rect)
199
+ walkable_cells.append((x, y))
193
200
 
194
201
  if ch == "P":
195
- player_cells.append(cell_rect)
202
+ player_cells.append((x, y))
196
203
  if ch == "C":
197
- car_cells.append(cell_rect)
204
+ car_cells.append((x, y))
198
205
  if ch == "Z":
199
- zombie_cells.append(cell_rect)
206
+ zombie_cells.append((x, y))
200
207
 
201
208
  if cell_has_beam and ch != "1":
202
209
  beam = SteelBeam(
@@ -208,17 +215,35 @@ def generate_level_from_blueprint(
208
215
  )
209
216
  add_beam_to_groups(beam)
210
217
 
211
- game_data.layout.outer_rect = (0, 0, game_data.level_width, game_data.level_height)
212
- game_data.layout.inner_rect = (0, 0, game_data.level_width, game_data.level_height)
213
- game_data.layout.outside_rects = outside_rects
218
+ game_data.layout.field_rect = pygame.Rect(
219
+ 0,
220
+ 0,
221
+ game_data.level_width,
222
+ game_data.level_height,
223
+ )
224
+ game_data.layout.outside_cells = outside_cells
214
225
  game_data.layout.walkable_cells = walkable_cells
215
226
  game_data.layout.outer_wall_cells = outer_wall_cells
216
227
  game_data.layout.wall_cells = wall_cells
217
- game_data.layout.fall_spawn_cells = _expand_zone_cells(
228
+ fall_spawn_cells = _expand_zone_cells(
218
229
  stage.fall_spawn_zones,
219
230
  grid_cols=stage.grid_cols,
220
231
  grid_rows=stage.grid_rows,
221
232
  )
233
+ floor_ratio = max(0.0, min(1.0, stage.fall_spawn_floor_ratio))
234
+ if floor_ratio > 0.0 and interior_min_x <= interior_max_x:
235
+ for y in range(interior_min_y, interior_max_y + 1):
236
+ for x in range(interior_min_x, interior_max_x + 1):
237
+ if RNG.random() < floor_ratio:
238
+ fall_spawn_cells.add((x, y))
239
+ if not fall_spawn_cells:
240
+ fall_spawn_cells.add(
241
+ (
242
+ RNG.randint(interior_min_x, interior_max_x),
243
+ RNG.randint(interior_min_y, interior_max_y),
244
+ )
245
+ )
246
+ game_data.layout.fall_spawn_cells = fall_spawn_cells
222
247
  game_data.layout.bevel_corners = bevel_corners
223
248
 
224
249
  return {
@@ -13,10 +13,15 @@ from ..entities import (
13
13
  Zombie,
14
14
  )
15
15
  from ..entities_constants import (
16
+ HUMANOID_WALL_BUMP_FRAMES,
16
17
  PLAYER_SPEED,
17
18
  ZOMBIE_SEPARATION_DISTANCE,
18
19
  ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE,
19
20
  )
21
+ from ..gameplay_constants import (
22
+ SHOES_SPEED_MULTIPLIER_ONE,
23
+ SHOES_SPEED_MULTIPLIER_TWO,
24
+ )
20
25
  from ..models import GameData
21
26
  from ..world_grid import WallIndex, apply_tile_edge_nudge, walls_for_radius
22
27
  from .constants import MAX_ZOMBIES
@@ -29,6 +34,7 @@ def process_player_input(
29
34
  keys: Sequence[bool],
30
35
  player: Player,
31
36
  car: Car | None,
37
+ shoes_count: int = 0,
32
38
  pad_input: tuple[float, float] = (0.0, 0.0),
33
39
  ) -> tuple[float, float, float, float]:
34
40
  """Process keyboard input and return movement deltas."""
@@ -44,9 +50,12 @@ def process_player_input(
44
50
  dx_input += pad_input[0]
45
51
  dy_input += pad_input[1]
46
52
 
53
+ player.update_facing_from_input(dx_input, dy_input)
54
+
47
55
  player_dx, player_dy, car_dx, car_dy = 0, 0, 0, 0
48
56
 
49
57
  if player.in_car and car and car.alive():
58
+ car.update_facing_from_input(dx_input, dy_input)
50
59
  target_speed = car.speed
51
60
  move_len = math.hypot(dx_input, dy_input)
52
61
  if move_len > 0:
@@ -55,7 +64,7 @@ def process_player_input(
55
64
  (dy_input / move_len) * target_speed,
56
65
  )
57
66
  elif not player.in_car:
58
- target_speed = PLAYER_SPEED
67
+ target_speed = PLAYER_SPEED * _shoes_speed_multiplier(shoes_count)
59
68
  move_len = math.hypot(dx_input, dy_input)
60
69
  if move_len > 0:
61
70
  player_dx, player_dy = (
@@ -66,6 +75,15 @@ def process_player_input(
66
75
  return player_dx, player_dy, car_dx, car_dy
67
76
 
68
77
 
78
+ def _shoes_speed_multiplier(shoes_count: int) -> float:
79
+ count = max(0, int(shoes_count))
80
+ if count >= 2:
81
+ return SHOES_SPEED_MULTIPLIER_TWO
82
+ if count == 1:
83
+ return SHOES_SPEED_MULTIPLIER_ONE
84
+ return 1.0
85
+
86
+
69
87
  def update_entities(
70
88
  game_data: GameData,
71
89
  player_dx: float,
@@ -152,7 +170,25 @@ def update_entities(
152
170
  target_for_camera = active_car if player.in_car and active_car else player
153
171
  camera.update(target_for_camera)
154
172
 
155
- update_survivors(game_data, wall_index=wall_index)
173
+ if player.inner_wall_hit and player.inner_wall_cell is not None:
174
+ game_data.state.player_wall_target_cell = player.inner_wall_cell
175
+ game_data.state.player_wall_target_ttl = HUMANOID_WALL_BUMP_FRAMES
176
+ elif game_data.state.player_wall_target_ttl > 0:
177
+ game_data.state.player_wall_target_ttl -= 1
178
+ if game_data.state.player_wall_target_ttl <= 0:
179
+ game_data.state.player_wall_target_cell = None
180
+
181
+ wall_target_cell = (
182
+ game_data.state.player_wall_target_cell
183
+ if game_data.state.player_wall_target_ttl > 0
184
+ else None
185
+ )
186
+
187
+ update_survivors(
188
+ game_data,
189
+ wall_index=wall_index,
190
+ wall_target_cell=wall_target_cell,
191
+ )
156
192
  update_falling_zombies(game_data, config)
157
193
 
158
194
  # Spawn new zombies if needed