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/creatures/runtime.py
CHANGED
|
@@ -14,6 +14,7 @@ from dataclasses import dataclass, replace
|
|
|
14
14
|
import math
|
|
15
15
|
from typing import Callable, Sequence
|
|
16
16
|
|
|
17
|
+
from grim.math import clamp, distance_sq
|
|
17
18
|
from grim.rand import Crand
|
|
18
19
|
from ..effects import FxQueue, FxQueueRotated
|
|
19
20
|
from ..gameplay import GameplayState, PlayerState, award_experience, perk_active
|
|
@@ -62,14 +63,6 @@ CREATURE_CORPSE_DESPAWN_HITBOX = -10.0
|
|
|
62
63
|
CREATURE_DEATH_SLIDE_SCALE = 9.0
|
|
63
64
|
|
|
64
65
|
|
|
65
|
-
def _clamp(value: float, lo: float, hi: float) -> float:
|
|
66
|
-
if value < lo:
|
|
67
|
-
return lo
|
|
68
|
-
if value > hi:
|
|
69
|
-
return hi
|
|
70
|
-
return value
|
|
71
|
-
|
|
72
|
-
|
|
73
66
|
def _wrap_angle(angle: float) -> float:
|
|
74
67
|
return (angle + math.pi) % math.tau - math.pi
|
|
75
68
|
|
|
@@ -85,12 +78,6 @@ def _angle_approach(current: float, target: float, rate: float, dt: float) -> fl
|
|
|
85
78
|
return _wrap_angle(current)
|
|
86
79
|
|
|
87
80
|
|
|
88
|
-
def _distance_sq(x0: float, y0: float, x1: float, y1: float) -> float:
|
|
89
|
-
dx = x1 - x0
|
|
90
|
-
dy = y1 - y0
|
|
91
|
-
return dx * dx + dy * dy
|
|
92
|
-
|
|
93
|
-
|
|
94
81
|
def _owner_id_to_player_index(owner_id: int) -> int | None:
|
|
95
82
|
# Native uses `-1/-2/-3/-4` for player indices and `-100` as a player-owned sentinel.
|
|
96
83
|
if owner_id == -100:
|
|
@@ -136,8 +123,8 @@ class CreatureState:
|
|
|
136
123
|
attack_cooldown: float = 0.0
|
|
137
124
|
reward_value: float = 0.0
|
|
138
125
|
|
|
139
|
-
#
|
|
140
|
-
|
|
126
|
+
# Plaguebearer infection state (native: `collision_flag` byte).
|
|
127
|
+
plague_infected: bool = False
|
|
141
128
|
collision_timer: float = CONTACT_DAMAGE_PERIOD
|
|
142
129
|
hitbox_size: float = CREATURE_HITBOX_ALIVE
|
|
143
130
|
|
|
@@ -174,6 +161,182 @@ class CreatureUpdateResult:
|
|
|
174
161
|
sfx: tuple[str, ...] = ()
|
|
175
162
|
|
|
176
163
|
|
|
164
|
+
@dataclass(slots=True)
|
|
165
|
+
class _CreatureInteractionCtx:
|
|
166
|
+
pool: CreaturePool
|
|
167
|
+
creature_index: int
|
|
168
|
+
creature: CreatureState
|
|
169
|
+
state: GameplayState
|
|
170
|
+
players: list[PlayerState]
|
|
171
|
+
player: PlayerState
|
|
172
|
+
dt: float
|
|
173
|
+
rand: Callable[[], int]
|
|
174
|
+
detail_preset: int
|
|
175
|
+
world_width: float
|
|
176
|
+
world_height: float
|
|
177
|
+
fx_queue: FxQueue | None
|
|
178
|
+
fx_queue_rotated: FxQueueRotated | None
|
|
179
|
+
deaths: list[CreatureDeath]
|
|
180
|
+
sfx: list[str]
|
|
181
|
+
skip_creature: bool = False
|
|
182
|
+
contact_dist_sq: float = 0.0
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
_CreatureInteractionStep = Callable[[_CreatureInteractionCtx], None]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _creature_interaction_plaguebearer_spread(ctx: _CreatureInteractionCtx) -> None:
|
|
189
|
+
if ctx.players and perk_active(ctx.players[0], PerkId.PLAGUEBEARER) and int(ctx.state.plaguebearer_infection_count) < 0x3C:
|
|
190
|
+
ctx.pool._plaguebearer_spread_infection(ctx.creature_index)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _creature_interaction_energizer_eat(ctx: _CreatureInteractionCtx) -> None:
|
|
194
|
+
creature = ctx.creature
|
|
195
|
+
if float(ctx.state.bonuses.energizer) <= 0.0:
|
|
196
|
+
return
|
|
197
|
+
if float(creature.max_hp) >= 380.0:
|
|
198
|
+
return
|
|
199
|
+
if float(ctx.player.health) <= 0.0:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
eat_dist_sq = distance_sq(creature.x, creature.y, ctx.player.pos_x, ctx.player.pos_y)
|
|
203
|
+
if eat_dist_sq >= 20.0 * 20.0:
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
creature.x = clamp(creature.x - creature.vel_x * ctx.dt, 0.0, float(ctx.world_width))
|
|
207
|
+
creature.y = clamp(creature.y - creature.vel_y * ctx.dt, 0.0, float(ctx.world_height))
|
|
208
|
+
|
|
209
|
+
ctx.state.effects.spawn_burst(
|
|
210
|
+
pos_x=float(creature.x),
|
|
211
|
+
pos_y=float(creature.y),
|
|
212
|
+
count=6,
|
|
213
|
+
rand=ctx.rand,
|
|
214
|
+
detail_preset=int(ctx.detail_preset),
|
|
215
|
+
)
|
|
216
|
+
ctx.sfx.append("sfx_ui_bonus")
|
|
217
|
+
|
|
218
|
+
prev_guard = bool(ctx.state.bonus_spawn_guard)
|
|
219
|
+
ctx.state.bonus_spawn_guard = True
|
|
220
|
+
creature.last_hit_owner_id = -1 - int(ctx.player.index)
|
|
221
|
+
ctx.deaths.append(
|
|
222
|
+
ctx.pool.handle_death(
|
|
223
|
+
ctx.creature_index,
|
|
224
|
+
state=ctx.state,
|
|
225
|
+
players=ctx.players,
|
|
226
|
+
rand=ctx.rand,
|
|
227
|
+
detail_preset=int(ctx.detail_preset),
|
|
228
|
+
world_width=float(ctx.world_width),
|
|
229
|
+
world_height=float(ctx.world_height),
|
|
230
|
+
fx_queue=ctx.fx_queue,
|
|
231
|
+
keep_corpse=False,
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
ctx.state.bonus_spawn_guard = prev_guard
|
|
235
|
+
ctx.skip_creature = True
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _creature_interaction_contact_damage(ctx: _CreatureInteractionCtx) -> None:
|
|
239
|
+
creature = ctx.creature
|
|
240
|
+
if float(ctx.state.bonuses.energizer) > 0.0:
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
ctx.contact_dist_sq = distance_sq(creature.x, creature.y, ctx.player.pos_x, ctx.player.pos_y)
|
|
244
|
+
contact_r = (float(creature.size) + float(ctx.player.size)) * 0.25 + 20.0
|
|
245
|
+
in_contact = ctx.contact_dist_sq <= contact_r * contact_r
|
|
246
|
+
if not in_contact:
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
creature.collision_timer -= ctx.dt
|
|
250
|
+
if creature.collision_timer >= 0.0:
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
creature.collision_timer += CONTACT_DAMAGE_PERIOD
|
|
254
|
+
|
|
255
|
+
mr_melee_killed = False
|
|
256
|
+
mr_melee_death_start_needed = False
|
|
257
|
+
if perk_active(ctx.player, PerkId.MR_MELEE):
|
|
258
|
+
mr_melee_death_start_needed = creature.hp > 0.0 and creature.hitbox_size == CREATURE_HITBOX_ALIVE
|
|
259
|
+
|
|
260
|
+
from .damage import creature_apply_damage
|
|
261
|
+
|
|
262
|
+
mr_melee_killed = creature_apply_damage(
|
|
263
|
+
creature,
|
|
264
|
+
damage_amount=25.0,
|
|
265
|
+
damage_type=2,
|
|
266
|
+
impulse_x=0.0,
|
|
267
|
+
impulse_y=0.0,
|
|
268
|
+
owner_id=-1 - int(ctx.player.index),
|
|
269
|
+
dt=ctx.dt,
|
|
270
|
+
players=ctx.players,
|
|
271
|
+
rand=ctx.rand,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if float(ctx.player.shield_timer) <= 0.0:
|
|
275
|
+
if perk_active(ctx.player, PerkId.TOXIC_AVENGER):
|
|
276
|
+
creature.flags |= CreatureFlags.SELF_DAMAGE_TICK | CreatureFlags.SELF_DAMAGE_TICK_STRONG
|
|
277
|
+
elif perk_active(ctx.player, PerkId.VEINS_OF_POISON):
|
|
278
|
+
creature.flags |= CreatureFlags.SELF_DAMAGE_TICK
|
|
279
|
+
|
|
280
|
+
player_take_damage(ctx.state, ctx.player, float(creature.contact_damage), dt=ctx.dt, rand=ctx.rand)
|
|
281
|
+
|
|
282
|
+
if ctx.fx_queue is not None:
|
|
283
|
+
dx = float(ctx.player.pos_x) - float(creature.x)
|
|
284
|
+
dy = float(ctx.player.pos_y) - float(creature.y)
|
|
285
|
+
dist = math.hypot(dx, dy)
|
|
286
|
+
if dist > 1e-9:
|
|
287
|
+
dx /= dist
|
|
288
|
+
dy /= dist
|
|
289
|
+
else:
|
|
290
|
+
dx = 0.0
|
|
291
|
+
dy = 0.0
|
|
292
|
+
ctx.fx_queue.add_random(
|
|
293
|
+
pos_x=float(ctx.player.pos_x) + dx * 3.0,
|
|
294
|
+
pos_y=float(ctx.player.pos_y) + dy * 3.0,
|
|
295
|
+
rand=ctx.rand,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if mr_melee_killed and mr_melee_death_start_needed:
|
|
299
|
+
ctx.deaths.append(
|
|
300
|
+
ctx.pool.handle_death(
|
|
301
|
+
ctx.creature_index,
|
|
302
|
+
state=ctx.state,
|
|
303
|
+
players=ctx.players,
|
|
304
|
+
rand=ctx.rand,
|
|
305
|
+
detail_preset=int(ctx.detail_preset),
|
|
306
|
+
world_width=float(ctx.world_width),
|
|
307
|
+
world_height=float(ctx.world_height),
|
|
308
|
+
fx_queue=ctx.fx_queue,
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
if creature.active:
|
|
312
|
+
ctx.pool._tick_dead(
|
|
313
|
+
creature,
|
|
314
|
+
dt=ctx.dt,
|
|
315
|
+
world_width=float(ctx.world_width),
|
|
316
|
+
world_height=float(ctx.world_height),
|
|
317
|
+
fx_queue_rotated=ctx.fx_queue_rotated,
|
|
318
|
+
)
|
|
319
|
+
ctx.skip_creature = True
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _creature_interaction_plaguebearer_contact_flag(ctx: _CreatureInteractionCtx) -> None:
|
|
323
|
+
if float(ctx.state.bonuses.energizer) > 0.0:
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
creature = ctx.creature
|
|
327
|
+
if bool(ctx.player.plaguebearer_active) and float(creature.hp) < 150.0 and int(ctx.state.plaguebearer_infection_count) < 0x32:
|
|
328
|
+
if ctx.contact_dist_sq < 30.0 * 30.0:
|
|
329
|
+
creature.plague_infected = True
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
_CREATURE_INTERACTION_STEPS: tuple[_CreatureInteractionStep, ...] = (
|
|
333
|
+
_creature_interaction_plaguebearer_spread,
|
|
334
|
+
_creature_interaction_energizer_eat,
|
|
335
|
+
_creature_interaction_contact_damage,
|
|
336
|
+
_creature_interaction_plaguebearer_contact_flag,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
177
340
|
class CreaturePool:
|
|
178
341
|
def __init__(self, *, size: int = CREATURE_POOL_SIZE, env: SpawnEnv | None = None) -> None:
|
|
179
342
|
self._entries = [CreatureState() for _ in range(int(size))]
|
|
@@ -211,10 +374,10 @@ class CreaturePool:
|
|
|
211
374
|
continue
|
|
212
375
|
|
|
213
376
|
if math.hypot(float(creature.x) - float(origin.x), float(creature.y) - float(origin.y)) < 45.0:
|
|
214
|
-
if creature.
|
|
215
|
-
origin.
|
|
216
|
-
if origin.
|
|
217
|
-
creature.
|
|
377
|
+
if creature.plague_infected and float(origin.hp) < 150.0:
|
|
378
|
+
origin.plague_infected = True
|
|
379
|
+
if origin.plague_infected and float(creature.hp) < 150.0:
|
|
380
|
+
creature.plague_infected = True
|
|
218
381
|
return
|
|
219
382
|
|
|
220
383
|
def _alloc_slot(self, *, rand: Callable[[], int] | None = None) -> int:
|
|
@@ -403,11 +566,36 @@ class CreaturePool:
|
|
|
403
566
|
creature.vel_y = 0.0
|
|
404
567
|
continue
|
|
405
568
|
|
|
569
|
+
poison_killed = False
|
|
406
570
|
if creature.flags & CreatureFlags.SELF_DAMAGE_TICK_STRONG:
|
|
407
|
-
|
|
571
|
+
from .damage import creature_apply_damage
|
|
572
|
+
|
|
573
|
+
poison_killed = creature_apply_damage(
|
|
574
|
+
creature,
|
|
575
|
+
damage_amount=dt * 180.0,
|
|
576
|
+
damage_type=0,
|
|
577
|
+
impulse_x=0.0,
|
|
578
|
+
impulse_y=0.0,
|
|
579
|
+
owner_id=int(creature.last_hit_owner_id),
|
|
580
|
+
dt=dt,
|
|
581
|
+
players=players,
|
|
582
|
+
rand=rand,
|
|
583
|
+
)
|
|
408
584
|
elif creature.flags & CreatureFlags.SELF_DAMAGE_TICK:
|
|
409
|
-
|
|
410
|
-
|
|
585
|
+
from .damage import creature_apply_damage
|
|
586
|
+
|
|
587
|
+
poison_killed = creature_apply_damage(
|
|
588
|
+
creature,
|
|
589
|
+
damage_amount=dt * 60.0,
|
|
590
|
+
damage_type=0,
|
|
591
|
+
impulse_x=0.0,
|
|
592
|
+
impulse_y=0.0,
|
|
593
|
+
owner_id=int(creature.last_hit_owner_id),
|
|
594
|
+
dt=dt,
|
|
595
|
+
players=players,
|
|
596
|
+
rand=rand,
|
|
597
|
+
)
|
|
598
|
+
if poison_killed:
|
|
411
599
|
deaths.append(
|
|
412
600
|
self.handle_death(
|
|
413
601
|
idx,
|
|
@@ -430,7 +618,7 @@ class CreaturePool:
|
|
|
430
618
|
)
|
|
431
619
|
continue
|
|
432
620
|
|
|
433
|
-
if creature.
|
|
621
|
+
if creature.plague_infected:
|
|
434
622
|
creature.collision_timer -= float(dt)
|
|
435
623
|
if creature.collision_timer < 0.0:
|
|
436
624
|
creature.collision_timer += CONTACT_DAMAGE_PERIOD
|
|
@@ -531,7 +719,7 @@ class CreaturePool:
|
|
|
531
719
|
)
|
|
532
720
|
continue
|
|
533
721
|
|
|
534
|
-
if (float(state.bonuses.energizer) > 0.0 and float(creature.max_hp) < 500.0) or creature.
|
|
722
|
+
if (float(state.bonuses.energizer) > 0.0 and float(creature.max_hp) < 500.0) or creature.plague_infected:
|
|
535
723
|
creature.target_heading = _wrap_angle(float(creature.target_heading) + math.pi)
|
|
536
724
|
|
|
537
725
|
turn_rate = float(creature.move_speed) * CREATURE_TURN_RATE_SCALE
|
|
@@ -547,16 +735,16 @@ class CreaturePool:
|
|
|
547
735
|
dir_y = math.sin(creature.heading - math.pi / 2.0)
|
|
548
736
|
creature.vel_x = dir_x * speed
|
|
549
737
|
creature.vel_y = dir_y * speed
|
|
550
|
-
creature.x =
|
|
551
|
-
creature.y =
|
|
738
|
+
creature.x = clamp(creature.x + creature.vel_x * dt, 0.0, float(world_width))
|
|
739
|
+
creature.y = clamp(creature.y + creature.vel_y * dt, 0.0, float(world_height))
|
|
552
740
|
else:
|
|
553
741
|
# Spawner/short-strip creatures clamp to bounds using `size` as a radius; most are stationary
|
|
554
742
|
# unless ANIM_LONG_STRIP is set (see creature_update_all).
|
|
555
743
|
radius = max(0.0, float(creature.size))
|
|
556
744
|
max_x = max(radius, float(world_width) - radius)
|
|
557
745
|
max_y = max(radius, float(world_height) - radius)
|
|
558
|
-
creature.x =
|
|
559
|
-
creature.y =
|
|
746
|
+
creature.x = clamp(creature.x, radius, max_x)
|
|
747
|
+
creature.y = clamp(creature.y, radius, max_y)
|
|
560
748
|
if (creature.flags & CreatureFlags.ANIM_LONG_STRIP) == 0:
|
|
561
749
|
creature.vel_x = 0.0
|
|
562
750
|
creature.vel_y = 0.0
|
|
@@ -566,130 +754,32 @@ class CreaturePool:
|
|
|
566
754
|
dir_y = math.sin(creature.heading - math.pi / 2.0)
|
|
567
755
|
creature.vel_x = dir_x * speed
|
|
568
756
|
creature.vel_y = dir_y * speed
|
|
569
|
-
creature.x =
|
|
570
|
-
creature.y =
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
state.bonus_spawn_guard = True
|
|
596
|
-
creature.last_hit_owner_id = -1 - int(player.index)
|
|
597
|
-
deaths.append(
|
|
598
|
-
self.handle_death(
|
|
599
|
-
idx,
|
|
600
|
-
state=state,
|
|
601
|
-
players=players,
|
|
602
|
-
rand=rand,
|
|
603
|
-
detail_preset=int(detail_preset),
|
|
604
|
-
world_width=world_width,
|
|
605
|
-
world_height=world_height,
|
|
606
|
-
fx_queue=fx_queue,
|
|
607
|
-
keep_corpse=False,
|
|
608
|
-
)
|
|
609
|
-
)
|
|
610
|
-
state.bonus_spawn_guard = prev_guard
|
|
611
|
-
continue
|
|
612
|
-
|
|
613
|
-
# Contact damage throttle. While Energizer is active, the native suppresses
|
|
614
|
-
# contact/melee interactions for most creatures (and instead allows "eat" kills).
|
|
615
|
-
if float(state.bonuses.energizer) <= 0.0:
|
|
616
|
-
dist_sq = _distance_sq(creature.x, creature.y, player.pos_x, player.pos_y)
|
|
617
|
-
contact_r = (float(creature.size) + float(player.size)) * 0.25 + 20.0
|
|
618
|
-
in_contact = dist_sq <= contact_r * contact_r
|
|
619
|
-
if in_contact:
|
|
620
|
-
creature.collision_timer -= dt
|
|
621
|
-
if creature.collision_timer < 0.0:
|
|
622
|
-
creature.collision_timer += CONTACT_DAMAGE_PERIOD
|
|
623
|
-
if perk_active(player, PerkId.MR_MELEE):
|
|
624
|
-
death_start_needed = creature.hp > 0.0 and creature.hitbox_size == CREATURE_HITBOX_ALIVE
|
|
625
|
-
|
|
626
|
-
from .damage import creature_apply_damage
|
|
627
|
-
|
|
628
|
-
killed = creature_apply_damage(
|
|
629
|
-
creature,
|
|
630
|
-
damage_amount=25.0,
|
|
631
|
-
damage_type=2,
|
|
632
|
-
impulse_x=0.0,
|
|
633
|
-
impulse_y=0.0,
|
|
634
|
-
owner_id=-1 - int(player.index),
|
|
635
|
-
dt=dt,
|
|
636
|
-
players=players,
|
|
637
|
-
rand=rand,
|
|
638
|
-
)
|
|
639
|
-
if killed and death_start_needed:
|
|
640
|
-
deaths.append(
|
|
641
|
-
self.handle_death(
|
|
642
|
-
idx,
|
|
643
|
-
state=state,
|
|
644
|
-
players=players,
|
|
645
|
-
rand=rand,
|
|
646
|
-
detail_preset=int(detail_preset),
|
|
647
|
-
world_width=world_width,
|
|
648
|
-
world_height=world_height,
|
|
649
|
-
fx_queue=fx_queue,
|
|
650
|
-
)
|
|
651
|
-
)
|
|
652
|
-
if creature.active:
|
|
653
|
-
self._tick_dead(
|
|
654
|
-
creature,
|
|
655
|
-
dt=dt,
|
|
656
|
-
world_width=world_width,
|
|
657
|
-
world_height=world_height,
|
|
658
|
-
fx_queue_rotated=fx_queue_rotated,
|
|
659
|
-
)
|
|
660
|
-
continue
|
|
661
|
-
|
|
662
|
-
if float(player.shield_timer) <= 0.0:
|
|
663
|
-
if perk_active(player, PerkId.TOXIC_AVENGER):
|
|
664
|
-
creature.flags |= (
|
|
665
|
-
CreatureFlags.SELF_DAMAGE_TICK | CreatureFlags.SELF_DAMAGE_TICK_STRONG
|
|
666
|
-
)
|
|
667
|
-
elif perk_active(player, PerkId.VEINS_OF_POISON):
|
|
668
|
-
creature.flags |= CreatureFlags.SELF_DAMAGE_TICK
|
|
669
|
-
player_take_damage(state, player, float(creature.contact_damage), dt=dt, rand=rand)
|
|
670
|
-
if fx_queue is not None:
|
|
671
|
-
dx = float(player.pos_x) - float(creature.x)
|
|
672
|
-
dy = float(player.pos_y) - float(creature.y)
|
|
673
|
-
dist = math.hypot(dx, dy)
|
|
674
|
-
if dist > 1e-9:
|
|
675
|
-
dx /= dist
|
|
676
|
-
dy /= dist
|
|
677
|
-
else:
|
|
678
|
-
dx = 0.0
|
|
679
|
-
dy = 0.0
|
|
680
|
-
fx_queue.add_random(
|
|
681
|
-
pos_x=float(player.pos_x) + dx * 3.0,
|
|
682
|
-
pos_y=float(player.pos_y) + dy * 3.0,
|
|
683
|
-
rand=rand,
|
|
684
|
-
)
|
|
685
|
-
|
|
686
|
-
if (
|
|
687
|
-
bool(player.plaguebearer_active)
|
|
688
|
-
and float(creature.hp) < 150.0
|
|
689
|
-
and int(state.plaguebearer_infection_count) < 0x32
|
|
690
|
-
and dist_sq < 30.0 * 30.0
|
|
691
|
-
):
|
|
692
|
-
creature.collision_flag = 1
|
|
757
|
+
creature.x = clamp(creature.x + creature.vel_x * dt, radius, max_x)
|
|
758
|
+
creature.y = clamp(creature.y + creature.vel_y * dt, radius, max_y)
|
|
759
|
+
|
|
760
|
+
interaction_ctx = _CreatureInteractionCtx(
|
|
761
|
+
pool=self,
|
|
762
|
+
creature_index=int(idx),
|
|
763
|
+
creature=creature,
|
|
764
|
+
state=state,
|
|
765
|
+
players=players,
|
|
766
|
+
player=player,
|
|
767
|
+
dt=dt,
|
|
768
|
+
rand=rand,
|
|
769
|
+
detail_preset=int(detail_preset),
|
|
770
|
+
world_width=float(world_width),
|
|
771
|
+
world_height=float(world_height),
|
|
772
|
+
fx_queue=fx_queue,
|
|
773
|
+
fx_queue_rotated=fx_queue_rotated,
|
|
774
|
+
deaths=deaths,
|
|
775
|
+
sfx=sfx,
|
|
776
|
+
)
|
|
777
|
+
for step in _CREATURE_INTERACTION_STEPS:
|
|
778
|
+
step(interaction_ctx)
|
|
779
|
+
if interaction_ctx.skip_creature:
|
|
780
|
+
break
|
|
781
|
+
if interaction_ctx.skip_creature:
|
|
782
|
+
continue
|
|
693
783
|
|
|
694
784
|
if (not frozen_by_evil_eyes) and (creature.flags & (CreatureFlags.RANGED_ATTACK_SHOCK | CreatureFlags.RANGED_ATTACK_VARIANT)):
|
|
695
785
|
# Ported from creature_update_all (see `analysis/ghidra/raw/crimsonland.exe_decompiled.c`
|
|
@@ -862,7 +952,7 @@ class CreaturePool:
|
|
|
862
952
|
entry.tint_b = float(tint[2])
|
|
863
953
|
entry.tint_a = float(tint[3])
|
|
864
954
|
|
|
865
|
-
entry.
|
|
955
|
+
entry.plague_infected = False
|
|
866
956
|
entry.collision_timer = CONTACT_DAMAGE_PERIOD
|
|
867
957
|
entry.hitbox_size = CREATURE_HITBOX_ALIVE
|
|
868
958
|
|
|
@@ -909,8 +999,8 @@ class CreaturePool:
|
|
|
909
999
|
dir_y = math.sin(creature.heading - math.pi / 2.0)
|
|
910
1000
|
creature.vel_x = dir_x * new_hitbox * float(dt) * CREATURE_DEATH_SLIDE_SCALE
|
|
911
1001
|
creature.vel_y = dir_y * new_hitbox * float(dt) * CREATURE_DEATH_SLIDE_SCALE
|
|
912
|
-
creature.x =
|
|
913
|
-
creature.y =
|
|
1002
|
+
creature.x = clamp(creature.x - creature.vel_x, 0.0, float(world_width))
|
|
1003
|
+
creature.y = clamp(creature.y - creature.vel_y, 0.0, float(world_height))
|
|
914
1004
|
else:
|
|
915
1005
|
creature.vel_x = 0.0
|
|
916
1006
|
creature.vel_y = 0.0
|
crimson/creatures/spawn.py
CHANGED
|
@@ -27,6 +27,7 @@ __all__ = [
|
|
|
27
27
|
"CreatureFlags",
|
|
28
28
|
"CreatureInit",
|
|
29
29
|
"CreatureTypeId",
|
|
30
|
+
"RANDOM_HEADING_SENTINEL",
|
|
30
31
|
"SpawnId",
|
|
31
32
|
"SPAWN_ID_TO_TEMPLATE",
|
|
32
33
|
"SPAWN_TEMPLATES",
|
|
@@ -54,6 +55,9 @@ __all__ = [
|
|
|
54
55
|
Tint = tuple[float | None, float | None, float | None, float | None]
|
|
55
56
|
TintRGBA = tuple[float, float, float, float]
|
|
56
57
|
|
|
58
|
+
# Heading sentinel that forces randomized heading in `creature_spawn_template`.
|
|
59
|
+
RANDOM_HEADING_SENTINEL = -100.0
|
|
60
|
+
|
|
57
61
|
|
|
58
62
|
class CreatureTypeId(IntEnum):
|
|
59
63
|
ZOMBIE = 0
|
|
@@ -1366,7 +1370,7 @@ class CreatureInit:
|
|
|
1366
1370
|
pos_y: float
|
|
1367
1371
|
|
|
1368
1372
|
# Headings are in radians. The original seeds a random heading early, then overwrites it
|
|
1369
|
-
# at the end with the function argument (or a randomized argument for
|
|
1373
|
+
# at the end with the function argument (or a randomized argument for `RANDOM_HEADING_SENTINEL`).
|
|
1370
1374
|
heading: float
|
|
1371
1375
|
|
|
1372
1376
|
phase_seed: float
|
|
@@ -1648,9 +1652,9 @@ class PlanBuilder:
|
|
|
1648
1652
|
spawn_slots: list[SpawnSlotInit] = []
|
|
1649
1653
|
effects: list[BurstEffect] = []
|
|
1650
1654
|
|
|
1651
|
-
# `heading ==
|
|
1655
|
+
# `heading == RANDOM_HEADING_SENTINEL` uses a randomized heading.
|
|
1652
1656
|
final_heading = heading
|
|
1653
|
-
if final_heading ==
|
|
1657
|
+
if final_heading == RANDOM_HEADING_SENTINEL:
|
|
1654
1658
|
final_heading = float(rng.rand() % 628) * 0.01
|
|
1655
1659
|
|
|
1656
1660
|
# Base initialization always consumes one rand() for a transient heading value.
|