crimsonland 0.1.0.dev5__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 (139) hide show
  1. crimson/__init__.py +24 -0
  2. crimson/assets_fetch.py +60 -0
  3. crimson/atlas.py +92 -0
  4. crimson/audio_router.py +155 -0
  5. crimson/bonuses.py +167 -0
  6. crimson/camera.py +75 -0
  7. crimson/cli.py +380 -0
  8. crimson/creatures/__init__.py +8 -0
  9. crimson/creatures/ai.py +186 -0
  10. crimson/creatures/anim.py +173 -0
  11. crimson/creatures/damage.py +103 -0
  12. crimson/creatures/runtime.py +1019 -0
  13. crimson/creatures/spawn.py +2871 -0
  14. crimson/debug.py +7 -0
  15. crimson/demo.py +1360 -0
  16. crimson/demo_trial.py +140 -0
  17. crimson/effects.py +1086 -0
  18. crimson/effects_atlas.py +73 -0
  19. crimson/frontend/__init__.py +1 -0
  20. crimson/frontend/assets.py +43 -0
  21. crimson/frontend/boot.py +424 -0
  22. crimson/frontend/menu.py +700 -0
  23. crimson/frontend/panels/__init__.py +1 -0
  24. crimson/frontend/panels/base.py +410 -0
  25. crimson/frontend/panels/controls.py +132 -0
  26. crimson/frontend/panels/mods.py +128 -0
  27. crimson/frontend/panels/options.py +409 -0
  28. crimson/frontend/panels/play_game.py +627 -0
  29. crimson/frontend/panels/stats.py +351 -0
  30. crimson/frontend/transitions.py +31 -0
  31. crimson/game.py +2533 -0
  32. crimson/game_modes.py +15 -0
  33. crimson/game_world.py +652 -0
  34. crimson/gameplay.py +2467 -0
  35. crimson/input_codes.py +176 -0
  36. crimson/modes/__init__.py +1 -0
  37. crimson/modes/base_gameplay_mode.py +219 -0
  38. crimson/modes/quest_mode.py +502 -0
  39. crimson/modes/rush_mode.py +300 -0
  40. crimson/modes/survival_mode.py +792 -0
  41. crimson/modes/tutorial_mode.py +648 -0
  42. crimson/modes/typo_mode.py +472 -0
  43. crimson/paths.py +23 -0
  44. crimson/perks.py +828 -0
  45. crimson/persistence/__init__.py +1 -0
  46. crimson/persistence/highscores.py +385 -0
  47. crimson/persistence/save_status.py +245 -0
  48. crimson/player_damage.py +77 -0
  49. crimson/projectiles.py +1133 -0
  50. crimson/quests/__init__.py +18 -0
  51. crimson/quests/helpers.py +147 -0
  52. crimson/quests/registry.py +49 -0
  53. crimson/quests/results.py +164 -0
  54. crimson/quests/runtime.py +91 -0
  55. crimson/quests/tier1.py +620 -0
  56. crimson/quests/tier2.py +652 -0
  57. crimson/quests/tier3.py +579 -0
  58. crimson/quests/tier4.py +721 -0
  59. crimson/quests/tier5.py +886 -0
  60. crimson/quests/timeline.py +115 -0
  61. crimson/quests/types.py +70 -0
  62. crimson/render/__init__.py +1 -0
  63. crimson/render/terrain_fx.py +88 -0
  64. crimson/render/world_renderer.py +1941 -0
  65. crimson/sim/__init__.py +1 -0
  66. crimson/sim/world_defs.py +67 -0
  67. crimson/sim/world_state.py +422 -0
  68. crimson/terrain_assets.py +19 -0
  69. crimson/tutorial/__init__.py +12 -0
  70. crimson/tutorial/timeline.py +291 -0
  71. crimson/typo/__init__.py +2 -0
  72. crimson/typo/names.py +233 -0
  73. crimson/typo/player.py +43 -0
  74. crimson/typo/spawns.py +73 -0
  75. crimson/typo/typing.py +52 -0
  76. crimson/ui/__init__.py +3 -0
  77. crimson/ui/cursor.py +95 -0
  78. crimson/ui/demo_trial_overlay.py +235 -0
  79. crimson/ui/game_over.py +660 -0
  80. crimson/ui/hud.py +601 -0
  81. crimson/ui/perk_menu.py +388 -0
  82. crimson/views/__init__.py +40 -0
  83. crimson/views/aim_debug.py +276 -0
  84. crimson/views/animations.py +274 -0
  85. crimson/views/arsenal_debug.py +404 -0
  86. crimson/views/audio_bootstrap.py +47 -0
  87. crimson/views/bonuses.py +201 -0
  88. crimson/views/camera_debug.py +359 -0
  89. crimson/views/camera_shake.py +229 -0
  90. crimson/views/corpse_stamp_debug.py +324 -0
  91. crimson/views/decals_debug.py +739 -0
  92. crimson/views/empty.py +19 -0
  93. crimson/views/fonts.py +114 -0
  94. crimson/views/game_over.py +117 -0
  95. crimson/views/ground.py +259 -0
  96. crimson/views/lighting_debug.py +1166 -0
  97. crimson/views/particles.py +293 -0
  98. crimson/views/perk_menu_debug.py +430 -0
  99. crimson/views/perks.py +398 -0
  100. crimson/views/player.py +434 -0
  101. crimson/views/player_sprite_debug.py +314 -0
  102. crimson/views/projectile_fx.py +609 -0
  103. crimson/views/projectile_render_debug.py +393 -0
  104. crimson/views/projectiles.py +221 -0
  105. crimson/views/quest_title_overlay.py +108 -0
  106. crimson/views/registry.py +34 -0
  107. crimson/views/rush.py +16 -0
  108. crimson/views/small_font_debug.py +204 -0
  109. crimson/views/spawn_plan.py +363 -0
  110. crimson/views/sprites.py +214 -0
  111. crimson/views/survival.py +15 -0
  112. crimson/views/terrain.py +132 -0
  113. crimson/views/ui.py +123 -0
  114. crimson/views/wicons.py +166 -0
  115. crimson/weapon_sfx.py +63 -0
  116. crimson/weapons.py +860 -0
  117. crimsonland-0.1.0.dev5.dist-info/METADATA +9 -0
  118. crimsonland-0.1.0.dev5.dist-info/RECORD +139 -0
  119. crimsonland-0.1.0.dev5.dist-info/WHEEL +4 -0
  120. crimsonland-0.1.0.dev5.dist-info/entry_points.txt +4 -0
  121. grim/__init__.py +20 -0
  122. grim/app.py +92 -0
  123. grim/assets.py +231 -0
  124. grim/audio.py +106 -0
  125. grim/config.py +294 -0
  126. grim/console.py +737 -0
  127. grim/fonts/__init__.py +7 -0
  128. grim/fonts/grim_mono.py +111 -0
  129. grim/fonts/small.py +120 -0
  130. grim/input.py +44 -0
  131. grim/jaz.py +103 -0
  132. grim/math.py +17 -0
  133. grim/music.py +403 -0
  134. grim/paq.py +76 -0
  135. grim/rand.py +37 -0
  136. grim/sfx.py +276 -0
  137. grim/sfx_map.py +103 -0
  138. grim/terrain_render.py +840 -0
  139. grim/view.py +16 -0
@@ -0,0 +1,652 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+
5
+ from ..perks import PerkId
6
+ from ..creatures.spawn import SpawnId
7
+ from .helpers import (
8
+ center_point,
9
+ edge_midpoints,
10
+ heading_from_center,
11
+ line_points_x,
12
+ random_angle,
13
+ radial_points,
14
+ spawn,
15
+ spawn_at,
16
+ )
17
+ from .registry import register_quest
18
+ from .types import QuestContext, SpawnEntry
19
+
20
+
21
+ @register_quest(
22
+ level="2.1",
23
+ title="Everred Pastures",
24
+ time_limit_ms=300000,
25
+ start_weapon_id=1,
26
+ unlock_perk_id=PerkId.BONUS_ECONOMIST,
27
+ builder_address=0x004375A0,
28
+ )
29
+ def build_2_1_everred_pastures(ctx: QuestContext) -> list[SpawnEntry]:
30
+ edges = edge_midpoints(ctx.width)
31
+ entries: list[SpawnEntry] = []
32
+ for wave in range(1, 9):
33
+ trigger = (wave - 1) * 13000 + 1500
34
+ count = wave
35
+ entries.append(
36
+ spawn_at(
37
+ edges.right,
38
+ heading=0.0,
39
+ spawn_id=SpawnId.SPIDER_SP1_RANDOM_32,
40
+ trigger_ms=trigger,
41
+ count=count,
42
+ )
43
+ )
44
+ entries.append(
45
+ spawn_at(
46
+ edges.left,
47
+ heading=0.0,
48
+ spawn_id=SpawnId.SPIDER_SP1_RANDOM_RED_33,
49
+ trigger_ms=trigger,
50
+ count=count,
51
+ )
52
+ )
53
+ entries.append(
54
+ spawn_at(
55
+ edges.bottom,
56
+ heading=0.0,
57
+ spawn_id=SpawnId.SPIDER_SP1_RANDOM_GREEN_34,
58
+ trigger_ms=trigger,
59
+ count=count,
60
+ )
61
+ )
62
+ entries.append(
63
+ spawn_at(
64
+ edges.top,
65
+ heading=0.0,
66
+ spawn_id=SpawnId.SPIDER_SP2_RANDOM_35,
67
+ trigger_ms=trigger,
68
+ count=count,
69
+ )
70
+ )
71
+ if wave == 4:
72
+ entries.append(
73
+ spawn_at(
74
+ edges.top,
75
+ heading=0.0,
76
+ spawn_id=SpawnId.AI1_SPIDER_SP1_BLUE_TINT_1B,
77
+ trigger_ms=40500,
78
+ count=8,
79
+ )
80
+ )
81
+ entries.append(
82
+ spawn_at(
83
+ edges.bottom,
84
+ heading=0.0,
85
+ spawn_id=SpawnId.AI1_SPIDER_SP1_BLUE_TINT_1B,
86
+ trigger_ms=40500,
87
+ count=8,
88
+ )
89
+ )
90
+ return entries
91
+
92
+
93
+ @register_quest(
94
+ level="2.2",
95
+ title="Spider Spawns",
96
+ time_limit_ms=300000,
97
+ start_weapon_id=1,
98
+ unlock_weapon_id=0x09,
99
+ builder_address=0x00436D70,
100
+ )
101
+ def build_2_2_spider_spawns(ctx: QuestContext) -> list[SpawnEntry]:
102
+ return [
103
+ spawn(
104
+ x=128.0,
105
+ y=128.0,
106
+ heading=0.0,
107
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
108
+ trigger_ms=1500,
109
+ count=1,
110
+ ),
111
+ spawn(
112
+ x=896.0,
113
+ y=896.0,
114
+ heading=0.0,
115
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
116
+ trigger_ms=1500,
117
+ count=1,
118
+ ),
119
+ spawn(
120
+ x=896.0,
121
+ y=128.0,
122
+ heading=0.0,
123
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
124
+ trigger_ms=1500,
125
+ count=1,
126
+ ),
127
+ spawn(
128
+ x=128.0,
129
+ y=896.0,
130
+ heading=0.0,
131
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
132
+ trigger_ms=1500,
133
+ count=1,
134
+ ),
135
+ spawn(
136
+ x=-64.0,
137
+ y=512.0,
138
+ heading=0.0,
139
+ spawn_id=SpawnId.SPIDER_SP1_AI7_TIMER_38,
140
+ trigger_ms=3000,
141
+ count=2,
142
+ ),
143
+ spawn(
144
+ x=512.0,
145
+ y=512.0,
146
+ heading=0.0,
147
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_SLOW_0A,
148
+ trigger_ms=18000,
149
+ count=1,
150
+ ),
151
+ spawn(
152
+ x=448.0,
153
+ y=448.0,
154
+ heading=0.0,
155
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
156
+ trigger_ms=20500,
157
+ count=1,
158
+ ),
159
+ spawn(
160
+ x=576.0,
161
+ y=448.0,
162
+ heading=0.0,
163
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
164
+ trigger_ms=26000,
165
+ count=1,
166
+ ),
167
+ spawn(
168
+ x=1088.0,
169
+ y=512.0,
170
+ heading=0.0,
171
+ spawn_id=SpawnId.SPIDER_SP1_AI7_TIMER_38,
172
+ trigger_ms=21000,
173
+ count=2,
174
+ ),
175
+ spawn(
176
+ x=576.0,
177
+ y=576.0,
178
+ heading=0.0,
179
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
180
+ trigger_ms=31500,
181
+ count=1,
182
+ ),
183
+ spawn(
184
+ x=448.0,
185
+ y=576.0,
186
+ heading=0.0,
187
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
188
+ trigger_ms=22000,
189
+ count=1,
190
+ ),
191
+ ]
192
+
193
+
194
+ @register_quest(
195
+ level="2.3",
196
+ title="Arachnoid Farm",
197
+ time_limit_ms=240000,
198
+ start_weapon_id=1,
199
+ unlock_perk_id=PerkId.THICK_SKINNED,
200
+ builder_address=0x00436820,
201
+ )
202
+ def build_2_3_arachnoid_farm(ctx: QuestContext) -> list[SpawnEntry]:
203
+ entries: list[SpawnEntry] = []
204
+ if ctx.player_count + 4 >= 0:
205
+ trigger = 500
206
+ for x, y in line_points_x(256.0, 102.4, ctx.player_count + 4, 256.0):
207
+ entries.append(
208
+ spawn(
209
+ x=x,
210
+ y=y,
211
+ heading=0.0,
212
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_SLOW_0A,
213
+ trigger_ms=trigger,
214
+ count=1,
215
+ )
216
+ )
217
+ trigger += 500
218
+ trigger = 10500
219
+ for x, y in line_points_x(256.0, 102.4, ctx.player_count + 4, 768.0):
220
+ entries.append(
221
+ spawn(
222
+ x=x,
223
+ y=y,
224
+ heading=0.0,
225
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_SLOW_0A,
226
+ trigger_ms=trigger,
227
+ count=1,
228
+ )
229
+ )
230
+ trigger += 500
231
+ if ctx.player_count + 7 >= 0:
232
+ trigger = 40500
233
+ for x, y in line_points_x(256.0, 64.0, ctx.player_count + 7, 512.0):
234
+ entries.append(
235
+ spawn(
236
+ x=x,
237
+ y=y,
238
+ heading=0.0,
239
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10,
240
+ trigger_ms=trigger,
241
+ count=1,
242
+ )
243
+ )
244
+ trigger += 3500
245
+ return entries
246
+
247
+
248
+ @register_quest(
249
+ level="2.4",
250
+ title="Two Fronts",
251
+ time_limit_ms=240000,
252
+ start_weapon_id=1,
253
+ unlock_weapon_id=0x15,
254
+ builder_address=0x00436EE0,
255
+ )
256
+ def build_2_4_two_fronts(ctx: QuestContext) -> list[SpawnEntry]:
257
+ entries: list[SpawnEntry] = []
258
+ edges = edge_midpoints(ctx.width)
259
+ for wave in range(0, 40):
260
+ trigger_a = wave * 2000 + 1000
261
+ trigger_b = (wave * 5 + 5) * 400
262
+ entries.append(
263
+ spawn_at(
264
+ edges.right,
265
+ heading=0.0,
266
+ spawn_id=SpawnId.AI1_ALIEN_BLUE_TINT_1A,
267
+ trigger_ms=trigger_a,
268
+ count=1,
269
+ )
270
+ )
271
+ entries.append(
272
+ spawn_at(
273
+ edges.left,
274
+ heading=0.0,
275
+ spawn_id=SpawnId.AI1_SPIDER_SP1_BLUE_TINT_1B,
276
+ trigger_ms=trigger_b,
277
+ count=1,
278
+ )
279
+ )
280
+ if wave in (10, 20):
281
+ trigger = wave * 2000 + 2500
282
+ entries.append(
283
+ spawn(
284
+ x=256.0,
285
+ y=256.0,
286
+ heading=0.0,
287
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_SLOW_0A,
288
+ trigger_ms=trigger,
289
+ count=1,
290
+ )
291
+ )
292
+ entries.append(
293
+ spawn(
294
+ x=768.0,
295
+ y=768.0,
296
+ heading=0.0,
297
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_1D_FAST_07,
298
+ trigger_ms=trigger,
299
+ count=1,
300
+ )
301
+ )
302
+ if wave == 30:
303
+ trigger = 62500
304
+ entries.append(
305
+ spawn(
306
+ x=768.0,
307
+ y=256.0,
308
+ heading=0.0,
309
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_32_SLOW_0A,
310
+ trigger_ms=trigger,
311
+ count=1,
312
+ )
313
+ )
314
+ entries.append(
315
+ spawn(
316
+ x=256.0,
317
+ y=768.0,
318
+ heading=0.0,
319
+ spawn_id=SpawnId.ALIEN_SPAWNER_CHILD_1D_FAST_07,
320
+ trigger_ms=trigger,
321
+ count=1,
322
+ )
323
+ )
324
+ return entries
325
+
326
+
327
+ @register_quest(
328
+ level="2.5",
329
+ title="Sweep Stakes",
330
+ time_limit_ms=35000,
331
+ start_weapon_id=6,
332
+ unlock_perk_id=PerkId.BARREL_GREASER,
333
+ builder_address=0x00437810,
334
+ )
335
+ def build_2_5_sweep_stakes(ctx: QuestContext, rng: random.Random | None = None) -> list[SpawnEntry]:
336
+ rng = rng or random.Random()
337
+ entries: list[SpawnEntry] = []
338
+ center_x, center_y = center_point(ctx.width, ctx.height)
339
+ trigger = 2000
340
+ step = 2000
341
+ while step > 720:
342
+ angle = random_angle(rng)
343
+ for x, y in radial_points(center_x, center_y, angle, 0x54, 0xFC, 0x2A):
344
+ heading = heading_from_center(x, y, center_x, center_y)
345
+ entries.append(
346
+ spawn(
347
+ x=x,
348
+ y=y,
349
+ heading=heading,
350
+ spawn_id=SpawnId.ALIEN_AI7_ORBITER_36,
351
+ trigger_ms=trigger,
352
+ count=1,
353
+ )
354
+ )
355
+ trigger += max(step, 600)
356
+ step -= 0x50
357
+ return entries
358
+
359
+
360
+ @register_quest(
361
+ level="2.6",
362
+ title="Evil Zombies At Large",
363
+ time_limit_ms=180000,
364
+ start_weapon_id=1,
365
+ unlock_weapon_id=0x07,
366
+ builder_address=0x004374A0,
367
+ )
368
+ def build_2_6_evil_zombies_at_large(ctx: QuestContext) -> list[SpawnEntry]:
369
+ entries: list[SpawnEntry] = []
370
+ edges = edge_midpoints(ctx.width)
371
+ trigger = 1500
372
+ count = 4
373
+ while count <= 13:
374
+ entries.append(
375
+ spawn_at(
376
+ edges.right,
377
+ heading=0.0,
378
+ spawn_id=SpawnId.ZOMBIE_RANDOM_41,
379
+ trigger_ms=trigger,
380
+ count=count,
381
+ )
382
+ )
383
+ entries.append(
384
+ spawn_at(
385
+ edges.left,
386
+ heading=0.0,
387
+ spawn_id=SpawnId.ZOMBIE_RANDOM_41,
388
+ trigger_ms=trigger,
389
+ count=count,
390
+ )
391
+ )
392
+ entries.append(
393
+ spawn_at(
394
+ edges.bottom,
395
+ heading=0.0,
396
+ spawn_id=SpawnId.ZOMBIE_RANDOM_41,
397
+ trigger_ms=trigger,
398
+ count=count,
399
+ )
400
+ )
401
+ entries.append(
402
+ spawn_at(
403
+ edges.top,
404
+ heading=0.0,
405
+ spawn_id=SpawnId.ZOMBIE_RANDOM_41,
406
+ trigger_ms=trigger,
407
+ count=count,
408
+ )
409
+ )
410
+ trigger += 5500
411
+ count += 1
412
+ return entries
413
+
414
+
415
+ @register_quest(
416
+ level="2.7",
417
+ title="Survival Of The Fastest",
418
+ time_limit_ms=120000,
419
+ start_weapon_id=5,
420
+ unlock_perk_id=PerkId.AMMUNITION_WITHIN,
421
+ builder_address=0x00437060,
422
+ )
423
+ def build_2_7_survival_of_the_fastest(ctx: QuestContext) -> list[SpawnEntry]:
424
+ entries: list[SpawnEntry | None] = [None] * 26
425
+
426
+ def set_entry(idx: int, x: float, y: float, spawn_id: int, trigger: int, count: int) -> None:
427
+ if idx < 0 or idx >= len(entries):
428
+ return
429
+ entries[idx] = spawn(x=x, y=y, heading=0.0, spawn_id=spawn_id, trigger_ms=trigger, count=count)
430
+
431
+ # Loop 1: x from 256 to <688, step 72
432
+ trigger = 500
433
+ idx = 0
434
+ for x in range(0x100, 0x2B0, 0x48):
435
+ set_entry(idx, float(x), 256.0, SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10, trigger, 1)
436
+ trigger += 900
437
+ idx += 1
438
+
439
+ # Loop 2: y from 256 to <688, step 72, starting at index 6
440
+ trigger = 5900
441
+ idx = 6
442
+ for y in range(0x100, 0x2B0, 0x48):
443
+ set_entry(idx, 688.0, float(y), SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10, trigger, 1)
444
+ trigger += 900
445
+ idx += 1
446
+
447
+ # Loop 3: x descending from 688, y=688, starting at index 12
448
+ trigger = 11300
449
+ idx = 12
450
+ for x in (0x2B0, 0x268, 0x220, 0x1D8):
451
+ set_entry(idx, float(x), 688.0, SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10, trigger, 1)
452
+ trigger += 900
453
+ idx += 1
454
+
455
+ # Loop 4: y descending from 688, x=400, starting at index 16
456
+ trigger = 14900
457
+ idx = 16
458
+ for y in (0x2B0, 0x268, 0x220, 0x1D8):
459
+ set_entry(idx, 400.0, float(y), SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10, trigger, 1)
460
+ trigger += 900
461
+ idx += 1
462
+
463
+ # Loop 5: x from 400 to <544, y=400, starting at index 20
464
+ trigger = 18500
465
+ idx = 20
466
+ for x in range(400, 0x220, 0x48):
467
+ set_entry(idx, float(x), 400.0, SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10, trigger, 1)
468
+ trigger += 900
469
+ idx += 1
470
+
471
+ # Final fixed entries
472
+ set_entry(22, 128.0, 128.0, SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10, 22300, 1)
473
+ set_entry(23, 896.0, 128.0, SpawnId.ALIEN_SPAWNER_CHILD_1D_FAST_07, 22300, 1)
474
+ set_entry(24, 128.0, 896.0, SpawnId.ALIEN_SPAWNER_CHILD_1D_FAST_07, 24300, 1)
475
+ set_entry(25, 896.0, 896.0, SpawnId.ALIEN_SPAWNER_CHILD_32_FAST_10, 24300, 1)
476
+
477
+ return [entry for entry in entries if entry is not None]
478
+
479
+
480
+ @register_quest(
481
+ level="2.8",
482
+ title="Land Of Lizards",
483
+ time_limit_ms=180000,
484
+ start_weapon_id=1,
485
+ unlock_weapon_id=0x04,
486
+ builder_address=0x00437BA0,
487
+ )
488
+ def build_2_8_land_of_lizards(ctx: QuestContext) -> list[SpawnEntry]:
489
+ return [
490
+ spawn(
491
+ x=256.0,
492
+ y=256.0,
493
+ heading=0.0,
494
+ spawn_id=SpawnId.ALIEN_SPAWNER_RING_24_0E,
495
+ trigger_ms=2000,
496
+ count=1,
497
+ ),
498
+ spawn(
499
+ x=768.0,
500
+ y=256.0,
501
+ heading=0.0,
502
+ spawn_id=SpawnId.ALIEN_SPAWNER_RING_24_0E,
503
+ trigger_ms=12000,
504
+ count=1,
505
+ ),
506
+ spawn(
507
+ x=256.0,
508
+ y=768.0,
509
+ heading=0.0,
510
+ spawn_id=SpawnId.ALIEN_SPAWNER_RING_24_0E,
511
+ trigger_ms=22000,
512
+ count=1,
513
+ ),
514
+ spawn(
515
+ x=768.0,
516
+ y=768.0,
517
+ heading=0.0,
518
+ spawn_id=SpawnId.ALIEN_SPAWNER_RING_24_0E,
519
+ trigger_ms=32000,
520
+ count=1,
521
+ ),
522
+ ]
523
+
524
+
525
+ @register_quest(
526
+ level="2.9",
527
+ title="Ghost Patrols",
528
+ time_limit_ms=180000,
529
+ start_weapon_id=1,
530
+ unlock_perk_id=PerkId.VEINS_OF_POISON,
531
+ builder_address=0x00436200,
532
+ )
533
+ def build_2_9_ghost_patrols(ctx: QuestContext) -> list[SpawnEntry]:
534
+ entries: list[SpawnEntry] = []
535
+ edges = edge_midpoints(ctx.width, ctx.height, offset=128.0)
536
+ entries.append(spawn_at(edges.right, heading=0.0, spawn_id=SpawnId.ALIEN_CONST_RED_FAST_2B, trigger_ms=1500, count=2))
537
+ trigger = 2500
538
+ for i in range(12):
539
+ x = edges.left[0] if i % 2 == 0 else edges.right[0]
540
+ entries.append(
541
+ spawn(
542
+ x=x,
543
+ y=edges.left[1],
544
+ heading=0.0,
545
+ spawn_id=SpawnId.FORMATION_RING_ALIEN_5_19,
546
+ trigger_ms=trigger,
547
+ count=1,
548
+ )
549
+ )
550
+ trigger += 2500
551
+ loop_count = 12
552
+ entries.append(
553
+ spawn(
554
+ x=-264.0,
555
+ y=edges.left[1],
556
+ heading=0.0,
557
+ spawn_id=SpawnId.ALIEN_CONST_RED_FAST_2B,
558
+ trigger_ms=(loop_count - 1) * 2500,
559
+ count=1,
560
+ )
561
+ )
562
+ special_trigger = (5 * loop_count + 15) * 500
563
+ entries.append(
564
+ spawn(
565
+ x=edges.left[0],
566
+ y=edges.left[1],
567
+ heading=0.0,
568
+ spawn_id=SpawnId.FORMATION_GRID_ALIEN_BRONZE_18,
569
+ trigger_ms=special_trigger,
570
+ count=1,
571
+ )
572
+ )
573
+ return entries
574
+
575
+
576
+ @register_quest(
577
+ level="2.10",
578
+ title="Spideroids",
579
+ time_limit_ms=360000,
580
+ start_weapon_id=1,
581
+ unlock_weapon_id=0x0B,
582
+ builder_address=0x004373C0,
583
+ )
584
+ def build_2_10_spideroids(ctx: QuestContext, full_version: bool = True) -> list[SpawnEntry]:
585
+ entries = [
586
+ spawn(
587
+ x=1088.0,
588
+ y=512.0,
589
+ heading=0.0,
590
+ spawn_id=SpawnId.SPIDER_SP2_SPLITTER_01,
591
+ trigger_ms=1000,
592
+ count=1,
593
+ ),
594
+ spawn(x=-64.0, y=512.0, heading=0.0, spawn_id=SpawnId.SPIDER_SP2_SPLITTER_01, trigger_ms=3000, count=1),
595
+ spawn(
596
+ x=1088.0,
597
+ y=256.0,
598
+ heading=0.0,
599
+ spawn_id=SpawnId.SPIDER_SP2_SPLITTER_01,
600
+ trigger_ms=6000,
601
+ count=1,
602
+ ),
603
+ ]
604
+ if full_version:
605
+ entries.append(
606
+ spawn(
607
+ x=1088.0,
608
+ y=762.0,
609
+ heading=0.0,
610
+ spawn_id=SpawnId.SPIDER_SP2_SPLITTER_01,
611
+ trigger_ms=9000,
612
+ count=1,
613
+ )
614
+ )
615
+ entries.append(
616
+ spawn(
617
+ x=512.0,
618
+ y=1088.0,
619
+ heading=0.0,
620
+ spawn_id=SpawnId.SPIDER_SP2_SPLITTER_01,
621
+ trigger_ms=9000,
622
+ count=1,
623
+ )
624
+ )
625
+ if ctx.player_count >= 2 or full_version:
626
+ entries.append(
627
+ spawn(
628
+ x=-64.0,
629
+ y=762.0,
630
+ heading=0.0,
631
+ spawn_id=SpawnId.SPIDER_SP2_SPLITTER_01,
632
+ trigger_ms=9000,
633
+ count=1,
634
+ )
635
+ )
636
+ return entries
637
+
638
+
639
+ __all__ = [
640
+ "QuestContext",
641
+ "SpawnEntry",
642
+ "build_2_1_everred_pastures",
643
+ "build_2_2_spider_spawns",
644
+ "build_2_3_arachnoid_farm",
645
+ "build_2_4_two_fronts",
646
+ "build_2_5_sweep_stakes",
647
+ "build_2_6_evil_zombies_at_large",
648
+ "build_2_7_survival_of_the_fastest",
649
+ "build_2_8_land_of_lizards",
650
+ "build_2_9_ghost_patrols",
651
+ "build_2_10_spideroids",
652
+ ]