crimsonland 0.1.0.dev15__py3-none-any.whl → 0.1.0.dev16__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.
- crimson/cli.py +61 -0
- crimson/creatures/damage.py +111 -36
- crimson/creatures/runtime.py +246 -156
- crimson/creatures/spawn.py +7 -3
- crimson/demo.py +38 -45
- crimson/effects.py +7 -13
- crimson/frontend/high_scores_layout.py +81 -0
- crimson/frontend/panels/base.py +4 -1
- crimson/frontend/panels/controls.py +0 -15
- crimson/frontend/panels/databases.py +291 -3
- crimson/frontend/panels/mods.py +0 -15
- crimson/frontend/panels/play_game.py +0 -16
- crimson/game.py +441 -1
- crimson/gameplay.py +905 -569
- crimson/modes/base_gameplay_mode.py +33 -12
- crimson/modes/components/__init__.py +2 -0
- crimson/modes/components/highscore_record_builder.py +58 -0
- crimson/modes/components/perk_menu_controller.py +325 -0
- crimson/modes/quest_mode.py +58 -273
- crimson/modes/rush_mode.py +12 -43
- crimson/modes/survival_mode.py +71 -328
- crimson/modes/tutorial_mode.py +46 -247
- crimson/modes/typo_mode.py +11 -38
- crimson/oracle.py +396 -0
- crimson/perks.py +5 -2
- crimson/player_damage.py +94 -37
- crimson/projectiles.py +539 -320
- crimson/render/projectile_draw_registry.py +637 -0
- crimson/render/projectile_render_registry.py +110 -0
- crimson/render/secondary_projectile_draw_registry.py +206 -0
- crimson/render/world_renderer.py +58 -707
- crimson/sim/world_state.py +118 -61
- crimson/typo/spawns.py +5 -12
- crimson/ui/demo_trial_overlay.py +3 -11
- crimson/ui/formatting.py +24 -0
- crimson/ui/game_over.py +12 -58
- crimson/ui/hud.py +72 -39
- crimson/ui/layout.py +20 -0
- crimson/ui/perk_menu.py +9 -34
- crimson/ui/quest_results.py +12 -64
- crimson/ui/text_input.py +20 -0
- crimson/views/_ui_helpers.py +27 -0
- crimson/views/aim_debug.py +15 -32
- crimson/views/animations.py +18 -28
- crimson/views/arsenal_debug.py +22 -32
- crimson/views/bonuses.py +23 -36
- crimson/views/camera_debug.py +16 -29
- crimson/views/camera_shake.py +9 -33
- crimson/views/corpse_stamp_debug.py +13 -21
- crimson/views/decals_debug.py +36 -23
- crimson/views/fonts.py +8 -25
- crimson/views/ground.py +4 -21
- crimson/views/lighting_debug.py +42 -45
- crimson/views/particles.py +33 -42
- crimson/views/perk_menu_debug.py +3 -10
- crimson/views/player.py +50 -44
- crimson/views/player_sprite_debug.py +24 -31
- crimson/views/projectile_fx.py +57 -52
- crimson/views/projectile_render_debug.py +24 -33
- crimson/views/projectiles.py +24 -37
- crimson/views/spawn_plan.py +13 -29
- crimson/views/sprites.py +14 -29
- crimson/views/terrain.py +6 -23
- crimson/views/ui.py +7 -24
- crimson/views/wicons.py +28 -33
- {crimsonland-0.1.0.dev15.dist-info → crimsonland-0.1.0.dev16.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev15.dist-info → crimsonland-0.1.0.dev16.dist-info}/RECORD +72 -64
- {crimsonland-0.1.0.dev15.dist-info → crimsonland-0.1.0.dev16.dist-info}/WHEEL +2 -2
- grim/config.py +29 -1
- grim/console.py +7 -10
- grim/math.py +12 -0
- crimson/.DS_Store +0 -0
- crimson/creatures/.DS_Store +0 -0
- grim/.DS_Store +0 -0
- {crimsonland-0.1.0.dev15.dist-info → crimsonland-0.1.0.dev16.dist-info}/entry_points.txt +0 -0
crimson/projectiles.py
CHANGED
|
@@ -5,6 +5,8 @@ from enum import IntEnum
|
|
|
5
5
|
import math
|
|
6
6
|
from typing import Callable, Protocol
|
|
7
7
|
|
|
8
|
+
from grim.math import distance_sq
|
|
9
|
+
|
|
8
10
|
from .creatures.spawn import CreatureFlags
|
|
9
11
|
from .perks import PerkId
|
|
10
12
|
from .weapons import weapon_entry_for_projectile_type_id
|
|
@@ -116,12 +118,6 @@ class SecondaryProjectile:
|
|
|
116
118
|
target_id: int = -1
|
|
117
119
|
|
|
118
120
|
|
|
119
|
-
def _distance_sq(x0: float, y0: float, x1: float, y1: float) -> float:
|
|
120
|
-
dx = x1 - x0
|
|
121
|
-
dy = y1 - y0
|
|
122
|
-
return dx * dx + dy * dy
|
|
123
|
-
|
|
124
|
-
|
|
125
121
|
def _hit_radius_for(creature: Damageable) -> float:
|
|
126
122
|
"""Approximate `creature_find_in_radius`/`creatures_apply_radius_damage` sizing.
|
|
127
123
|
|
|
@@ -136,6 +132,488 @@ def _hit_radius_for(creature: Damageable) -> float:
|
|
|
136
132
|
return max(0.0, size * 0.14285715 + 3.0)
|
|
137
133
|
|
|
138
134
|
|
|
135
|
+
def _apply_damage_to_creature(
|
|
136
|
+
creatures: list[Damageable],
|
|
137
|
+
creature_index: int,
|
|
138
|
+
damage: float,
|
|
139
|
+
*,
|
|
140
|
+
damage_type: int,
|
|
141
|
+
impulse_x: float,
|
|
142
|
+
impulse_y: float,
|
|
143
|
+
owner_id: int,
|
|
144
|
+
apply_creature_damage: CreatureDamageApplier | None = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
if damage <= 0.0:
|
|
147
|
+
return
|
|
148
|
+
idx = int(creature_index)
|
|
149
|
+
if not (0 <= idx < len(creatures)):
|
|
150
|
+
return
|
|
151
|
+
if apply_creature_damage is not None:
|
|
152
|
+
apply_creature_damage(
|
|
153
|
+
idx,
|
|
154
|
+
float(damage),
|
|
155
|
+
int(damage_type),
|
|
156
|
+
float(impulse_x),
|
|
157
|
+
float(impulse_y),
|
|
158
|
+
int(owner_id),
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
creatures[idx].hp -= float(damage)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _spawn_ion_hit_effects(
|
|
165
|
+
effects: object | None,
|
|
166
|
+
sfx_queue: object | None,
|
|
167
|
+
*,
|
|
168
|
+
type_id: int,
|
|
169
|
+
pos_x: float,
|
|
170
|
+
pos_y: float,
|
|
171
|
+
rng: Callable[[], int],
|
|
172
|
+
detail_preset: int,
|
|
173
|
+
) -> None:
|
|
174
|
+
if effects is None or not hasattr(effects, "spawn"):
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
ring_scale = 0.0
|
|
178
|
+
ring_strength = 0.0
|
|
179
|
+
burst_scale = 0.0
|
|
180
|
+
if type_id == int(ProjectileTypeId.ION_MINIGUN):
|
|
181
|
+
ring_scale = 1.5
|
|
182
|
+
ring_strength = 0.1
|
|
183
|
+
burst_scale = 0.8
|
|
184
|
+
elif type_id == int(ProjectileTypeId.ION_RIFLE):
|
|
185
|
+
ring_scale = 1.2
|
|
186
|
+
ring_strength = 0.4
|
|
187
|
+
burst_scale = 1.2
|
|
188
|
+
elif type_id == int(ProjectileTypeId.ION_CANNON):
|
|
189
|
+
ring_scale = 1.0
|
|
190
|
+
ring_strength = 1.0
|
|
191
|
+
burst_scale = 2.2
|
|
192
|
+
if isinstance(sfx_queue, list):
|
|
193
|
+
sfx_queue.append("sfx_shockwave")
|
|
194
|
+
else:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
detail = int(detail_preset)
|
|
198
|
+
|
|
199
|
+
# Port of `FUN_0042f270(pos, ring_scale, ring_strength)`: ring burst (effect_id=1).
|
|
200
|
+
effects.spawn(
|
|
201
|
+
effect_id=1,
|
|
202
|
+
pos_x=float(pos_x),
|
|
203
|
+
pos_y=float(pos_y),
|
|
204
|
+
vel_x=0.0,
|
|
205
|
+
vel_y=0.0,
|
|
206
|
+
rotation=0.0,
|
|
207
|
+
scale=1.0,
|
|
208
|
+
half_width=4.0,
|
|
209
|
+
half_height=4.0,
|
|
210
|
+
age=0.0,
|
|
211
|
+
lifetime=float(ring_strength) * 0.8,
|
|
212
|
+
flags=0x19,
|
|
213
|
+
color_r=0.6,
|
|
214
|
+
color_g=0.6,
|
|
215
|
+
color_b=0.9,
|
|
216
|
+
color_a=1.0,
|
|
217
|
+
rotation_step=0.0,
|
|
218
|
+
scale_step=float(ring_scale) * 45.0,
|
|
219
|
+
detail_preset=detail,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Port of `FUN_0042f540(pos, burst_scale)`: burst cloud (effect_id=0).
|
|
223
|
+
burst = float(burst_scale) * 0.8
|
|
224
|
+
lifetime = min(burst * 0.7, 1.1)
|
|
225
|
+
half = burst * 32.0
|
|
226
|
+
count = int(half)
|
|
227
|
+
if detail < 3:
|
|
228
|
+
count //= 2
|
|
229
|
+
|
|
230
|
+
for _ in range(max(0, count)):
|
|
231
|
+
rotation = float(int(rng()) & 0x7F) * 0.049087387
|
|
232
|
+
vel_x = float((int(rng()) & 0x7F) - 0x40) * burst * 1.4
|
|
233
|
+
vel_y = float((int(rng()) & 0x7F) - 0x40) * burst * 1.4
|
|
234
|
+
scale_step = (float(int(rng()) % 100) * 0.01 + 0.1) * burst
|
|
235
|
+
effects.spawn(
|
|
236
|
+
effect_id=0,
|
|
237
|
+
pos_x=float(pos_x),
|
|
238
|
+
pos_y=float(pos_y),
|
|
239
|
+
vel_x=vel_x,
|
|
240
|
+
vel_y=vel_y,
|
|
241
|
+
rotation=rotation,
|
|
242
|
+
scale=1.0,
|
|
243
|
+
half_width=half,
|
|
244
|
+
half_height=half,
|
|
245
|
+
age=0.0,
|
|
246
|
+
lifetime=float(lifetime),
|
|
247
|
+
flags=0x1D,
|
|
248
|
+
color_r=0.4,
|
|
249
|
+
color_g=0.5,
|
|
250
|
+
color_b=1.0,
|
|
251
|
+
color_a=0.5,
|
|
252
|
+
rotation_step=0.0,
|
|
253
|
+
scale_step=scale_step,
|
|
254
|
+
detail_preset=detail,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _spawn_plasma_cannon_hit_effects(
|
|
259
|
+
effects: object | None,
|
|
260
|
+
sfx_queue: object | None,
|
|
261
|
+
*,
|
|
262
|
+
pos_x: float,
|
|
263
|
+
pos_y: float,
|
|
264
|
+
detail_preset: int,
|
|
265
|
+
) -> None:
|
|
266
|
+
"""Port of `projectile_update` Plasma Cannon hit extras.
|
|
267
|
+
|
|
268
|
+
Native does:
|
|
269
|
+
- `sfx_play_panned(sfx_explosion_medium)`
|
|
270
|
+
- `sfx_play_panned(sfx_shockwave)`
|
|
271
|
+
- `FUN_0042f330(pos, 1.5, 1.0)`
|
|
272
|
+
- `FUN_0042f330(pos, 1.0, 1.0)`
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
if effects is None or not hasattr(effects, "spawn"):
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
if isinstance(sfx_queue, list):
|
|
279
|
+
sfx_queue.append("sfx_explosion_medium")
|
|
280
|
+
sfx_queue.append("sfx_shockwave")
|
|
281
|
+
|
|
282
|
+
detail = int(detail_preset)
|
|
283
|
+
|
|
284
|
+
def _spawn_ring(*, scale: float) -> None:
|
|
285
|
+
effects.spawn(
|
|
286
|
+
effect_id=1,
|
|
287
|
+
pos_x=float(pos_x),
|
|
288
|
+
pos_y=float(pos_y),
|
|
289
|
+
vel_x=0.0,
|
|
290
|
+
vel_y=0.0,
|
|
291
|
+
rotation=0.0,
|
|
292
|
+
scale=1.0,
|
|
293
|
+
half_width=4.0,
|
|
294
|
+
half_height=4.0,
|
|
295
|
+
age=0.1,
|
|
296
|
+
lifetime=1.0,
|
|
297
|
+
flags=0x19,
|
|
298
|
+
color_r=0.9,
|
|
299
|
+
color_g=0.6,
|
|
300
|
+
color_b=0.3,
|
|
301
|
+
color_a=1.0,
|
|
302
|
+
rotation_step=0.0,
|
|
303
|
+
scale_step=float(scale) * 45.0,
|
|
304
|
+
detail_preset=detail,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
_spawn_ring(scale=1.5)
|
|
308
|
+
_spawn_ring(scale=1.0)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _spawn_splitter_hit_effects(
|
|
312
|
+
effects: object | None,
|
|
313
|
+
*,
|
|
314
|
+
pos_x: float,
|
|
315
|
+
pos_y: float,
|
|
316
|
+
rng: Callable[[], int],
|
|
317
|
+
detail_preset: int,
|
|
318
|
+
) -> None:
|
|
319
|
+
"""Port of `FUN_0042f3f0(pos, 26.0, 3)` from the Splitter Gun hit branch."""
|
|
320
|
+
|
|
321
|
+
if effects is None or not hasattr(effects, "spawn"):
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
detail = int(detail_preset)
|
|
325
|
+
for _ in range(3):
|
|
326
|
+
angle = float(int(rng()) & 0x1FF) * (math.tau / 512.0)
|
|
327
|
+
radius = float(int(rng()) % 26)
|
|
328
|
+
jitter_age = -float(int(rng()) & 0xFF) * 0.0012
|
|
329
|
+
lifetime = 0.1 - jitter_age
|
|
330
|
+
|
|
331
|
+
effects.spawn(
|
|
332
|
+
effect_id=0,
|
|
333
|
+
pos_x=float(pos_x) + math.cos(angle) * radius,
|
|
334
|
+
pos_y=float(pos_y) + math.sin(angle) * radius,
|
|
335
|
+
vel_x=0.0,
|
|
336
|
+
vel_y=0.0,
|
|
337
|
+
rotation=0.0,
|
|
338
|
+
scale=1.0,
|
|
339
|
+
half_width=4.0,
|
|
340
|
+
half_height=4.0,
|
|
341
|
+
age=jitter_age,
|
|
342
|
+
lifetime=lifetime,
|
|
343
|
+
flags=0x19,
|
|
344
|
+
color_r=1.0,
|
|
345
|
+
color_g=0.9,
|
|
346
|
+
color_b=0.1,
|
|
347
|
+
color_a=1.0,
|
|
348
|
+
rotation_step=0.0,
|
|
349
|
+
scale_step=55.0,
|
|
350
|
+
detail_preset=detail,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@dataclass(slots=True)
|
|
355
|
+
class _ProjectileUpdateCtx:
|
|
356
|
+
pool: ProjectilePool
|
|
357
|
+
creatures: list[Damageable]
|
|
358
|
+
dt: float
|
|
359
|
+
ion_scale: float
|
|
360
|
+
detail_preset: int
|
|
361
|
+
rng: Callable[[], int]
|
|
362
|
+
runtime_state: object | None
|
|
363
|
+
effects: object | None
|
|
364
|
+
sfx_queue: object | None
|
|
365
|
+
apply_creature_damage: CreatureDamageApplier | None
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@dataclass(slots=True)
|
|
369
|
+
class _ProjectileHitInfo:
|
|
370
|
+
proj_index: int
|
|
371
|
+
proj: Projectile
|
|
372
|
+
hit_idx: int
|
|
373
|
+
move_dx: float
|
|
374
|
+
move_dy: float
|
|
375
|
+
target_x: float
|
|
376
|
+
target_y: float
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@dataclass(slots=True)
|
|
380
|
+
class _ProjectileHitPerkCtx:
|
|
381
|
+
proj: Projectile
|
|
382
|
+
creature: Damageable
|
|
383
|
+
rng: Callable[[], int]
|
|
384
|
+
owner_perk_active: Callable[[int, int], bool]
|
|
385
|
+
poison_idx: int
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
_ProjectileHitPerkHook = Callable[[_ProjectileHitPerkCtx], None]
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _projectile_hit_perk_poison_bullets(ctx: _ProjectileHitPerkCtx) -> None:
|
|
392
|
+
if ctx.owner_perk_active(int(ctx.proj.owner_id), int(ctx.poison_idx)) and (int(ctx.rng()) & 7) == 1:
|
|
393
|
+
if hasattr(ctx.creature, "flags"):
|
|
394
|
+
ctx.creature.flags |= CreatureFlags.SELF_DAMAGE_TICK
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
_PROJECTILE_HIT_PERK_HOOKS: tuple[_ProjectileHitPerkHook, ...] = (_projectile_hit_perk_poison_bullets,)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
ProjectileLingerHandler = Callable[[_ProjectileUpdateCtx, Projectile], None]
|
|
401
|
+
ProjectilePreHitCreatureHandler = Callable[[_ProjectileUpdateCtx, Projectile, int], None]
|
|
402
|
+
ProjectilePostHitCreatureHandler = Callable[[_ProjectileUpdateCtx, _ProjectileHitInfo], None]
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@dataclass(frozen=True, slots=True)
|
|
406
|
+
class ProjectileBehavior:
|
|
407
|
+
linger: ProjectileLingerHandler
|
|
408
|
+
pre_hit_creature: ProjectilePreHitCreatureHandler | None = None
|
|
409
|
+
post_hit_creature: ProjectilePostHitCreatureHandler | None = None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _linger_default(ctx: _ProjectileUpdateCtx, proj: Projectile) -> None:
|
|
413
|
+
proj.life_timer -= ctx.dt
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _linger_gauss_gun(ctx: _ProjectileUpdateCtx, proj: Projectile) -> None:
|
|
417
|
+
proj.life_timer -= ctx.dt * 0.1
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _linger_ion_minigun(ctx: _ProjectileUpdateCtx, proj: Projectile) -> None:
|
|
421
|
+
proj.life_timer -= ctx.dt
|
|
422
|
+
damage = ctx.dt * 40.0
|
|
423
|
+
radius = ctx.ion_scale * 60.0
|
|
424
|
+
for creature_idx, creature in enumerate(ctx.creatures):
|
|
425
|
+
if creature.hp <= 0.0:
|
|
426
|
+
continue
|
|
427
|
+
creature_radius = _hit_radius_for(creature)
|
|
428
|
+
hit_r = radius + creature_radius
|
|
429
|
+
if distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
430
|
+
_apply_damage_to_creature(
|
|
431
|
+
ctx.creatures,
|
|
432
|
+
creature_idx,
|
|
433
|
+
damage,
|
|
434
|
+
damage_type=7,
|
|
435
|
+
impulse_x=0.0,
|
|
436
|
+
impulse_y=0.0,
|
|
437
|
+
owner_id=int(proj.owner_id),
|
|
438
|
+
apply_creature_damage=ctx.apply_creature_damage,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _linger_ion_rifle(ctx: _ProjectileUpdateCtx, proj: Projectile) -> None:
|
|
443
|
+
proj.life_timer -= ctx.dt
|
|
444
|
+
damage = ctx.dt * 100.0
|
|
445
|
+
radius = ctx.ion_scale * 88.0
|
|
446
|
+
for creature_idx, creature in enumerate(ctx.creatures):
|
|
447
|
+
if creature.hp <= 0.0:
|
|
448
|
+
continue
|
|
449
|
+
creature_radius = _hit_radius_for(creature)
|
|
450
|
+
hit_r = radius + creature_radius
|
|
451
|
+
if distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
452
|
+
_apply_damage_to_creature(
|
|
453
|
+
ctx.creatures,
|
|
454
|
+
creature_idx,
|
|
455
|
+
damage,
|
|
456
|
+
damage_type=7,
|
|
457
|
+
impulse_x=0.0,
|
|
458
|
+
impulse_y=0.0,
|
|
459
|
+
owner_id=int(proj.owner_id),
|
|
460
|
+
apply_creature_damage=ctx.apply_creature_damage,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _linger_ion_cannon(ctx: _ProjectileUpdateCtx, proj: Projectile) -> None:
|
|
465
|
+
proj.life_timer -= ctx.dt * 0.7
|
|
466
|
+
damage = ctx.dt * 300.0
|
|
467
|
+
radius = ctx.ion_scale * 128.0
|
|
468
|
+
for creature_idx, creature in enumerate(ctx.creatures):
|
|
469
|
+
if creature.hp <= 0.0:
|
|
470
|
+
continue
|
|
471
|
+
creature_radius = _hit_radius_for(creature)
|
|
472
|
+
hit_r = radius + creature_radius
|
|
473
|
+
if distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
474
|
+
_apply_damage_to_creature(
|
|
475
|
+
ctx.creatures,
|
|
476
|
+
creature_idx,
|
|
477
|
+
damage,
|
|
478
|
+
damage_type=7,
|
|
479
|
+
impulse_x=0.0,
|
|
480
|
+
impulse_y=0.0,
|
|
481
|
+
owner_id=int(proj.owner_id),
|
|
482
|
+
apply_creature_damage=ctx.apply_creature_damage,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _pre_hit_splitter(ctx: _ProjectileUpdateCtx, proj: Projectile, hit_idx: int) -> None:
|
|
487
|
+
_spawn_splitter_hit_effects(
|
|
488
|
+
ctx.effects,
|
|
489
|
+
pos_x=float(proj.pos_x),
|
|
490
|
+
pos_y=float(proj.pos_y),
|
|
491
|
+
rng=ctx.rng,
|
|
492
|
+
detail_preset=ctx.detail_preset,
|
|
493
|
+
)
|
|
494
|
+
ctx.pool.spawn(
|
|
495
|
+
pos_x=proj.pos_x,
|
|
496
|
+
pos_y=proj.pos_y,
|
|
497
|
+
angle=proj.angle - 1.0471976,
|
|
498
|
+
type_id=ProjectileTypeId.SPLITTER_GUN,
|
|
499
|
+
owner_id=int(hit_idx),
|
|
500
|
+
base_damage=proj.base_damage,
|
|
501
|
+
hits_players=proj.hits_players,
|
|
502
|
+
)
|
|
503
|
+
ctx.pool.spawn(
|
|
504
|
+
pos_x=proj.pos_x,
|
|
505
|
+
pos_y=proj.pos_y,
|
|
506
|
+
angle=proj.angle + 1.0471976,
|
|
507
|
+
type_id=ProjectileTypeId.SPLITTER_GUN,
|
|
508
|
+
owner_id=int(hit_idx),
|
|
509
|
+
base_damage=proj.base_damage,
|
|
510
|
+
hits_players=proj.hits_players,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _post_hit_ion_common(ctx: _ProjectileUpdateCtx, hit: _ProjectileHitInfo) -> None:
|
|
515
|
+
_spawn_ion_hit_effects(
|
|
516
|
+
ctx.effects,
|
|
517
|
+
ctx.sfx_queue,
|
|
518
|
+
type_id=int(hit.proj.type_id),
|
|
519
|
+
pos_x=float(hit.target_x),
|
|
520
|
+
pos_y=float(hit.target_y),
|
|
521
|
+
rng=ctx.rng,
|
|
522
|
+
detail_preset=ctx.detail_preset,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _post_hit_ion_rifle(ctx: _ProjectileUpdateCtx, hit: _ProjectileHitInfo) -> None:
|
|
527
|
+
if ctx.runtime_state is not None and getattr(ctx.runtime_state, "shock_chain_projectile_id", -1) == hit.proj_index:
|
|
528
|
+
hit.proj.reserved = float(int(hit.hit_idx) + 1)
|
|
529
|
+
_post_hit_ion_common(ctx, hit)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _post_hit_plasma_cannon(ctx: _ProjectileUpdateCtx, hit: _ProjectileHitInfo) -> None:
|
|
533
|
+
creature = ctx.creatures[int(hit.hit_idx)]
|
|
534
|
+
size = float(getattr(creature, "size", 50.0) or 50.0)
|
|
535
|
+
ring_radius = size * 0.5 + 1.0
|
|
536
|
+
|
|
537
|
+
plasma_entry = weapon_entry_for_projectile_type_id(int(ProjectileTypeId.PLASMA_RIFLE))
|
|
538
|
+
plasma_meta = float(plasma_entry.projectile_meta) if plasma_entry and plasma_entry.projectile_meta is not None else hit.proj.base_damage
|
|
539
|
+
|
|
540
|
+
for ring_idx in range(12):
|
|
541
|
+
ring_angle = float(ring_idx) * (math.pi / 6.0)
|
|
542
|
+
ctx.pool.spawn(
|
|
543
|
+
pos_x=hit.proj.pos_x + math.cos(ring_angle) * ring_radius,
|
|
544
|
+
pos_y=hit.proj.pos_y + math.sin(ring_angle) * ring_radius,
|
|
545
|
+
angle=ring_angle,
|
|
546
|
+
type_id=ProjectileTypeId.PLASMA_RIFLE,
|
|
547
|
+
owner_id=-100,
|
|
548
|
+
base_damage=plasma_meta,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
_spawn_plasma_cannon_hit_effects(
|
|
552
|
+
ctx.effects,
|
|
553
|
+
ctx.sfx_queue,
|
|
554
|
+
pos_x=float(hit.proj.pos_x),
|
|
555
|
+
pos_y=float(hit.proj.pos_y),
|
|
556
|
+
detail_preset=ctx.detail_preset,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _post_hit_shrinkifier(ctx: _ProjectileUpdateCtx, hit: _ProjectileHitInfo) -> None:
|
|
561
|
+
creature = ctx.creatures[int(hit.hit_idx)]
|
|
562
|
+
if hasattr(creature, "size"):
|
|
563
|
+
new_size = float(getattr(creature, "size", 50.0) or 50.0) * 0.65
|
|
564
|
+
setattr(creature, "size", new_size)
|
|
565
|
+
if new_size < 16.0:
|
|
566
|
+
_apply_damage_to_creature(
|
|
567
|
+
ctx.creatures,
|
|
568
|
+
int(hit.hit_idx),
|
|
569
|
+
float(creature.hp) + 1.0,
|
|
570
|
+
damage_type=1,
|
|
571
|
+
impulse_x=0.0,
|
|
572
|
+
impulse_y=0.0,
|
|
573
|
+
owner_id=int(hit.proj.owner_id),
|
|
574
|
+
apply_creature_damage=ctx.apply_creature_damage,
|
|
575
|
+
)
|
|
576
|
+
hit.proj.life_timer = 0.25
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def _post_hit_pulse_gun(ctx: _ProjectileUpdateCtx, hit: _ProjectileHitInfo) -> None:
|
|
580
|
+
creature = ctx.creatures[int(hit.hit_idx)]
|
|
581
|
+
creature.x += hit.move_dx * 3.0
|
|
582
|
+
creature.y += hit.move_dy * 3.0
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def _post_hit_plague_spreader(ctx: _ProjectileUpdateCtx, hit: _ProjectileHitInfo) -> None:
|
|
586
|
+
creature = ctx.creatures[int(hit.hit_idx)]
|
|
587
|
+
if hasattr(creature, "plague_infected"):
|
|
588
|
+
setattr(creature, "plague_infected", True)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
_DEFAULT_BEHAVIOR = ProjectileBehavior(linger=_linger_default)
|
|
592
|
+
|
|
593
|
+
# Public: used by tests to ensure handler coverage.
|
|
594
|
+
PROJECTILE_BEHAVIOR_BY_TYPE_ID: dict[int, ProjectileBehavior] = {
|
|
595
|
+
int(ProjectileTypeId.PISTOL): _DEFAULT_BEHAVIOR,
|
|
596
|
+
int(ProjectileTypeId.ASSAULT_RIFLE): _DEFAULT_BEHAVIOR,
|
|
597
|
+
int(ProjectileTypeId.SHOTGUN): _DEFAULT_BEHAVIOR,
|
|
598
|
+
int(ProjectileTypeId.SUBMACHINE_GUN): _DEFAULT_BEHAVIOR,
|
|
599
|
+
int(ProjectileTypeId.GAUSS_GUN): ProjectileBehavior(linger=_linger_gauss_gun),
|
|
600
|
+
int(ProjectileTypeId.PLASMA_RIFLE): _DEFAULT_BEHAVIOR,
|
|
601
|
+
int(ProjectileTypeId.PLASMA_MINIGUN): _DEFAULT_BEHAVIOR,
|
|
602
|
+
int(ProjectileTypeId.PULSE_GUN): ProjectileBehavior(linger=_linger_default, post_hit_creature=_post_hit_pulse_gun),
|
|
603
|
+
int(ProjectileTypeId.ION_RIFLE): ProjectileBehavior(linger=_linger_ion_rifle, post_hit_creature=_post_hit_ion_rifle),
|
|
604
|
+
int(ProjectileTypeId.ION_MINIGUN): ProjectileBehavior(linger=_linger_ion_minigun, post_hit_creature=_post_hit_ion_common),
|
|
605
|
+
int(ProjectileTypeId.ION_CANNON): ProjectileBehavior(linger=_linger_ion_cannon, post_hit_creature=_post_hit_ion_common),
|
|
606
|
+
int(ProjectileTypeId.SHRINKIFIER): ProjectileBehavior(linger=_linger_default, post_hit_creature=_post_hit_shrinkifier),
|
|
607
|
+
int(ProjectileTypeId.BLADE_GUN): _DEFAULT_BEHAVIOR,
|
|
608
|
+
int(ProjectileTypeId.SPIDER_PLASMA): _DEFAULT_BEHAVIOR,
|
|
609
|
+
int(ProjectileTypeId.PLASMA_CANNON): ProjectileBehavior(linger=_linger_default, post_hit_creature=_post_hit_plasma_cannon),
|
|
610
|
+
int(ProjectileTypeId.SPLITTER_GUN): ProjectileBehavior(linger=_linger_default, pre_hit_creature=_pre_hit_splitter),
|
|
611
|
+
int(ProjectileTypeId.PLAGUE_SPREADER): ProjectileBehavior(linger=_linger_default, post_hit_creature=_post_hit_plague_spreader),
|
|
612
|
+
int(ProjectileTypeId.RAINBOW_GUN): _DEFAULT_BEHAVIOR,
|
|
613
|
+
int(ProjectileTypeId.FIRE_BULLETS): _DEFAULT_BEHAVIOR,
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
139
617
|
class ProjectilePool:
|
|
140
618
|
def __init__(self, *, size: int = MAIN_PROJECTILE_POOL_SIZE) -> None:
|
|
141
619
|
self._entries = [Projectile() for _ in range(size)]
|
|
@@ -299,195 +777,18 @@ class ProjectilePool:
|
|
|
299
777
|
def _damage_type_for() -> int:
|
|
300
778
|
return 1
|
|
301
779
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
ring_scale = 1.2
|
|
315
|
-
ring_strength = 0.4
|
|
316
|
-
burst_scale = 1.2
|
|
317
|
-
elif type_id == int(ProjectileTypeId.ION_CANNON):
|
|
318
|
-
ring_scale = 1.0
|
|
319
|
-
ring_strength = 1.0
|
|
320
|
-
burst_scale = 2.2
|
|
321
|
-
if isinstance(sfx_queue, list):
|
|
322
|
-
sfx_queue.append("sfx_shockwave")
|
|
323
|
-
else:
|
|
324
|
-
return
|
|
325
|
-
|
|
326
|
-
detail = int(detail_preset)
|
|
327
|
-
|
|
328
|
-
# Port of `FUN_0042f270(pos, ring_scale, ring_strength)`: ring burst (effect_id=1).
|
|
329
|
-
effects.spawn(
|
|
330
|
-
effect_id=1,
|
|
331
|
-
pos_x=float(pos_x),
|
|
332
|
-
pos_y=float(pos_y),
|
|
333
|
-
vel_x=0.0,
|
|
334
|
-
vel_y=0.0,
|
|
335
|
-
rotation=0.0,
|
|
336
|
-
scale=1.0,
|
|
337
|
-
half_width=4.0,
|
|
338
|
-
half_height=4.0,
|
|
339
|
-
age=0.0,
|
|
340
|
-
lifetime=float(ring_strength) * 0.8,
|
|
341
|
-
flags=0x19,
|
|
342
|
-
color_r=0.6,
|
|
343
|
-
color_g=0.6,
|
|
344
|
-
color_b=0.9,
|
|
345
|
-
color_a=1.0,
|
|
346
|
-
rotation_step=0.0,
|
|
347
|
-
scale_step=float(ring_scale) * 45.0,
|
|
348
|
-
detail_preset=detail,
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
# Port of `FUN_0042f540(pos, burst_scale)`: burst cloud (effect_id=0).
|
|
352
|
-
burst = float(burst_scale) * 0.8
|
|
353
|
-
lifetime = min(burst * 0.7, 1.1)
|
|
354
|
-
half = burst * 32.0
|
|
355
|
-
count = int(half)
|
|
356
|
-
if detail < 3:
|
|
357
|
-
count //= 2
|
|
358
|
-
|
|
359
|
-
for _ in range(max(0, count)):
|
|
360
|
-
rotation = float(int(rng()) & 0x7F) * 0.049087387
|
|
361
|
-
vel_x = float((int(rng()) & 0x7F) - 0x40) * burst * 1.4
|
|
362
|
-
vel_y = float((int(rng()) & 0x7F) - 0x40) * burst * 1.4
|
|
363
|
-
scale_step = (float(int(rng()) % 100) * 0.01 + 0.1) * burst
|
|
364
|
-
effects.spawn(
|
|
365
|
-
effect_id=0,
|
|
366
|
-
pos_x=float(pos_x),
|
|
367
|
-
pos_y=float(pos_y),
|
|
368
|
-
vel_x=vel_x,
|
|
369
|
-
vel_y=vel_y,
|
|
370
|
-
rotation=rotation,
|
|
371
|
-
scale=1.0,
|
|
372
|
-
half_width=half,
|
|
373
|
-
half_height=half,
|
|
374
|
-
age=0.0,
|
|
375
|
-
lifetime=float(lifetime),
|
|
376
|
-
flags=0x1D,
|
|
377
|
-
color_r=0.4,
|
|
378
|
-
color_g=0.5,
|
|
379
|
-
color_b=1.0,
|
|
380
|
-
color_a=0.5,
|
|
381
|
-
rotation_step=0.0,
|
|
382
|
-
scale_step=scale_step,
|
|
383
|
-
detail_preset=detail,
|
|
384
|
-
)
|
|
385
|
-
|
|
386
|
-
def _spawn_plasma_cannon_hit_effects(pos_x: float, pos_y: float) -> None:
|
|
387
|
-
"""Port of `projectile_update` Plasma Cannon hit extras.
|
|
388
|
-
|
|
389
|
-
Native does:
|
|
390
|
-
- `sfx_play_panned(sfx_explosion_medium)`
|
|
391
|
-
- `sfx_play_panned(sfx_shockwave)`
|
|
392
|
-
- `FUN_0042f330(pos, 1.5, 1.0)`
|
|
393
|
-
- `FUN_0042f330(pos, 1.0, 1.0)`
|
|
394
|
-
"""
|
|
395
|
-
|
|
396
|
-
if effects is None or not hasattr(effects, "spawn"):
|
|
397
|
-
return
|
|
398
|
-
|
|
399
|
-
if isinstance(sfx_queue, list):
|
|
400
|
-
sfx_queue.append("sfx_explosion_medium")
|
|
401
|
-
sfx_queue.append("sfx_shockwave")
|
|
402
|
-
|
|
403
|
-
detail = int(detail_preset)
|
|
404
|
-
|
|
405
|
-
def _spawn_ring(*, scale: float) -> None:
|
|
406
|
-
effects.spawn(
|
|
407
|
-
effect_id=1,
|
|
408
|
-
pos_x=float(pos_x),
|
|
409
|
-
pos_y=float(pos_y),
|
|
410
|
-
vel_x=0.0,
|
|
411
|
-
vel_y=0.0,
|
|
412
|
-
rotation=0.0,
|
|
413
|
-
scale=1.0,
|
|
414
|
-
half_width=4.0,
|
|
415
|
-
half_height=4.0,
|
|
416
|
-
age=0.1,
|
|
417
|
-
lifetime=1.0,
|
|
418
|
-
flags=0x19,
|
|
419
|
-
color_r=0.9,
|
|
420
|
-
color_g=0.6,
|
|
421
|
-
color_b=0.3,
|
|
422
|
-
color_a=1.0,
|
|
423
|
-
rotation_step=0.0,
|
|
424
|
-
scale_step=float(scale) * 45.0,
|
|
425
|
-
detail_preset=detail,
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
_spawn_ring(scale=1.5)
|
|
429
|
-
_spawn_ring(scale=1.0)
|
|
430
|
-
|
|
431
|
-
def _spawn_splitter_hit_effects(pos_x: float, pos_y: float) -> None:
|
|
432
|
-
"""Port of `FUN_0042f3f0(pos, 26.0, 3)` from the Splitter Gun hit branch."""
|
|
433
|
-
|
|
434
|
-
if effects is None or not hasattr(effects, "spawn"):
|
|
435
|
-
return
|
|
436
|
-
|
|
437
|
-
detail = int(detail_preset)
|
|
438
|
-
for _ in range(3):
|
|
439
|
-
angle = float(int(rng()) & 0x1FF) * (math.tau / 512.0)
|
|
440
|
-
radius = float(int(rng()) % 26)
|
|
441
|
-
jitter_age = -float(int(rng()) & 0xFF) * 0.0012
|
|
442
|
-
lifetime = 0.1 - jitter_age
|
|
443
|
-
|
|
444
|
-
effects.spawn(
|
|
445
|
-
effect_id=0,
|
|
446
|
-
pos_x=float(pos_x) + math.cos(angle) * radius,
|
|
447
|
-
pos_y=float(pos_y) + math.sin(angle) * radius,
|
|
448
|
-
vel_x=0.0,
|
|
449
|
-
vel_y=0.0,
|
|
450
|
-
rotation=0.0,
|
|
451
|
-
scale=1.0,
|
|
452
|
-
half_width=4.0,
|
|
453
|
-
half_height=4.0,
|
|
454
|
-
age=jitter_age,
|
|
455
|
-
lifetime=lifetime,
|
|
456
|
-
flags=0x19,
|
|
457
|
-
color_r=1.0,
|
|
458
|
-
color_g=0.9,
|
|
459
|
-
color_b=0.1,
|
|
460
|
-
color_a=1.0,
|
|
461
|
-
rotation_step=0.0,
|
|
462
|
-
scale_step=55.0,
|
|
463
|
-
detail_preset=detail,
|
|
464
|
-
)
|
|
465
|
-
|
|
466
|
-
def _apply_damage_to_creature(
|
|
467
|
-
creature_index: int,
|
|
468
|
-
damage: float,
|
|
469
|
-
*,
|
|
470
|
-
damage_type: int,
|
|
471
|
-
impulse_x: float,
|
|
472
|
-
impulse_y: float,
|
|
473
|
-
owner_id: int,
|
|
474
|
-
) -> None:
|
|
475
|
-
if damage <= 0.0:
|
|
476
|
-
return
|
|
477
|
-
idx = int(creature_index)
|
|
478
|
-
if not (0 <= idx < len(creatures)):
|
|
479
|
-
return
|
|
480
|
-
if apply_creature_damage is not None:
|
|
481
|
-
apply_creature_damage(
|
|
482
|
-
idx,
|
|
483
|
-
float(damage),
|
|
484
|
-
int(damage_type),
|
|
485
|
-
float(impulse_x),
|
|
486
|
-
float(impulse_y),
|
|
487
|
-
int(owner_id),
|
|
488
|
-
)
|
|
489
|
-
else:
|
|
490
|
-
creatures[idx].hp -= float(damage)
|
|
780
|
+
ctx = _ProjectileUpdateCtx(
|
|
781
|
+
pool=self,
|
|
782
|
+
creatures=creatures,
|
|
783
|
+
dt=float(dt),
|
|
784
|
+
ion_scale=float(ion_scale),
|
|
785
|
+
detail_preset=int(detail_preset),
|
|
786
|
+
rng=rng,
|
|
787
|
+
runtime_state=runtime_state,
|
|
788
|
+
effects=effects,
|
|
789
|
+
sfx_queue=sfx_queue,
|
|
790
|
+
apply_creature_damage=apply_creature_damage,
|
|
791
|
+
)
|
|
491
792
|
|
|
492
793
|
def _reset_shock_chain_if_owner(index: int) -> None:
|
|
493
794
|
if runtime_state is None:
|
|
@@ -517,7 +818,7 @@ class ProjectilePool:
|
|
|
517
818
|
continue
|
|
518
819
|
if creature.hp <= 0.0:
|
|
519
820
|
continue
|
|
520
|
-
d =
|
|
821
|
+
d = distance_sq(origin.x, origin.y, creature.x, creature.y)
|
|
521
822
|
if d > max_dist * max_dist:
|
|
522
823
|
continue
|
|
523
824
|
if best_idx == -1 or d < best_dist:
|
|
@@ -549,6 +850,7 @@ class ProjectilePool:
|
|
|
549
850
|
for proj_index, proj in enumerate(self._entries):
|
|
550
851
|
if not proj.active:
|
|
551
852
|
continue
|
|
853
|
+
behavior = PROJECTILE_BEHAVIOR_BY_TYPE_ID.get(int(proj.type_id), _DEFAULT_BEHAVIOR)
|
|
552
854
|
|
|
553
855
|
if proj.life_timer <= 0.0:
|
|
554
856
|
_reset_shock_chain_if_owner(proj_index)
|
|
@@ -562,51 +864,7 @@ class ProjectilePool:
|
|
|
562
864
|
_try_spawn_shock_chain_link(proj_index, pending_hit - 1)
|
|
563
865
|
|
|
564
866
|
if proj.life_timer < 0.4:
|
|
565
|
-
|
|
566
|
-
if type_id in (ProjectileTypeId.ION_RIFLE, ProjectileTypeId.ION_MINIGUN):
|
|
567
|
-
proj.life_timer -= dt
|
|
568
|
-
if type_id == ProjectileTypeId.ION_RIFLE:
|
|
569
|
-
damage = dt * 100.0
|
|
570
|
-
radius = ion_scale * 88.0
|
|
571
|
-
else:
|
|
572
|
-
damage = dt * 40.0
|
|
573
|
-
radius = ion_scale * 60.0
|
|
574
|
-
for creature_idx, creature in enumerate(creatures):
|
|
575
|
-
if creature.hp <= 0.0:
|
|
576
|
-
continue
|
|
577
|
-
creature_radius = _hit_radius_for(creature)
|
|
578
|
-
hit_r = radius + creature_radius
|
|
579
|
-
if _distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
580
|
-
_apply_damage_to_creature(
|
|
581
|
-
creature_idx,
|
|
582
|
-
damage,
|
|
583
|
-
damage_type=7,
|
|
584
|
-
impulse_x=0.0,
|
|
585
|
-
impulse_y=0.0,
|
|
586
|
-
owner_id=int(proj.owner_id),
|
|
587
|
-
)
|
|
588
|
-
elif type_id == ProjectileTypeId.ION_CANNON:
|
|
589
|
-
proj.life_timer -= dt * 0.7
|
|
590
|
-
damage = dt * 300.0
|
|
591
|
-
radius = ion_scale * 128.0
|
|
592
|
-
for creature_idx, creature in enumerate(creatures):
|
|
593
|
-
if creature.hp <= 0.0:
|
|
594
|
-
continue
|
|
595
|
-
creature_radius = _hit_radius_for(creature)
|
|
596
|
-
hit_r = radius + creature_radius
|
|
597
|
-
if _distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
598
|
-
_apply_damage_to_creature(
|
|
599
|
-
creature_idx,
|
|
600
|
-
damage,
|
|
601
|
-
damage_type=7,
|
|
602
|
-
impulse_x=0.0,
|
|
603
|
-
impulse_y=0.0,
|
|
604
|
-
owner_id=int(proj.owner_id),
|
|
605
|
-
)
|
|
606
|
-
elif type_id == ProjectileTypeId.GAUSS_GUN:
|
|
607
|
-
proj.life_timer -= dt * 0.1
|
|
608
|
-
else:
|
|
609
|
-
proj.life_timer -= dt
|
|
867
|
+
behavior.linger(ctx, proj)
|
|
610
868
|
|
|
611
869
|
if proj.life_timer <= 0.0:
|
|
612
870
|
proj.active = False
|
|
@@ -655,7 +913,7 @@ class ProjectilePool:
|
|
|
655
913
|
continue
|
|
656
914
|
creature_radius = _hit_radius_for(creature)
|
|
657
915
|
hit_r = proj.hit_radius + creature_radius
|
|
658
|
-
if
|
|
916
|
+
if distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
659
917
|
hit_idx = idx
|
|
660
918
|
break
|
|
661
919
|
|
|
@@ -673,7 +931,7 @@ class ProjectilePool:
|
|
|
673
931
|
player_radius = _hit_radius_for(player)
|
|
674
932
|
hit_r = proj.hit_radius + player_radius
|
|
675
933
|
if (
|
|
676
|
-
|
|
934
|
+
distance_sq(proj.pos_x, proj.pos_y, player.pos_x, player.pos_y)
|
|
677
935
|
<= hit_r * hit_r
|
|
678
936
|
):
|
|
679
937
|
hit_player_idx = idx
|
|
@@ -721,30 +979,18 @@ class ProjectilePool:
|
|
|
721
979
|
type_id = proj.type_id
|
|
722
980
|
creature = creatures[hit_idx]
|
|
723
981
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
base_damage=proj.base_damage,
|
|
737
|
-
hits_players=proj.hits_players,
|
|
738
|
-
)
|
|
739
|
-
self.spawn(
|
|
740
|
-
pos_x=proj.pos_x,
|
|
741
|
-
pos_y=proj.pos_y,
|
|
742
|
-
angle=proj.angle + 1.0471976,
|
|
743
|
-
type_id=ProjectileTypeId.SPLITTER_GUN,
|
|
744
|
-
owner_id=hit_idx,
|
|
745
|
-
base_damage=proj.base_damage,
|
|
746
|
-
hits_players=proj.hits_players,
|
|
747
|
-
)
|
|
982
|
+
perk_ctx = _ProjectileHitPerkCtx(
|
|
983
|
+
proj=proj,
|
|
984
|
+
creature=creature,
|
|
985
|
+
rng=rng,
|
|
986
|
+
owner_perk_active=_owner_perk_active,
|
|
987
|
+
poison_idx=poison_idx,
|
|
988
|
+
)
|
|
989
|
+
for hook in _PROJECTILE_HIT_PERK_HOOKS:
|
|
990
|
+
hook(perk_ctx)
|
|
991
|
+
|
|
992
|
+
if behavior.pre_hit_creature is not None:
|
|
993
|
+
behavior.pre_hit_creature(ctx, proj, int(hit_idx))
|
|
748
994
|
|
|
749
995
|
shots_hit = getattr(runtime_state, "shots_hit", None) if runtime_state is not None else None
|
|
750
996
|
if isinstance(shots_hit, list):
|
|
@@ -774,50 +1020,19 @@ class ProjectilePool:
|
|
|
774
1020
|
if dist < 50.0:
|
|
775
1021
|
dist = 50.0
|
|
776
1022
|
|
|
777
|
-
if
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
else proj.base_damage
|
|
1023
|
+
if behavior.post_hit_creature is not None:
|
|
1024
|
+
behavior.post_hit_creature(
|
|
1025
|
+
ctx,
|
|
1026
|
+
_ProjectileHitInfo(
|
|
1027
|
+
proj_index=int(proj_index),
|
|
1028
|
+
proj=proj,
|
|
1029
|
+
hit_idx=int(hit_idx),
|
|
1030
|
+
move_dx=float(move_dx),
|
|
1031
|
+
move_dy=float(move_dy),
|
|
1032
|
+
target_x=float(target_x),
|
|
1033
|
+
target_y=float(target_y),
|
|
1034
|
+
),
|
|
790
1035
|
)
|
|
791
|
-
for ring_idx in range(12):
|
|
792
|
-
ring_angle = float(ring_idx) * (math.pi / 6.0)
|
|
793
|
-
self.spawn(
|
|
794
|
-
pos_x=proj.pos_x + math.cos(ring_angle) * ring_radius,
|
|
795
|
-
pos_y=proj.pos_y + math.sin(ring_angle) * ring_radius,
|
|
796
|
-
angle=ring_angle,
|
|
797
|
-
type_id=ProjectileTypeId.PLASMA_RIFLE,
|
|
798
|
-
owner_id=-100,
|
|
799
|
-
base_damage=plasma_meta,
|
|
800
|
-
)
|
|
801
|
-
_spawn_plasma_cannon_hit_effects(proj.pos_x, proj.pos_y)
|
|
802
|
-
elif type_id == ProjectileTypeId.SHRINKIFIER:
|
|
803
|
-
if hasattr(creature, "size"):
|
|
804
|
-
new_size = float(getattr(creature, "size", 50.0) or 50.0) * 0.65
|
|
805
|
-
setattr(creature, "size", new_size)
|
|
806
|
-
if new_size < 16.0:
|
|
807
|
-
_apply_damage_to_creature(
|
|
808
|
-
hit_idx,
|
|
809
|
-
float(creature.hp) + 1.0,
|
|
810
|
-
damage_type=_damage_type_for(),
|
|
811
|
-
impulse_x=0.0,
|
|
812
|
-
impulse_y=0.0,
|
|
813
|
-
owner_id=int(proj.owner_id),
|
|
814
|
-
)
|
|
815
|
-
proj.life_timer = 0.25
|
|
816
|
-
elif type_id == ProjectileTypeId.PULSE_GUN:
|
|
817
|
-
creature.x += move_dx * 3.0
|
|
818
|
-
creature.y += move_dy * 3.0
|
|
819
|
-
elif type_id == ProjectileTypeId.PLAGUE_SPREADER and hasattr(creature, "collision_flag"):
|
|
820
|
-
setattr(creature, "collision_flag", 1)
|
|
821
1036
|
|
|
822
1037
|
damage_scale = _damage_scale(type_id)
|
|
823
1038
|
damage_amount = ((100.0 / dist) * damage_scale * 30.0 + 10.0) * 0.95
|
|
@@ -830,24 +1045,28 @@ class ProjectilePool:
|
|
|
830
1045
|
damage_type = _damage_type_for()
|
|
831
1046
|
if remaining <= 0.0:
|
|
832
1047
|
_apply_damage_to_creature(
|
|
833
|
-
|
|
834
|
-
|
|
1048
|
+
creatures,
|
|
1049
|
+
int(hit_idx),
|
|
1050
|
+
float(damage_amount),
|
|
835
1051
|
damage_type=damage_type,
|
|
836
1052
|
impulse_x=impulse_x,
|
|
837
1053
|
impulse_y=impulse_y,
|
|
838
1054
|
owner_id=int(proj.owner_id),
|
|
1055
|
+
apply_creature_damage=apply_creature_damage,
|
|
839
1056
|
)
|
|
840
1057
|
if proj.life_timer != 0.25:
|
|
841
1058
|
proj.life_timer = 0.25
|
|
842
1059
|
else:
|
|
843
1060
|
hp_before = float(creature.hp)
|
|
844
1061
|
_apply_damage_to_creature(
|
|
845
|
-
|
|
846
|
-
|
|
1062
|
+
creatures,
|
|
1063
|
+
int(hit_idx),
|
|
1064
|
+
float(remaining),
|
|
847
1065
|
damage_type=damage_type,
|
|
848
1066
|
impulse_x=impulse_x,
|
|
849
1067
|
impulse_y=impulse_y,
|
|
850
1068
|
owner_id=int(proj.owner_id),
|
|
1069
|
+
apply_creature_damage=apply_creature_damage,
|
|
851
1070
|
)
|
|
852
1071
|
proj.damage_pool -= hp_before
|
|
853
1072
|
|
|
@@ -906,7 +1125,7 @@ class ProjectilePool:
|
|
|
906
1125
|
continue
|
|
907
1126
|
creature_radius = _hit_radius_for(creature)
|
|
908
1127
|
hit_r = radius + creature_radius
|
|
909
|
-
if
|
|
1128
|
+
if distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
910
1129
|
creature.hp -= damage
|
|
911
1130
|
elif proj.type_id == ProjectileTypeId.ION_MINIGUN:
|
|
912
1131
|
damage = dt * 40.0
|
|
@@ -916,7 +1135,7 @@ class ProjectilePool:
|
|
|
916
1135
|
continue
|
|
917
1136
|
creature_radius = _hit_radius_for(creature)
|
|
918
1137
|
hit_r = radius + creature_radius
|
|
919
|
-
if
|
|
1138
|
+
if distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
920
1139
|
creature.hp -= damage
|
|
921
1140
|
proj.life_timer -= dt
|
|
922
1141
|
if proj.life_timer <= 0.0:
|
|
@@ -946,7 +1165,7 @@ class ProjectilePool:
|
|
|
946
1165
|
continue
|
|
947
1166
|
creature_radius = _hit_radius_for(creature)
|
|
948
1167
|
hit_r = proj.hit_radius + creature_radius
|
|
949
|
-
if
|
|
1168
|
+
if distance_sq(proj.pos_x, proj.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
950
1169
|
hit_idx = idx
|
|
951
1170
|
break
|
|
952
1171
|
if hit_idx is None:
|
|
@@ -1099,7 +1318,7 @@ class SecondaryProjectilePool:
|
|
|
1099
1318
|
continue
|
|
1100
1319
|
creature_radius = _hit_radius_for(creature)
|
|
1101
1320
|
hit_r = radius + creature_radius
|
|
1102
|
-
if
|
|
1321
|
+
if distance_sq(entry.pos_x, entry.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
1103
1322
|
_apply_damage_to_creature(creature_idx, damage, owner_id=int(entry.owner_id))
|
|
1104
1323
|
continue
|
|
1105
1324
|
|
|
@@ -1133,7 +1352,7 @@ class SecondaryProjectilePool:
|
|
|
1133
1352
|
for idx, creature in enumerate(creatures):
|
|
1134
1353
|
if creature.hp <= 0.0:
|
|
1135
1354
|
continue
|
|
1136
|
-
d =
|
|
1355
|
+
d = distance_sq(entry.pos_x, entry.pos_y, creature.x, creature.y)
|
|
1137
1356
|
if best_idx == -1 or d < best_dist:
|
|
1138
1357
|
best_idx = idx
|
|
1139
1358
|
best_dist = d
|
|
@@ -1182,7 +1401,7 @@ class SecondaryProjectilePool:
|
|
|
1182
1401
|
continue
|
|
1183
1402
|
creature_radius = _hit_radius_for(creature)
|
|
1184
1403
|
hit_r = 8.0 + creature_radius
|
|
1185
|
-
if
|
|
1404
|
+
if distance_sq(entry.pos_x, entry.pos_y, creature.x, creature.y) <= hit_r * hit_r:
|
|
1186
1405
|
hit_idx = idx
|
|
1187
1406
|
break
|
|
1188
1407
|
if hit_idx is not None:
|