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/gameplay.py
CHANGED
|
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
import math
|
|
5
|
-
from typing import TYPE_CHECKING, Protocol
|
|
5
|
+
from typing import TYPE_CHECKING, Callable, Protocol
|
|
6
6
|
|
|
7
|
+
from grim.math import clamp, distance_sq
|
|
7
8
|
from .bonuses import BONUS_BY_ID, BonusId
|
|
8
9
|
from grim.rand import Crand
|
|
9
10
|
from .effects import EffectPool, FxQueue, ParticlePool, SpriteEffectPool
|
|
@@ -277,8 +278,8 @@ class BonusPool:
|
|
|
277
278
|
if entry is None:
|
|
278
279
|
return None
|
|
279
280
|
|
|
280
|
-
x =
|
|
281
|
-
y =
|
|
281
|
+
x = clamp(float(pos_x), BONUS_SPAWN_MARGIN, float(world_width) - BONUS_SPAWN_MARGIN)
|
|
282
|
+
y = clamp(float(pos_y), BONUS_SPAWN_MARGIN, float(world_height) - BONUS_SPAWN_MARGIN)
|
|
282
283
|
|
|
283
284
|
entry.bonus_id = int(bonus_id)
|
|
284
285
|
entry.picked = False
|
|
@@ -316,7 +317,7 @@ class BonusPool:
|
|
|
316
317
|
for entry in self._entries:
|
|
317
318
|
if entry.bonus_id == 0:
|
|
318
319
|
continue
|
|
319
|
-
if
|
|
320
|
+
if distance_sq(pos_x, pos_y, entry.pos_x, entry.pos_y) < min_dist_sq:
|
|
320
321
|
return None
|
|
321
322
|
|
|
322
323
|
entry = self._alloc_slot()
|
|
@@ -421,7 +422,7 @@ class BonusPool:
|
|
|
421
422
|
|
|
422
423
|
if entry.bonus_id == int(BonusId.WEAPON):
|
|
423
424
|
near_sq = BONUS_WEAPON_NEAR_RADIUS * BONUS_WEAPON_NEAR_RADIUS
|
|
424
|
-
if players and
|
|
425
|
+
if players and distance_sq(pos_x, pos_y, players[0].pos_x, players[0].pos_y) < near_sq:
|
|
425
426
|
entry.bonus_id = int(BonusId.POINTS)
|
|
426
427
|
entry.amount = 100
|
|
427
428
|
|
|
@@ -466,7 +467,7 @@ class BonusPool:
|
|
|
466
467
|
continue
|
|
467
468
|
|
|
468
469
|
for player in players:
|
|
469
|
-
if
|
|
470
|
+
if distance_sq(entry.pos_x, entry.pos_y, player.pos_x, player.pos_y) < BONUS_PICKUP_RADIUS * BONUS_PICKUP_RADIUS:
|
|
470
471
|
bonus_apply(
|
|
471
472
|
state,
|
|
472
473
|
player,
|
|
@@ -503,7 +504,7 @@ def bonus_find_aim_hover_entry(player: PlayerState, bonus_pool: BonusPool) -> tu
|
|
|
503
504
|
for idx, entry in enumerate(bonus_pool.entries):
|
|
504
505
|
if entry.bonus_id == 0 or entry.picked:
|
|
505
506
|
continue
|
|
506
|
-
if
|
|
507
|
+
if distance_sq(aim_x, aim_y, entry.pos_x, entry.pos_y) < radius_sq:
|
|
507
508
|
return idx, entry
|
|
508
509
|
return None
|
|
509
510
|
|
|
@@ -596,6 +597,155 @@ def _creature_find_in_radius(creatures: list[_CreatureForPerks], *, pos_x: float
|
|
|
596
597
|
return -1
|
|
597
598
|
|
|
598
599
|
|
|
600
|
+
@dataclass(slots=True)
|
|
601
|
+
class _PerksUpdateEffectsCtx:
|
|
602
|
+
state: GameplayState
|
|
603
|
+
players: list[PlayerState]
|
|
604
|
+
dt: float
|
|
605
|
+
creatures: list[_CreatureForPerks] | None
|
|
606
|
+
fx_queue: FxQueue | None
|
|
607
|
+
_aim_target: int | None = None
|
|
608
|
+
|
|
609
|
+
def aim_target(self) -> int:
|
|
610
|
+
if self._aim_target is not None:
|
|
611
|
+
return int(self._aim_target)
|
|
612
|
+
|
|
613
|
+
target = -1
|
|
614
|
+
if self.players and self.creatures is not None and (
|
|
615
|
+
perk_active(self.players[0], PerkId.PYROKINETIC) or perk_active(self.players[0], PerkId.EVIL_EYES)
|
|
616
|
+
):
|
|
617
|
+
target = _creature_find_in_radius(
|
|
618
|
+
self.creatures,
|
|
619
|
+
pos_x=self.players[0].aim_x,
|
|
620
|
+
pos_y=self.players[0].aim_y,
|
|
621
|
+
radius=12.0,
|
|
622
|
+
start_index=0,
|
|
623
|
+
)
|
|
624
|
+
self._aim_target = int(target)
|
|
625
|
+
return int(target)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
_PerksUpdateEffectsStep = Callable[[_PerksUpdateEffectsCtx], None]
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def _perks_update_regeneration(ctx: _PerksUpdateEffectsCtx) -> None:
|
|
632
|
+
if ctx.players and perk_active(ctx.players[0], PerkId.REGENERATION) and (ctx.state.rng.rand() & 1):
|
|
633
|
+
for player in ctx.players:
|
|
634
|
+
if not (0.0 < float(player.health) < 100.0):
|
|
635
|
+
continue
|
|
636
|
+
player.health = float(player.health) + ctx.dt
|
|
637
|
+
if player.health > 100.0:
|
|
638
|
+
player.health = 100.0
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _perks_update_lean_mean_exp_machine(ctx: _PerksUpdateEffectsCtx) -> None:
|
|
642
|
+
ctx.state.lean_mean_exp_timer -= ctx.dt
|
|
643
|
+
if ctx.state.lean_mean_exp_timer < 0.0:
|
|
644
|
+
ctx.state.lean_mean_exp_timer = 0.25
|
|
645
|
+
for player in ctx.players:
|
|
646
|
+
perk_count = perk_count_get(player, PerkId.LEAN_MEAN_EXP_MACHINE)
|
|
647
|
+
if perk_count > 0:
|
|
648
|
+
player.experience += perk_count * 10
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _perks_update_death_clock(ctx: _PerksUpdateEffectsCtx) -> None:
|
|
652
|
+
for player in ctx.players:
|
|
653
|
+
if not perk_active(player, PerkId.DEATH_CLOCK):
|
|
654
|
+
continue
|
|
655
|
+
|
|
656
|
+
if float(player.health) <= 0.0:
|
|
657
|
+
player.health = 0.0
|
|
658
|
+
else:
|
|
659
|
+
player.health = float(player.health) - ctx.dt * 3.3333333
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _perks_update_evil_eyes_target(ctx: _PerksUpdateEffectsCtx) -> None:
|
|
663
|
+
if not ctx.players:
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
target = ctx.aim_target()
|
|
667
|
+
player0 = ctx.players[0]
|
|
668
|
+
player0.evil_eyes_target_creature = target if perk_active(player0, PerkId.EVIL_EYES) else -1
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def _perks_update_pyrokinetic(ctx: _PerksUpdateEffectsCtx) -> None:
|
|
672
|
+
if not ctx.players:
|
|
673
|
+
return
|
|
674
|
+
if ctx.creatures is None:
|
|
675
|
+
return
|
|
676
|
+
if not perk_active(ctx.players[0], PerkId.PYROKINETIC):
|
|
677
|
+
return
|
|
678
|
+
|
|
679
|
+
target = ctx.aim_target()
|
|
680
|
+
if target == -1:
|
|
681
|
+
return
|
|
682
|
+
|
|
683
|
+
creature = ctx.creatures[target]
|
|
684
|
+
creature.collision_timer = float(creature.collision_timer) - ctx.dt
|
|
685
|
+
if creature.collision_timer < 0.0:
|
|
686
|
+
creature.collision_timer = 0.5
|
|
687
|
+
pos_x = float(creature.x)
|
|
688
|
+
pos_y = float(creature.y)
|
|
689
|
+
for intensity in (0.8, 0.6, 0.4, 0.3, 0.2):
|
|
690
|
+
angle = float(int(ctx.state.rng.rand()) % 0x274) * 0.01
|
|
691
|
+
ctx.state.particles.spawn_particle(pos_x=pos_x, pos_y=pos_y, angle=angle, intensity=float(intensity))
|
|
692
|
+
if ctx.fx_queue is not None:
|
|
693
|
+
ctx.fx_queue.add_random(pos_x=pos_x, pos_y=pos_y, rand=ctx.state.rng.rand)
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def _perks_update_jinxed_timer(ctx: _PerksUpdateEffectsCtx) -> None:
|
|
697
|
+
if ctx.state.jinxed_timer >= 0.0:
|
|
698
|
+
ctx.state.jinxed_timer -= ctx.dt
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def _perks_update_jinxed(ctx: _PerksUpdateEffectsCtx) -> None:
|
|
702
|
+
if ctx.state.jinxed_timer >= 0.0:
|
|
703
|
+
return
|
|
704
|
+
if not ctx.players:
|
|
705
|
+
return
|
|
706
|
+
if not perk_active(ctx.players[0], PerkId.JINXED):
|
|
707
|
+
return
|
|
708
|
+
|
|
709
|
+
player = ctx.players[0]
|
|
710
|
+
if int(ctx.state.rng.rand()) % 10 == 3:
|
|
711
|
+
player.health = float(player.health) - 5.0
|
|
712
|
+
if ctx.fx_queue is not None:
|
|
713
|
+
ctx.fx_queue.add_random(pos_x=player.pos_x, pos_y=player.pos_y, rand=ctx.state.rng.rand)
|
|
714
|
+
ctx.fx_queue.add_random(pos_x=player.pos_x, pos_y=player.pos_y, rand=ctx.state.rng.rand)
|
|
715
|
+
|
|
716
|
+
ctx.state.jinxed_timer = float(int(ctx.state.rng.rand()) % 0x14) * 0.1 + float(ctx.state.jinxed_timer) + 2.0
|
|
717
|
+
|
|
718
|
+
if float(ctx.state.bonuses.freeze) <= 0.0 and ctx.creatures is not None:
|
|
719
|
+
pool_mod = min(0x17F, len(ctx.creatures))
|
|
720
|
+
if pool_mod <= 0:
|
|
721
|
+
return
|
|
722
|
+
|
|
723
|
+
idx = int(ctx.state.rng.rand()) % pool_mod
|
|
724
|
+
attempts = 0
|
|
725
|
+
while attempts < 10 and not ctx.creatures[idx].active:
|
|
726
|
+
idx = int(ctx.state.rng.rand()) % pool_mod
|
|
727
|
+
attempts += 1
|
|
728
|
+
if not ctx.creatures[idx].active:
|
|
729
|
+
return
|
|
730
|
+
|
|
731
|
+
creature = ctx.creatures[idx]
|
|
732
|
+
creature.hp = -1.0
|
|
733
|
+
creature.hitbox_size = float(creature.hitbox_size) - ctx.dt * 20.0
|
|
734
|
+
player.experience = int(float(player.experience) + float(creature.reward_value))
|
|
735
|
+
ctx.state.sfx_queue.append("sfx_trooper_inpain_01")
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
_PERKS_UPDATE_EFFECT_STEPS: tuple[_PerksUpdateEffectsStep, ...] = (
|
|
739
|
+
_perks_update_regeneration,
|
|
740
|
+
_perks_update_lean_mean_exp_machine,
|
|
741
|
+
_perks_update_death_clock,
|
|
742
|
+
_perks_update_evil_eyes_target,
|
|
743
|
+
_perks_update_pyrokinetic,
|
|
744
|
+
_perks_update_jinxed_timer,
|
|
745
|
+
_perks_update_jinxed,
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
|
|
599
749
|
def perks_update_effects(
|
|
600
750
|
state: GameplayState,
|
|
601
751
|
players: list[PlayerState],
|
|
@@ -609,83 +759,15 @@ def perks_update_effects(
|
|
|
609
759
|
dt = float(dt)
|
|
610
760
|
if dt <= 0.0:
|
|
611
761
|
return
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
state.lean_mean_exp_timer -= dt
|
|
622
|
-
if state.lean_mean_exp_timer < 0.0:
|
|
623
|
-
state.lean_mean_exp_timer = 0.25
|
|
624
|
-
for player in players:
|
|
625
|
-
perk_count = perk_count_get(player, PerkId.LEAN_MEAN_EXP_MACHINE)
|
|
626
|
-
if perk_count > 0:
|
|
627
|
-
player.experience += perk_count * 10
|
|
628
|
-
|
|
629
|
-
target = -1
|
|
630
|
-
if players and creatures is not None and (
|
|
631
|
-
perk_active(players[0], PerkId.PYROKINETIC) or perk_active(players[0], PerkId.EVIL_EYES)
|
|
632
|
-
):
|
|
633
|
-
target = _creature_find_in_radius(
|
|
634
|
-
creatures,
|
|
635
|
-
pos_x=players[0].aim_x,
|
|
636
|
-
pos_y=players[0].aim_y,
|
|
637
|
-
radius=12.0,
|
|
638
|
-
start_index=0,
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
if players:
|
|
642
|
-
player0 = players[0]
|
|
643
|
-
player0.evil_eyes_target_creature = target if perk_active(player0, PerkId.EVIL_EYES) else -1
|
|
644
|
-
|
|
645
|
-
if players and creatures is not None and perk_active(players[0], PerkId.PYROKINETIC) and target != -1:
|
|
646
|
-
creature = creatures[target]
|
|
647
|
-
creature.collision_timer = float(creature.collision_timer) - dt
|
|
648
|
-
if creature.collision_timer < 0.0:
|
|
649
|
-
creature.collision_timer = 0.5
|
|
650
|
-
pos_x = float(creature.x)
|
|
651
|
-
pos_y = float(creature.y)
|
|
652
|
-
for intensity in (0.8, 0.6, 0.4, 0.3, 0.2):
|
|
653
|
-
angle = float(int(state.rng.rand()) % 0x274) * 0.01
|
|
654
|
-
state.particles.spawn_particle(pos_x=pos_x, pos_y=pos_y, angle=angle, intensity=float(intensity))
|
|
655
|
-
if fx_queue is not None:
|
|
656
|
-
fx_queue.add_random(pos_x=pos_x, pos_y=pos_y, rand=state.rng.rand)
|
|
657
|
-
|
|
658
|
-
if state.jinxed_timer >= 0.0:
|
|
659
|
-
state.jinxed_timer -= dt
|
|
660
|
-
|
|
661
|
-
if state.jinxed_timer < 0.0 and players and perk_active(players[0], PerkId.JINXED):
|
|
662
|
-
player = players[0]
|
|
663
|
-
if int(state.rng.rand()) % 10 == 3:
|
|
664
|
-
player.health = float(player.health) - 5.0
|
|
665
|
-
if fx_queue is not None:
|
|
666
|
-
fx_queue.add_random(pos_x=player.pos_x, pos_y=player.pos_y, rand=state.rng.rand)
|
|
667
|
-
fx_queue.add_random(pos_x=player.pos_x, pos_y=player.pos_y, rand=state.rng.rand)
|
|
668
|
-
|
|
669
|
-
state.jinxed_timer = float(int(state.rng.rand()) % 0x14) * 0.1 + float(state.jinxed_timer) + 2.0
|
|
670
|
-
|
|
671
|
-
if float(state.bonuses.freeze) <= 0.0 and creatures is not None:
|
|
672
|
-
pool_mod = min(0x17F, len(creatures))
|
|
673
|
-
if pool_mod <= 0:
|
|
674
|
-
return
|
|
675
|
-
|
|
676
|
-
idx = int(state.rng.rand()) % pool_mod
|
|
677
|
-
attempts = 0
|
|
678
|
-
while attempts < 10 and not creatures[idx].active:
|
|
679
|
-
idx = int(state.rng.rand()) % pool_mod
|
|
680
|
-
attempts += 1
|
|
681
|
-
if not creatures[idx].active:
|
|
682
|
-
return
|
|
683
|
-
|
|
684
|
-
creature = creatures[idx]
|
|
685
|
-
creature.hp = -1.0
|
|
686
|
-
creature.hitbox_size = float(creature.hitbox_size) - dt * 20.0
|
|
687
|
-
player.experience = int(float(player.experience) + float(creature.reward_value))
|
|
688
|
-
state.sfx_queue.append("sfx_trooper_inpain_01")
|
|
762
|
+
ctx = _PerksUpdateEffectsCtx(
|
|
763
|
+
state=state,
|
|
764
|
+
players=players,
|
|
765
|
+
dt=dt,
|
|
766
|
+
creatures=creatures,
|
|
767
|
+
fx_queue=fx_queue,
|
|
768
|
+
)
|
|
769
|
+
for step in _PERKS_UPDATE_EFFECT_STEPS:
|
|
770
|
+
step(ctx)
|
|
689
771
|
|
|
690
772
|
|
|
691
773
|
def award_experience(state: GameplayState, player: PlayerState, amount: int) -> int:
|
|
@@ -943,139 +1025,193 @@ def _increment_perk_count(player: PlayerState, perk_id: PerkId, *, amount: int =
|
|
|
943
1025
|
player.perk_counts[idx] += int(amount)
|
|
944
1026
|
|
|
945
1027
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
"""Apply immediate perk effects and increment the perk counter."""
|
|
1028
|
+
@dataclass(slots=True)
|
|
1029
|
+
class _PerkApplyCtx:
|
|
1030
|
+
state: GameplayState
|
|
1031
|
+
players: list[PlayerState]
|
|
1032
|
+
owner: PlayerState
|
|
1033
|
+
perk_id: PerkId
|
|
1034
|
+
perk_state: PerkSelectionState | None
|
|
1035
|
+
dt: float | None
|
|
1036
|
+
creatures: list[_CreatureForPerks] | None
|
|
956
1037
|
|
|
957
|
-
|
|
1038
|
+
def frame_dt(self) -> float:
|
|
1039
|
+
return float(self.dt) if self.dt is not None else 0.0
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
_PerkApplyHandler = Callable[[_PerkApplyCtx], None]
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def _perk_apply_instant_winner(ctx: _PerkApplyCtx) -> None:
|
|
1046
|
+
ctx.owner.experience += 2500
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def _perk_apply_fatal_lottery(ctx: _PerkApplyCtx) -> None:
|
|
1050
|
+
if ctx.state.rng.rand() & 1:
|
|
1051
|
+
ctx.owner.health = -1.0
|
|
1052
|
+
else:
|
|
1053
|
+
ctx.owner.experience += 10000
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def _perk_apply_random_weapon(ctx: _PerkApplyCtx) -> None:
|
|
1057
|
+
current = int(ctx.owner.weapon_id)
|
|
1058
|
+
weapon_id = int(current)
|
|
1059
|
+
for _ in range(100):
|
|
1060
|
+
candidate = int(weapon_pick_random_available(ctx.state))
|
|
1061
|
+
weapon_id = candidate
|
|
1062
|
+
if candidate != int(WeaponId.PISTOL) and candidate != current:
|
|
1063
|
+
break
|
|
1064
|
+
weapon_assign_player(ctx.owner, weapon_id, state=ctx.state)
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def _perk_apply_lifeline_50_50(ctx: _PerkApplyCtx) -> None:
|
|
1068
|
+
creatures = ctx.creatures
|
|
1069
|
+
if creatures is None:
|
|
958
1070
|
return
|
|
959
|
-
owner = players[0]
|
|
960
|
-
try:
|
|
961
|
-
_increment_perk_count(owner, perk_id)
|
|
962
1071
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1072
|
+
kill_toggle = False
|
|
1073
|
+
for creature in creatures:
|
|
1074
|
+
if kill_toggle and creature.active and float(creature.hp) <= 500.0 and (int(creature.flags) & 0x04) == 0:
|
|
1075
|
+
creature.active = False
|
|
1076
|
+
ctx.state.effects.spawn_burst(
|
|
1077
|
+
pos_x=float(creature.x),
|
|
1078
|
+
pos_y=float(creature.y),
|
|
1079
|
+
count=4,
|
|
1080
|
+
rand=ctx.state.rng.rand,
|
|
1081
|
+
detail_preset=5,
|
|
1082
|
+
)
|
|
1083
|
+
kill_toggle = not kill_toggle
|
|
966
1084
|
|
|
967
|
-
if perk_id == PerkId.FATAL_LOTTERY:
|
|
968
|
-
if state.rng.rand() & 1:
|
|
969
|
-
owner.health = -1.0
|
|
970
|
-
else:
|
|
971
|
-
owner.experience += 10000
|
|
972
|
-
return
|
|
973
1085
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
candidate = int(weapon_pick_random_available(state))
|
|
979
|
-
weapon_id = candidate
|
|
980
|
-
if candidate != int(WeaponId.PISTOL) and candidate != current:
|
|
981
|
-
break
|
|
982
|
-
weapon_assign_player(owner, weapon_id, state=state)
|
|
983
|
-
return
|
|
1086
|
+
def _perk_apply_thick_skinned(ctx: _PerkApplyCtx) -> None:
|
|
1087
|
+
for player in ctx.players:
|
|
1088
|
+
if player.health > 0.0:
|
|
1089
|
+
player.health = max(1.0, player.health * (2.0 / 3.0))
|
|
984
1090
|
|
|
985
|
-
if perk_id == PerkId.LIFELINE_50_50:
|
|
986
|
-
if creatures is None:
|
|
987
|
-
return
|
|
988
|
-
|
|
989
|
-
kill_toggle = False
|
|
990
|
-
for creature in creatures:
|
|
991
|
-
if (
|
|
992
|
-
kill_toggle
|
|
993
|
-
and creature.active
|
|
994
|
-
and float(creature.hp) <= 500.0
|
|
995
|
-
and (int(creature.flags) & 0x04) == 0
|
|
996
|
-
):
|
|
997
|
-
creature.active = False
|
|
998
|
-
state.effects.spawn_burst(
|
|
999
|
-
pos_x=float(creature.x),
|
|
1000
|
-
pos_y=float(creature.y),
|
|
1001
|
-
count=4,
|
|
1002
|
-
rand=state.rng.rand,
|
|
1003
|
-
detail_preset=5,
|
|
1004
|
-
)
|
|
1005
|
-
kill_toggle = not kill_toggle
|
|
1006
|
-
return
|
|
1007
1091
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
player.health = max(1.0, player.health * (2.0 / 3.0))
|
|
1012
|
-
return
|
|
1092
|
+
def _perk_apply_breathing_room(ctx: _PerkApplyCtx) -> None:
|
|
1093
|
+
for player in ctx.players:
|
|
1094
|
+
player.health -= player.health * (2.0 / 3.0)
|
|
1013
1095
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1096
|
+
frame_dt = ctx.frame_dt()
|
|
1097
|
+
creatures = ctx.creatures
|
|
1098
|
+
if creatures is not None:
|
|
1099
|
+
for creature in creatures:
|
|
1100
|
+
if creature.active:
|
|
1101
|
+
creature.hitbox_size = float(creature.hitbox_size) - frame_dt
|
|
1017
1102
|
|
|
1018
|
-
|
|
1019
|
-
if creatures is not None:
|
|
1020
|
-
for creature in creatures:
|
|
1021
|
-
if creature.active:
|
|
1022
|
-
creature.hitbox_size = float(creature.hitbox_size) - frame_dt
|
|
1103
|
+
ctx.state.bonus_spawn_guard = False
|
|
1023
1104
|
|
|
1024
|
-
state.bonus_spawn_guard = False
|
|
1025
|
-
return
|
|
1026
1105
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
return
|
|
1106
|
+
def _perk_apply_infernal_contract(ctx: _PerkApplyCtx) -> None:
|
|
1107
|
+
ctx.owner.level += 3
|
|
1108
|
+
if ctx.perk_state is not None:
|
|
1109
|
+
ctx.perk_state.pending_count += 3
|
|
1110
|
+
ctx.perk_state.choices_dirty = True
|
|
1111
|
+
for player in ctx.players:
|
|
1112
|
+
if player.health > 0.0:
|
|
1113
|
+
player.health = 0.1
|
|
1036
1114
|
|
|
1037
|
-
if perk_id == PerkId.GRIM_DEAL:
|
|
1038
|
-
owner.health = -1.0
|
|
1039
|
-
owner.experience += int(owner.experience * 0.18)
|
|
1040
|
-
return
|
|
1041
1115
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
player.perk_counts[:] = owner.perk_counts
|
|
1046
|
-
for player in players:
|
|
1047
|
-
weapon_assign_player(player, int(player.weapon_id), state=state)
|
|
1048
|
-
return
|
|
1116
|
+
def _perk_apply_grim_deal(ctx: _PerkApplyCtx) -> None:
|
|
1117
|
+
ctx.owner.health = -1.0
|
|
1118
|
+
ctx.owner.experience += int(ctx.owner.experience * 0.18)
|
|
1049
1119
|
|
|
1050
|
-
if perk_id == PerkId.DEATH_CLOCK:
|
|
1051
|
-
_increment_perk_count(owner, PerkId.REGENERATION, amount=-perk_count_get(owner, PerkId.REGENERATION))
|
|
1052
|
-
_increment_perk_count(owner, PerkId.GREATER_REGENERATION, amount=-perk_count_get(owner, PerkId.GREATER_REGENERATION))
|
|
1053
|
-
for player in players:
|
|
1054
|
-
if player.health > 0.0:
|
|
1055
|
-
player.health = 100.0
|
|
1056
|
-
return
|
|
1057
1120
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
pos_x=float(player.pos_x),
|
|
1065
|
-
pos_y=float(player.pos_y),
|
|
1066
|
-
count=8,
|
|
1067
|
-
rand=state.rng.rand,
|
|
1068
|
-
detail_preset=5,
|
|
1069
|
-
)
|
|
1070
|
-
return
|
|
1121
|
+
def _perk_apply_ammo_maniac(ctx: _PerkApplyCtx) -> None:
|
|
1122
|
+
if len(ctx.players) > 1:
|
|
1123
|
+
for player in ctx.players[1:]:
|
|
1124
|
+
player.perk_counts[:] = ctx.owner.perk_counts
|
|
1125
|
+
for player in ctx.players:
|
|
1126
|
+
weapon_assign_player(player, int(player.weapon_id), state=ctx.state)
|
|
1071
1127
|
|
|
1072
|
-
if perk_id == PerkId.MY_FAVOURITE_WEAPON:
|
|
1073
|
-
for player in players:
|
|
1074
|
-
player.clip_size += 2
|
|
1075
|
-
return
|
|
1076
1128
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1129
|
+
def _perk_apply_death_clock(ctx: _PerkApplyCtx) -> None:
|
|
1130
|
+
_increment_perk_count(
|
|
1131
|
+
ctx.owner,
|
|
1132
|
+
PerkId.REGENERATION,
|
|
1133
|
+
amount=-perk_count_get(ctx.owner, PerkId.REGENERATION),
|
|
1134
|
+
)
|
|
1135
|
+
_increment_perk_count(
|
|
1136
|
+
ctx.owner,
|
|
1137
|
+
PerkId.GREATER_REGENERATION,
|
|
1138
|
+
amount=-perk_count_get(ctx.owner, PerkId.GREATER_REGENERATION),
|
|
1139
|
+
)
|
|
1140
|
+
for player in ctx.players:
|
|
1141
|
+
if player.health > 0.0:
|
|
1142
|
+
player.health = 100.0
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
def _perk_apply_bandage(ctx: _PerkApplyCtx) -> None:
|
|
1146
|
+
for player in ctx.players:
|
|
1147
|
+
if player.health > 0.0:
|
|
1148
|
+
scale = float(ctx.state.rng.rand() % 50 + 1)
|
|
1149
|
+
player.health = min(100.0, player.health * scale)
|
|
1150
|
+
ctx.state.effects.spawn_burst(
|
|
1151
|
+
pos_x=float(player.pos_x),
|
|
1152
|
+
pos_y=float(player.pos_y),
|
|
1153
|
+
count=8,
|
|
1154
|
+
rand=ctx.state.rng.rand,
|
|
1155
|
+
detail_preset=5,
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
def _perk_apply_my_favourite_weapon(ctx: _PerkApplyCtx) -> None:
|
|
1160
|
+
for player in ctx.players:
|
|
1161
|
+
player.clip_size += 2
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
def _perk_apply_plaguebearer(ctx: _PerkApplyCtx) -> None:
|
|
1165
|
+
for player in ctx.players:
|
|
1166
|
+
player.plaguebearer_active = True
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
_PERK_APPLY_HANDLERS: dict[PerkId, _PerkApplyHandler] = {
|
|
1170
|
+
PerkId.INSTANT_WINNER: _perk_apply_instant_winner,
|
|
1171
|
+
PerkId.FATAL_LOTTERY: _perk_apply_fatal_lottery,
|
|
1172
|
+
PerkId.RANDOM_WEAPON: _perk_apply_random_weapon,
|
|
1173
|
+
PerkId.LIFELINE_50_50: _perk_apply_lifeline_50_50,
|
|
1174
|
+
PerkId.THICK_SKINNED: _perk_apply_thick_skinned,
|
|
1175
|
+
PerkId.BREATHING_ROOM: _perk_apply_breathing_room,
|
|
1176
|
+
PerkId.INFERNAL_CONTRACT: _perk_apply_infernal_contract,
|
|
1177
|
+
PerkId.GRIM_DEAL: _perk_apply_grim_deal,
|
|
1178
|
+
PerkId.AMMO_MANIAC: _perk_apply_ammo_maniac,
|
|
1179
|
+
PerkId.DEATH_CLOCK: _perk_apply_death_clock,
|
|
1180
|
+
PerkId.BANDAGE: _perk_apply_bandage,
|
|
1181
|
+
PerkId.MY_FAVOURITE_WEAPON: _perk_apply_my_favourite_weapon,
|
|
1182
|
+
PerkId.PLAGUEBEARER: _perk_apply_plaguebearer,
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
def perk_apply(
|
|
1187
|
+
state: GameplayState,
|
|
1188
|
+
players: list[PlayerState],
|
|
1189
|
+
perk_id: PerkId,
|
|
1190
|
+
*,
|
|
1191
|
+
perk_state: PerkSelectionState | None = None,
|
|
1192
|
+
dt: float | None = None,
|
|
1193
|
+
creatures: list[_CreatureForPerks] | None = None,
|
|
1194
|
+
) -> None:
|
|
1195
|
+
"""Apply immediate perk effects and increment the perk counter."""
|
|
1196
|
+
|
|
1197
|
+
if not players:
|
|
1198
|
+
return
|
|
1199
|
+
owner = players[0]
|
|
1200
|
+
try:
|
|
1201
|
+
_increment_perk_count(owner, perk_id)
|
|
1202
|
+
handler = _PERK_APPLY_HANDLERS.get(perk_id)
|
|
1203
|
+
if handler is not None:
|
|
1204
|
+
handler(
|
|
1205
|
+
_PerkApplyCtx(
|
|
1206
|
+
state=state,
|
|
1207
|
+
players=players,
|
|
1208
|
+
owner=owner,
|
|
1209
|
+
perk_id=perk_id,
|
|
1210
|
+
perk_state=perk_state,
|
|
1211
|
+
dt=dt,
|
|
1212
|
+
creatures=creatures,
|
|
1213
|
+
)
|
|
1214
|
+
)
|
|
1079
1215
|
finally:
|
|
1080
1216
|
if len(players) > 1:
|
|
1081
1217
|
for player in players[1:]:
|
|
@@ -1200,14 +1336,6 @@ def survival_progression_update(
|
|
|
1200
1336
|
return []
|
|
1201
1337
|
|
|
1202
1338
|
|
|
1203
|
-
def _clamp(value: float, lo: float, hi: float) -> float:
|
|
1204
|
-
if value < lo:
|
|
1205
|
-
return lo
|
|
1206
|
-
if value > hi:
|
|
1207
|
-
return hi
|
|
1208
|
-
return value
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
1339
|
def _normalize(x: float, y: float) -> tuple[float, float]:
|
|
1212
1340
|
mag = math.hypot(x, y)
|
|
1213
1341
|
if mag <= 1e-9:
|
|
@@ -1216,17 +1344,17 @@ def _normalize(x: float, y: float) -> tuple[float, float]:
|
|
|
1216
1344
|
return x * inv, y * inv
|
|
1217
1345
|
|
|
1218
1346
|
|
|
1219
|
-
def _distance_sq(x0: float, y0: float, x1: float, y1: float) -> float:
|
|
1220
|
-
dx = x1 - x0
|
|
1221
|
-
dy = y1 - y0
|
|
1222
|
-
return dx * dx + dy * dy
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
1347
|
def _owner_id_for_player(player_index: int) -> int:
|
|
1226
1348
|
# crimsonland.exe uses -1/-2/-3 for players (and sometimes -100 in demo paths).
|
|
1227
1349
|
return -1 - int(player_index)
|
|
1228
1350
|
|
|
1229
1351
|
|
|
1352
|
+
def _owner_id_for_player_projectiles(state: "GameplayState", player_index: int) -> int:
|
|
1353
|
+
if not state.friendly_fire_enabled:
|
|
1354
|
+
return -100
|
|
1355
|
+
return _owner_id_for_player(player_index)
|
|
1356
|
+
|
|
1357
|
+
|
|
1230
1358
|
def _weapon_entry(weapon_id: int) -> Weapon | None:
|
|
1231
1359
|
return WEAPON_BY_ID.get(int(weapon_id))
|
|
1232
1360
|
|
|
@@ -1384,6 +1512,85 @@ def _bonus_id_from_roll(roll: int, rng: Crand) -> int:
|
|
|
1384
1512
|
return int(v6)
|
|
1385
1513
|
|
|
1386
1514
|
|
|
1515
|
+
@dataclass(slots=True)
|
|
1516
|
+
class _BonusPickCtx:
|
|
1517
|
+
pool: BonusPool
|
|
1518
|
+
state: GameplayState
|
|
1519
|
+
players: list[PlayerState]
|
|
1520
|
+
bonus_id: int
|
|
1521
|
+
has_fire_bullets_drop: bool
|
|
1522
|
+
|
|
1523
|
+
|
|
1524
|
+
_BonusPickSuppressRule = Callable[[_BonusPickCtx], bool]
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
def _bonus_pick_suppress_active_shock_chain(ctx: _BonusPickCtx) -> bool:
|
|
1528
|
+
return ctx.state.shock_chain_links_left > 0 and int(ctx.bonus_id) == int(BonusId.SHOCK_CHAIN)
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
def _bonus_pick_suppress_quest_minor10_nuke(ctx: _BonusPickCtx) -> bool:
|
|
1532
|
+
if not (int(ctx.state.game_mode) == int(GameMode.QUESTS) and int(ctx.state.quest_stage_minor) == 10):
|
|
1533
|
+
return False
|
|
1534
|
+
if int(ctx.bonus_id) != int(BonusId.NUKE):
|
|
1535
|
+
return False
|
|
1536
|
+
major = int(ctx.state.quest_stage_major)
|
|
1537
|
+
if major in (2, 4, 5):
|
|
1538
|
+
return True
|
|
1539
|
+
return bool(ctx.state.hardcore) and major == 3
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
def _bonus_pick_suppress_quest_minor10_freeze(ctx: _BonusPickCtx) -> bool:
|
|
1543
|
+
if not (int(ctx.state.game_mode) == int(GameMode.QUESTS) and int(ctx.state.quest_stage_minor) == 10):
|
|
1544
|
+
return False
|
|
1545
|
+
if int(ctx.bonus_id) != int(BonusId.FREEZE):
|
|
1546
|
+
return False
|
|
1547
|
+
major = int(ctx.state.quest_stage_major)
|
|
1548
|
+
return major == 4 or (bool(ctx.state.hardcore) and major == 2)
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
def _bonus_pick_suppress_freeze_active(ctx: _BonusPickCtx) -> bool:
|
|
1552
|
+
return int(ctx.bonus_id) == int(BonusId.FREEZE) and float(ctx.state.bonuses.freeze) > 0.0
|
|
1553
|
+
|
|
1554
|
+
|
|
1555
|
+
def _bonus_pick_suppress_shield_active(ctx: _BonusPickCtx) -> bool:
|
|
1556
|
+
if int(ctx.bonus_id) != int(BonusId.SHIELD):
|
|
1557
|
+
return False
|
|
1558
|
+
return any(player.shield_timer > 0.0 for player in ctx.players)
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
def _bonus_pick_suppress_weapon_when_fire_bullets_drop(ctx: _BonusPickCtx) -> bool:
|
|
1562
|
+
return int(ctx.bonus_id) == int(BonusId.WEAPON) and bool(ctx.has_fire_bullets_drop)
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
def _bonus_pick_suppress_weapon_when_favourite_weapon(ctx: _BonusPickCtx) -> bool:
|
|
1566
|
+
if int(ctx.bonus_id) != int(BonusId.WEAPON):
|
|
1567
|
+
return False
|
|
1568
|
+
return any(perk_active(player, PerkId.MY_FAVOURITE_WEAPON) for player in ctx.players)
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
def _bonus_pick_suppress_medikit_when_death_clock(ctx: _BonusPickCtx) -> bool:
|
|
1572
|
+
if int(ctx.bonus_id) != int(BonusId.MEDIKIT):
|
|
1573
|
+
return False
|
|
1574
|
+
return any(perk_active(player, PerkId.DEATH_CLOCK) for player in ctx.players)
|
|
1575
|
+
|
|
1576
|
+
|
|
1577
|
+
def _bonus_pick_suppress_disabled(ctx: _BonusPickCtx) -> bool:
|
|
1578
|
+
return not _bonus_enabled(int(ctx.bonus_id))
|
|
1579
|
+
|
|
1580
|
+
|
|
1581
|
+
_BONUS_PICK_SUPPRESS_RULES: tuple[_BonusPickSuppressRule, ...] = (
|
|
1582
|
+
_bonus_pick_suppress_active_shock_chain,
|
|
1583
|
+
_bonus_pick_suppress_quest_minor10_nuke,
|
|
1584
|
+
_bonus_pick_suppress_quest_minor10_freeze,
|
|
1585
|
+
_bonus_pick_suppress_freeze_active,
|
|
1586
|
+
_bonus_pick_suppress_shield_active,
|
|
1587
|
+
_bonus_pick_suppress_weapon_when_fire_bullets_drop,
|
|
1588
|
+
_bonus_pick_suppress_weapon_when_favourite_weapon,
|
|
1589
|
+
_bonus_pick_suppress_medikit_when_death_clock,
|
|
1590
|
+
_bonus_pick_suppress_disabled,
|
|
1591
|
+
)
|
|
1592
|
+
|
|
1593
|
+
|
|
1387
1594
|
def bonus_pick_random_type(pool: BonusPool, state: "GameplayState", players: list["PlayerState"]) -> int:
|
|
1388
1595
|
has_fire_bullets_drop = any(
|
|
1389
1596
|
entry.bonus_id == int(BonusId.FIRE_BULLETS) and not entry.picked
|
|
@@ -1395,33 +1602,49 @@ def bonus_pick_random_type(pool: BonusPool, state: "GameplayState", players: lis
|
|
|
1395
1602
|
bonus_id = _bonus_id_from_roll(roll, state.rng)
|
|
1396
1603
|
if bonus_id <= 0:
|
|
1397
1604
|
continue
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
)
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
):
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
if bonus_id == int(BonusId.SHIELD) and any(player.shield_timer > 0.0 for player in players):
|
|
1412
|
-
continue
|
|
1413
|
-
if bonus_id == int(BonusId.WEAPON) and has_fire_bullets_drop:
|
|
1414
|
-
continue
|
|
1415
|
-
if bonus_id == int(BonusId.WEAPON) and any(perk_active(player, PerkId.MY_FAVOURITE_WEAPON) for player in players):
|
|
1416
|
-
continue
|
|
1417
|
-
if bonus_id == int(BonusId.MEDIKIT) and any(perk_active(player, PerkId.DEATH_CLOCK) for player in players):
|
|
1418
|
-
continue
|
|
1419
|
-
if not _bonus_enabled(bonus_id):
|
|
1605
|
+
ctx = _BonusPickCtx(
|
|
1606
|
+
pool=pool,
|
|
1607
|
+
state=state,
|
|
1608
|
+
players=players,
|
|
1609
|
+
bonus_id=int(bonus_id),
|
|
1610
|
+
has_fire_bullets_drop=bool(has_fire_bullets_drop),
|
|
1611
|
+
)
|
|
1612
|
+
suppressed = False
|
|
1613
|
+
for rule in _BONUS_PICK_SUPPRESS_RULES:
|
|
1614
|
+
if rule(ctx):
|
|
1615
|
+
suppressed = True
|
|
1616
|
+
break
|
|
1617
|
+
if suppressed:
|
|
1420
1618
|
continue
|
|
1421
1619
|
return bonus_id
|
|
1422
1620
|
return int(BonusId.POINTS)
|
|
1423
1621
|
|
|
1424
1622
|
|
|
1623
|
+
@dataclass(slots=True)
|
|
1624
|
+
class _WeaponAssignCtx:
|
|
1625
|
+
player: PlayerState
|
|
1626
|
+
clip_size: int
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
_WeaponAssignClipModifier = Callable[[_WeaponAssignCtx], None]
|
|
1630
|
+
|
|
1631
|
+
|
|
1632
|
+
def _weapon_assign_clip_ammo_maniac(ctx: _WeaponAssignCtx) -> None:
|
|
1633
|
+
if perk_active(ctx.player, PerkId.AMMO_MANIAC):
|
|
1634
|
+
ctx.clip_size += max(1, int(float(ctx.clip_size) * 0.25))
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
def _weapon_assign_clip_my_favourite_weapon(ctx: _WeaponAssignCtx) -> None:
|
|
1638
|
+
if perk_active(ctx.player, PerkId.MY_FAVOURITE_WEAPON):
|
|
1639
|
+
ctx.clip_size += 2
|
|
1640
|
+
|
|
1641
|
+
|
|
1642
|
+
_WEAPON_ASSIGN_CLIP_MODIFIERS: tuple[_WeaponAssignClipModifier, ...] = (
|
|
1643
|
+
_weapon_assign_clip_ammo_maniac,
|
|
1644
|
+
_weapon_assign_clip_my_favourite_weapon,
|
|
1645
|
+
)
|
|
1646
|
+
|
|
1647
|
+
|
|
1425
1648
|
def weapon_assign_player(player: PlayerState, weapon_id: int, *, state: GameplayState | None = None) -> None:
|
|
1426
1649
|
"""Assign weapon and reset per-weapon runtime state (ammo/cooldowns)."""
|
|
1427
1650
|
|
|
@@ -1436,15 +1659,10 @@ def weapon_assign_player(player: PlayerState, weapon_id: int, *, state: Gameplay
|
|
|
1436
1659
|
player.weapon_id = weapon_id
|
|
1437
1660
|
|
|
1438
1661
|
clip_size = int(weapon.clip_size) if weapon is not None and weapon.clip_size is not None else 0
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
clip_size += max(1, int(float(clip_size) * 0.25))
|
|
1444
|
-
if perk_active(player, PerkId.MY_FAVOURITE_WEAPON):
|
|
1445
|
-
clip_size += 2
|
|
1446
|
-
|
|
1447
|
-
player.clip_size = max(0, int(clip_size))
|
|
1662
|
+
clip_ctx = _WeaponAssignCtx(player=player, clip_size=max(0, clip_size))
|
|
1663
|
+
for modifier in _WEAPON_ASSIGN_CLIP_MODIFIERS:
|
|
1664
|
+
modifier(clip_ctx)
|
|
1665
|
+
player.clip_size = max(0, int(clip_ctx.clip_size))
|
|
1448
1666
|
player.ammo = float(player.clip_size)
|
|
1449
1667
|
player.weapon_reset_latch = 0
|
|
1450
1668
|
player.reload_active = False
|
|
@@ -1564,8 +1782,7 @@ def _perk_update_man_bomb(player: PlayerState, dt: float, state: GameplayState)
|
|
|
1564
1782
|
if player.man_bomb_timer <= state.perk_intervals.man_bomb:
|
|
1565
1783
|
return
|
|
1566
1784
|
|
|
1567
|
-
owner_id =
|
|
1568
|
-
state.bonus_spawn_guard = True
|
|
1785
|
+
owner_id = _owner_id_for_player_projectiles(state, player.index)
|
|
1569
1786
|
for idx in range(8):
|
|
1570
1787
|
type_id = ProjectileTypeId.ION_MINIGUN if ((idx & 1) == 0) else ProjectileTypeId.ION_RIFLE
|
|
1571
1788
|
angle = (float(state.rng.rand() % 50) * 0.01) + float(idx) * (math.pi / 4.0) - 0.25
|
|
@@ -1577,7 +1794,6 @@ def _perk_update_man_bomb(player: PlayerState, dt: float, state: GameplayState)
|
|
|
1577
1794
|
owner_id=owner_id,
|
|
1578
1795
|
base_damage=_projectile_meta_for_type_id(type_id),
|
|
1579
1796
|
)
|
|
1580
|
-
state.bonus_spawn_guard = False
|
|
1581
1797
|
state.sfx_queue.append("sfx_explosion_small")
|
|
1582
1798
|
|
|
1583
1799
|
player.man_bomb_timer -= state.perk_intervals.man_bomb
|
|
@@ -1589,7 +1805,7 @@ def _perk_update_hot_tempered(player: PlayerState, dt: float, state: GameplaySta
|
|
|
1589
1805
|
if player.hot_tempered_timer <= state.perk_intervals.hot_tempered:
|
|
1590
1806
|
return
|
|
1591
1807
|
|
|
1592
|
-
owner_id = _owner_id_for_player(player.index)
|
|
1808
|
+
owner_id = _owner_id_for_player(player.index) if state.friendly_fire_enabled else -100
|
|
1593
1809
|
state.bonus_spawn_guard = True
|
|
1594
1810
|
for idx in range(8):
|
|
1595
1811
|
type_id = ProjectileTypeId.PLASMA_MINIGUN if ((idx & 1) == 0) else ProjectileTypeId.PLASMA_RIFLE
|
|
@@ -1614,13 +1830,27 @@ def _perk_update_fire_cough(player: PlayerState, dt: float, state: GameplayState
|
|
|
1614
1830
|
if player.fire_cough_timer <= state.perk_intervals.fire_cough:
|
|
1615
1831
|
return
|
|
1616
1832
|
|
|
1617
|
-
owner_id =
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1833
|
+
owner_id = _owner_id_for_player_projectiles(state, player.index)
|
|
1834
|
+
state.sfx_queue.append("sfx_autorifle_fire")
|
|
1835
|
+
state.sfx_queue.append("sfx_plasmaminigun_fire")
|
|
1836
|
+
|
|
1837
|
+
aim_heading = float(player.aim_heading)
|
|
1838
|
+
muzzle_dir = (aim_heading - math.pi / 2.0) - 0.150915
|
|
1839
|
+
muzzle_x = player.pos_x + math.cos(muzzle_dir) * 16.0
|
|
1840
|
+
muzzle_y = player.pos_y + math.sin(muzzle_dir) * 16.0
|
|
1841
|
+
|
|
1842
|
+
aim_x = float(player.aim_x)
|
|
1843
|
+
aim_y = float(player.aim_y)
|
|
1844
|
+
dx = aim_x - float(player.pos_x)
|
|
1845
|
+
dy = aim_y - float(player.pos_y)
|
|
1846
|
+
dist = math.hypot(dx, dy)
|
|
1847
|
+
max_offset = dist * float(player.spread_heat) * 0.5
|
|
1848
|
+
dir_angle = float(int(state.rng.rand()) & 0x1FF) * (math.tau / 512.0)
|
|
1849
|
+
mag = float(int(state.rng.rand()) & 0x1FF) * (1.0 / 512.0)
|
|
1850
|
+
offset = max_offset * mag
|
|
1851
|
+
jitter_x = aim_x + math.cos(dir_angle) * offset
|
|
1852
|
+
jitter_y = aim_y + math.sin(dir_angle) * offset
|
|
1853
|
+
angle = math.atan2(jitter_y - float(player.pos_y), jitter_x - float(player.pos_x)) + math.pi / 2.0
|
|
1624
1854
|
state.projectiles.spawn(
|
|
1625
1855
|
pos_x=muzzle_x,
|
|
1626
1856
|
pos_y=muzzle_y,
|
|
@@ -1630,10 +1860,66 @@ def _perk_update_fire_cough(player: PlayerState, dt: float, state: GameplayState
|
|
|
1630
1860
|
base_damage=_projectile_meta_for_type_id(ProjectileTypeId.FIRE_BULLETS),
|
|
1631
1861
|
)
|
|
1632
1862
|
|
|
1863
|
+
vel_x = math.cos(aim_heading) * 25.0
|
|
1864
|
+
vel_y = math.sin(aim_heading) * 25.0
|
|
1865
|
+
sprite_id = state.sprite_effects.spawn(pos_x=muzzle_x, pos_y=muzzle_y, vel_x=vel_x, vel_y=vel_y, scale=1.0)
|
|
1866
|
+
sprite = state.sprite_effects.entries[int(sprite_id)]
|
|
1867
|
+
sprite.color_r = 0.5
|
|
1868
|
+
sprite.color_g = 0.5
|
|
1869
|
+
sprite.color_b = 0.5
|
|
1870
|
+
sprite.color_a = 0.413
|
|
1871
|
+
|
|
1633
1872
|
player.fire_cough_timer -= state.perk_intervals.fire_cough
|
|
1634
1873
|
state.perk_intervals.fire_cough = float(state.rng.rand() % 4) + 2.0
|
|
1635
1874
|
|
|
1636
1875
|
|
|
1876
|
+
@dataclass(slots=True)
|
|
1877
|
+
class _PlayerPerkTickCtx:
|
|
1878
|
+
state: GameplayState
|
|
1879
|
+
player: PlayerState
|
|
1880
|
+
dt: float
|
|
1881
|
+
stationary: bool
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
_PlayerPerkTickStep = Callable[[_PlayerPerkTickCtx], None]
|
|
1885
|
+
|
|
1886
|
+
|
|
1887
|
+
def _player_perk_tick_man_bomb(ctx: _PlayerPerkTickCtx) -> None:
|
|
1888
|
+
if ctx.stationary and perk_active(ctx.player, PerkId.MAN_BOMB):
|
|
1889
|
+
_perk_update_man_bomb(ctx.player, ctx.dt, ctx.state)
|
|
1890
|
+
else:
|
|
1891
|
+
ctx.player.man_bomb_timer = 0.0
|
|
1892
|
+
|
|
1893
|
+
|
|
1894
|
+
def _player_perk_tick_living_fortress(ctx: _PlayerPerkTickCtx) -> None:
|
|
1895
|
+
if ctx.stationary and perk_active(ctx.player, PerkId.LIVING_FORTRESS):
|
|
1896
|
+
ctx.player.living_fortress_timer = min(30.0, ctx.player.living_fortress_timer + ctx.dt)
|
|
1897
|
+
else:
|
|
1898
|
+
ctx.player.living_fortress_timer = 0.0
|
|
1899
|
+
|
|
1900
|
+
|
|
1901
|
+
def _player_perk_tick_fire_cough(ctx: _PlayerPerkTickCtx) -> None:
|
|
1902
|
+
if perk_active(ctx.player, PerkId.FIRE_CAUGH):
|
|
1903
|
+
_perk_update_fire_cough(ctx.player, ctx.dt, ctx.state)
|
|
1904
|
+
else:
|
|
1905
|
+
ctx.player.fire_cough_timer = 0.0
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
def _player_perk_tick_hot_tempered(ctx: _PlayerPerkTickCtx) -> None:
|
|
1909
|
+
if perk_active(ctx.player, PerkId.HOT_TEMPERED):
|
|
1910
|
+
_perk_update_hot_tempered(ctx.player, ctx.dt, ctx.state)
|
|
1911
|
+
else:
|
|
1912
|
+
ctx.player.hot_tempered_timer = 0.0
|
|
1913
|
+
|
|
1914
|
+
|
|
1915
|
+
_PLAYER_PERK_TICK_STEPS: tuple[_PlayerPerkTickStep, ...] = (
|
|
1916
|
+
_player_perk_tick_man_bomb,
|
|
1917
|
+
_player_perk_tick_living_fortress,
|
|
1918
|
+
_player_perk_tick_fire_cough,
|
|
1919
|
+
_player_perk_tick_hot_tempered,
|
|
1920
|
+
)
|
|
1921
|
+
|
|
1922
|
+
|
|
1637
1923
|
def player_fire_weapon(
|
|
1638
1924
|
player: PlayerState,
|
|
1639
1925
|
input_state: PlayerInput,
|
|
@@ -1658,26 +1944,25 @@ def player_fire_weapon(
|
|
|
1658
1944
|
ammo_cost = 1.0
|
|
1659
1945
|
is_fire_bullets = float(player.fire_bullets_timer) > 0.0
|
|
1660
1946
|
if player.reload_timer > 0.0:
|
|
1661
|
-
if player.
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
return
|
|
1947
|
+
if player.experience <= 0:
|
|
1948
|
+
return
|
|
1949
|
+
if perk_active(player, PerkId.REGRESSION_BULLETS):
|
|
1950
|
+
firing_during_reload = True
|
|
1951
|
+
ammo_class = int(weapon.ammo_class) if weapon.ammo_class is not None else 0
|
|
1952
|
+
|
|
1953
|
+
reload_time = float(weapon.reload_time) if weapon.reload_time is not None else 0.0
|
|
1954
|
+
factor = 4.0 if ammo_class == 1 else 200.0
|
|
1955
|
+
player.experience = int(float(player.experience) - reload_time * factor)
|
|
1956
|
+
if player.experience < 0:
|
|
1957
|
+
player.experience = 0
|
|
1958
|
+
elif perk_active(player, PerkId.AMMUNITION_WITHIN):
|
|
1959
|
+
firing_during_reload = True
|
|
1960
|
+
ammo_class = int(weapon.ammo_class) if weapon.ammo_class is not None else 0
|
|
1961
|
+
|
|
1962
|
+
from .player_damage import player_take_damage
|
|
1963
|
+
|
|
1964
|
+
cost = 0.15 if ammo_class == 1 else 1.0
|
|
1965
|
+
player_take_damage(state, player, cost, dt=dt, rand=state.rng.rand)
|
|
1681
1966
|
else:
|
|
1682
1967
|
return
|
|
1683
1968
|
|
|
@@ -2013,8 +2298,8 @@ def player_update(
|
|
|
2013
2298
|
if perk_active(player, PerkId.ALTERNATE_WEAPON):
|
|
2014
2299
|
speed *= 0.8
|
|
2015
2300
|
|
|
2016
|
-
player.pos_x =
|
|
2017
|
-
player.pos_y =
|
|
2301
|
+
player.pos_x = clamp(player.pos_x + move_x * speed * dt, 0.0, float(world_size))
|
|
2302
|
+
player.pos_y = clamp(player.pos_y + move_y * speed * dt, 0.0, float(world_size))
|
|
2018
2303
|
|
|
2019
2304
|
player.move_phase += dt * player.move_speed * 19.0
|
|
2020
2305
|
|
|
@@ -2023,25 +2308,9 @@ def player_update(
|
|
|
2023
2308
|
if stationary and perk_active(player, PerkId.STATIONARY_RELOADER):
|
|
2024
2309
|
reload_scale = 3.0
|
|
2025
2310
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
player.man_bomb_timer = 0.0
|
|
2030
|
-
|
|
2031
|
-
if stationary and perk_active(player, PerkId.LIVING_FORTRESS):
|
|
2032
|
-
player.living_fortress_timer = min(30.0, player.living_fortress_timer + dt)
|
|
2033
|
-
else:
|
|
2034
|
-
player.living_fortress_timer = 0.0
|
|
2035
|
-
|
|
2036
|
-
if perk_active(player, PerkId.FIRE_CAUGH):
|
|
2037
|
-
_perk_update_fire_cough(player, dt, state)
|
|
2038
|
-
else:
|
|
2039
|
-
player.fire_cough_timer = 0.0
|
|
2040
|
-
|
|
2041
|
-
if perk_active(player, PerkId.HOT_TEMPERED):
|
|
2042
|
-
_perk_update_hot_tempered(player, dt, state)
|
|
2043
|
-
else:
|
|
2044
|
-
player.hot_tempered_timer = 0.0
|
|
2311
|
+
perk_ctx = _PlayerPerkTickCtx(state=state, player=player, dt=dt, stationary=stationary)
|
|
2312
|
+
for step in _PLAYER_PERK_TICK_STEPS:
|
|
2313
|
+
step(perk_ctx)
|
|
2045
2314
|
|
|
2046
2315
|
# Reload + reload perks.
|
|
2047
2316
|
if perk_active(player, PerkId.ANXIOUS_LOADER) and input_state.fire_pressed and player.reload_timer > 0.0:
|
|
@@ -2065,7 +2334,7 @@ def player_update(
|
|
|
2065
2334
|
count=count,
|
|
2066
2335
|
angle_offset=0.1,
|
|
2067
2336
|
type_id=ProjectileTypeId.PLASMA_MINIGUN,
|
|
2068
|
-
owner_id=
|
|
2337
|
+
owner_id=_owner_id_for_player_projectiles(state, player.index),
|
|
2069
2338
|
)
|
|
2070
2339
|
state.bonus_spawn_guard = False
|
|
2071
2340
|
state.sfx_queue.append("sfx_explosion_small")
|
|
@@ -2101,309 +2370,376 @@ def player_update(
|
|
|
2101
2370
|
player.move_phase += 14.0
|
|
2102
2371
|
|
|
2103
2372
|
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
amount: int
|
|
2110
|
-
origin: _HasPos | None
|
|
2111
|
-
creatures: list[Damageable] | None
|
|
2112
|
-
players: list[PlayerState] | None
|
|
2113
|
-
apply_creature_damage: CreatureDamageApplier | None
|
|
2114
|
-
detail_preset: int
|
|
2115
|
-
|
|
2116
|
-
|
|
2373
|
+
@dataclass(slots=True)
|
|
2374
|
+
class _BonusApplyCtx:
|
|
2375
|
+
state: GameplayState
|
|
2376
|
+
player: PlayerState
|
|
2377
|
+
bonus_id: BonusId
|
|
2378
|
+
amount: int
|
|
2379
|
+
origin: _HasPos | None
|
|
2380
|
+
creatures: list[Damageable] | None
|
|
2381
|
+
players: list[PlayerState] | None
|
|
2382
|
+
apply_creature_damage: CreatureDamageApplier | None
|
|
2383
|
+
detail_preset: int
|
|
2384
|
+
economist_multiplier: float
|
|
2385
|
+
label: str
|
|
2386
|
+
icon_id: int
|
|
2387
|
+
|
|
2388
|
+
def register_global(self, timer_key: str) -> None:
|
|
2389
|
+
self.state.bonus_hud.register(
|
|
2390
|
+
self.bonus_id,
|
|
2391
|
+
label=self.label,
|
|
2392
|
+
icon_id=self.icon_id,
|
|
2393
|
+
timer_ref=_TimerRef("global", str(timer_key)),
|
|
2394
|
+
)
|
|
2117
2395
|
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2396
|
+
def register_player(self, timer_key: str) -> None:
|
|
2397
|
+
if self.players is not None and len(self.players) > 1:
|
|
2398
|
+
self.state.bonus_hud.register(
|
|
2399
|
+
self.bonus_id,
|
|
2400
|
+
label=self.label,
|
|
2401
|
+
icon_id=self.icon_id,
|
|
2402
|
+
timer_ref=_TimerRef("player", str(timer_key), player_index=0),
|
|
2403
|
+
timer_ref_alt=_TimerRef("player", str(timer_key), player_index=1),
|
|
2404
|
+
)
|
|
2405
|
+
else:
|
|
2406
|
+
self.state.bonus_hud.register(
|
|
2407
|
+
self.bonus_id,
|
|
2408
|
+
label=self.label,
|
|
2409
|
+
icon_id=self.icon_id,
|
|
2410
|
+
timer_ref=_TimerRef("player", str(timer_key), player_index=int(self.player.index)),
|
|
2411
|
+
)
|
|
2123
2412
|
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
return
|
|
2413
|
+
def origin_pos(self) -> _HasPos:
|
|
2414
|
+
return self.origin or self.player
|
|
2127
2415
|
|
|
2128
|
-
economist_multiplier = 1.0 + 0.5 * float(perk_count_get(player, PerkId.BONUS_ECONOMIST))
|
|
2129
2416
|
|
|
2130
|
-
|
|
2131
|
-
label = meta.name
|
|
2417
|
+
_BonusApplyHandler = Callable[[_BonusApplyCtx], None]
|
|
2132
2418
|
|
|
2133
|
-
def _register_global(timer_key: str) -> None:
|
|
2134
|
-
state.bonus_hud.register(
|
|
2135
|
-
bonus_id,
|
|
2136
|
-
label=label,
|
|
2137
|
-
icon_id=icon_id,
|
|
2138
|
-
timer_ref=_TimerRef("global", timer_key),
|
|
2139
|
-
)
|
|
2140
2419
|
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
state.bonus_hud.register(
|
|
2144
|
-
bonus_id,
|
|
2145
|
-
label=label,
|
|
2146
|
-
icon_id=icon_id,
|
|
2147
|
-
timer_ref=_TimerRef("player", timer_key, player_index=0),
|
|
2148
|
-
timer_ref_alt=_TimerRef("player", timer_key, player_index=1),
|
|
2149
|
-
)
|
|
2150
|
-
else:
|
|
2151
|
-
state.bonus_hud.register(
|
|
2152
|
-
bonus_id,
|
|
2153
|
-
label=label,
|
|
2154
|
-
icon_id=icon_id,
|
|
2155
|
-
timer_ref=_TimerRef("player", timer_key, player_index=int(player.index)),
|
|
2156
|
-
)
|
|
2420
|
+
def _bonus_apply_points(ctx: _BonusApplyCtx) -> None:
|
|
2421
|
+
award_experience(ctx.state, ctx.player, int(ctx.amount))
|
|
2157
2422
|
|
|
2158
|
-
if bonus_id == BonusId.ENERGIZER:
|
|
2159
|
-
old = float(state.bonuses.energizer)
|
|
2160
|
-
if old <= 0.0:
|
|
2161
|
-
_register_global("energizer")
|
|
2162
|
-
state.bonuses.energizer = float(old + float(amount) * economist_multiplier)
|
|
2163
|
-
return
|
|
2164
2423
|
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
player.weapon_reset_latch = 0
|
|
2171
|
-
player.shot_cooldown = 0.0
|
|
2172
|
-
player.reload_active = False
|
|
2173
|
-
player.reload_timer = 0.0
|
|
2174
|
-
player.reload_timer_max = 0.0
|
|
2175
|
-
player.ammo = float(player.clip_size)
|
|
2176
|
-
return
|
|
2424
|
+
def _bonus_apply_energizer(ctx: _BonusApplyCtx) -> None:
|
|
2425
|
+
old = float(ctx.state.bonuses.energizer)
|
|
2426
|
+
if old <= 0.0:
|
|
2427
|
+
ctx.register_global("energizer")
|
|
2428
|
+
ctx.state.bonuses.energizer = float(old + float(ctx.amount) * ctx.economist_multiplier)
|
|
2177
2429
|
|
|
2178
|
-
if bonus_id == BonusId.DOUBLE_EXPERIENCE:
|
|
2179
|
-
old = float(state.bonuses.double_experience)
|
|
2180
|
-
if old <= 0.0:
|
|
2181
|
-
_register_global("double_experience")
|
|
2182
|
-
state.bonuses.double_experience = float(old + float(amount) * economist_multiplier)
|
|
2183
|
-
return
|
|
2184
2430
|
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
target.reload_timer_max = 0.0
|
|
2197
|
-
return
|
|
2431
|
+
def _bonus_apply_weapon_power_up(ctx: _BonusApplyCtx) -> None:
|
|
2432
|
+
old = float(ctx.state.bonuses.weapon_power_up)
|
|
2433
|
+
if old <= 0.0:
|
|
2434
|
+
ctx.register_global("weapon_power_up")
|
|
2435
|
+
ctx.state.bonuses.weapon_power_up = float(old + float(ctx.amount) * ctx.economist_multiplier)
|
|
2436
|
+
ctx.player.weapon_reset_latch = 0
|
|
2437
|
+
ctx.player.shot_cooldown = 0.0
|
|
2438
|
+
ctx.player.reload_active = False
|
|
2439
|
+
ctx.player.reload_timer = 0.0
|
|
2440
|
+
ctx.player.reload_timer_max = 0.0
|
|
2441
|
+
ctx.player.ammo = float(ctx.player.clip_size)
|
|
2198
2442
|
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2443
|
+
|
|
2444
|
+
def _bonus_apply_double_experience(ctx: _BonusApplyCtx) -> None:
|
|
2445
|
+
old = float(ctx.state.bonuses.double_experience)
|
|
2446
|
+
if old <= 0.0:
|
|
2447
|
+
ctx.register_global("double_experience")
|
|
2448
|
+
ctx.state.bonuses.double_experience = float(old + float(ctx.amount) * ctx.economist_multiplier)
|
|
2449
|
+
|
|
2450
|
+
|
|
2451
|
+
def _bonus_apply_reflex_boost(ctx: _BonusApplyCtx) -> None:
|
|
2452
|
+
old = float(ctx.state.bonuses.reflex_boost)
|
|
2453
|
+
if old <= 0.0:
|
|
2454
|
+
ctx.register_global("reflex_boost")
|
|
2455
|
+
ctx.state.bonuses.reflex_boost = float(old + float(ctx.amount) * ctx.economist_multiplier)
|
|
2456
|
+
|
|
2457
|
+
targets = ctx.players if ctx.players is not None else [ctx.player]
|
|
2458
|
+
for target in targets:
|
|
2459
|
+
target.ammo = float(target.clip_size)
|
|
2460
|
+
target.reload_active = False
|
|
2461
|
+
target.reload_timer = 0.0
|
|
2462
|
+
target.reload_timer_max = 0.0
|
|
2463
|
+
|
|
2464
|
+
|
|
2465
|
+
def _bonus_apply_freeze(ctx: _BonusApplyCtx) -> None:
|
|
2466
|
+
old = float(ctx.state.bonuses.freeze)
|
|
2467
|
+
if old <= 0.0:
|
|
2468
|
+
ctx.register_global("freeze")
|
|
2469
|
+
ctx.state.bonuses.freeze = float(old + float(ctx.amount) * ctx.economist_multiplier)
|
|
2470
|
+
|
|
2471
|
+
creatures = ctx.creatures
|
|
2472
|
+
if creatures:
|
|
2473
|
+
rand = ctx.state.rng.rand
|
|
2474
|
+
for creature in creatures:
|
|
2475
|
+
active = getattr(creature, "active", True)
|
|
2476
|
+
if not bool(active):
|
|
2477
|
+
continue
|
|
2478
|
+
if float(getattr(creature, "hp", 0.0)) > 0.0:
|
|
2479
|
+
continue
|
|
2480
|
+
pos_x = float(getattr(creature, "x", 0.0))
|
|
2481
|
+
pos_y = float(getattr(creature, "y", 0.0))
|
|
2482
|
+
for _ in range(8):
|
|
2223
2483
|
angle = float(int(rand()) % 0x264) * 0.01
|
|
2224
|
-
state.effects.
|
|
2484
|
+
ctx.state.effects.spawn_freeze_shard(
|
|
2225
2485
|
pos_x=pos_x,
|
|
2226
2486
|
pos_y=pos_y,
|
|
2227
2487
|
angle=angle,
|
|
2228
2488
|
rand=rand,
|
|
2229
|
-
detail_preset=int(detail_preset),
|
|
2489
|
+
detail_preset=int(ctx.detail_preset),
|
|
2230
2490
|
)
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2491
|
+
angle = float(int(rand()) % 0x264) * 0.01
|
|
2492
|
+
ctx.state.effects.spawn_freeze_shatter(
|
|
2493
|
+
pos_x=pos_x,
|
|
2494
|
+
pos_y=pos_y,
|
|
2495
|
+
angle=angle,
|
|
2496
|
+
rand=rand,
|
|
2497
|
+
detail_preset=int(ctx.detail_preset),
|
|
2498
|
+
)
|
|
2499
|
+
if hasattr(creature, "active"):
|
|
2500
|
+
setattr(creature, "active", False)
|
|
2235
2501
|
|
|
2236
|
-
|
|
2237
|
-
should_register = float(player.shield_timer) <= 0.0
|
|
2238
|
-
if players is not None and len(players) > 1:
|
|
2239
|
-
should_register = float(players[0].shield_timer) <= 0.0 and float(players[1].shield_timer) <= 0.0
|
|
2240
|
-
if should_register:
|
|
2241
|
-
_register_player("shield_timer")
|
|
2242
|
-
player.shield_timer = float(player.shield_timer + float(amount) * economist_multiplier)
|
|
2243
|
-
return
|
|
2502
|
+
ctx.state.sfx_queue.append("sfx_shockwave")
|
|
2244
2503
|
|
|
2245
|
-
if bonus_id == BonusId.SPEED:
|
|
2246
|
-
should_register = float(player.speed_bonus_timer) <= 0.0
|
|
2247
|
-
if players is not None and len(players) > 1:
|
|
2248
|
-
should_register = float(players[0].speed_bonus_timer) <= 0.0 and float(players[1].speed_bonus_timer) <= 0.0
|
|
2249
|
-
if should_register:
|
|
2250
|
-
_register_player("speed_bonus_timer")
|
|
2251
|
-
player.speed_bonus_timer = float(player.speed_bonus_timer + float(amount) * economist_multiplier)
|
|
2252
|
-
return
|
|
2253
2504
|
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
player.weapon_reset_latch = 0
|
|
2262
|
-
player.shot_cooldown = 0.0
|
|
2263
|
-
player.reload_active = False
|
|
2264
|
-
player.reload_timer = 0.0
|
|
2265
|
-
player.reload_timer_max = 0.0
|
|
2266
|
-
player.ammo = float(player.clip_size)
|
|
2267
|
-
return
|
|
2505
|
+
def _bonus_apply_shield(ctx: _BonusApplyCtx) -> None:
|
|
2506
|
+
should_register = float(ctx.player.shield_timer) <= 0.0
|
|
2507
|
+
if ctx.players is not None and len(ctx.players) > 1:
|
|
2508
|
+
should_register = float(ctx.players[0].shield_timer) <= 0.0 and float(ctx.players[1].shield_timer) <= 0.0
|
|
2509
|
+
if should_register:
|
|
2510
|
+
ctx.register_player("shield_timer")
|
|
2511
|
+
ctx.player.shield_timer = float(ctx.player.shield_timer + float(ctx.amount) * ctx.economist_multiplier)
|
|
2268
2512
|
|
|
2269
|
-
if bonus_id == BonusId.SHOCK_CHAIN:
|
|
2270
|
-
if creatures:
|
|
2271
|
-
origin_pos = origin or player
|
|
2272
|
-
best_idx: int | None = None
|
|
2273
|
-
best_dist = 0.0
|
|
2274
|
-
for idx, creature in enumerate(creatures):
|
|
2275
|
-
if creature.hp <= 0.0:
|
|
2276
|
-
continue
|
|
2277
|
-
d = _distance_sq(float(origin_pos.pos_x), float(origin_pos.pos_y), creature.x, creature.y)
|
|
2278
|
-
if best_idx is None or d < best_dist:
|
|
2279
|
-
best_idx = idx
|
|
2280
|
-
best_dist = d
|
|
2281
|
-
if best_idx is not None:
|
|
2282
|
-
target = creatures[best_idx]
|
|
2283
|
-
dx = target.x - float(origin_pos.pos_x)
|
|
2284
|
-
dy = target.y - float(origin_pos.pos_y)
|
|
2285
|
-
angle = math.atan2(dy, dx) + math.pi / 2.0
|
|
2286
|
-
owner_id = _owner_id_for_player(player.index) if state.friendly_fire_enabled else -100
|
|
2287
2513
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2514
|
+
def _bonus_apply_speed(ctx: _BonusApplyCtx) -> None:
|
|
2515
|
+
should_register = float(ctx.player.speed_bonus_timer) <= 0.0
|
|
2516
|
+
if ctx.players is not None and len(ctx.players) > 1:
|
|
2517
|
+
should_register = (
|
|
2518
|
+
float(ctx.players[0].speed_bonus_timer) <= 0.0 and float(ctx.players[1].speed_bonus_timer) <= 0.0
|
|
2519
|
+
)
|
|
2520
|
+
if should_register:
|
|
2521
|
+
ctx.register_player("speed_bonus_timer")
|
|
2522
|
+
ctx.player.speed_bonus_timer = float(ctx.player.speed_bonus_timer + float(ctx.amount) * ctx.economist_multiplier)
|
|
2523
|
+
|
|
2524
|
+
|
|
2525
|
+
def _bonus_apply_fire_bullets(ctx: _BonusApplyCtx) -> None:
|
|
2526
|
+
should_register = float(ctx.player.fire_bullets_timer) <= 0.0
|
|
2527
|
+
if ctx.players is not None and len(ctx.players) > 1:
|
|
2528
|
+
should_register = float(ctx.players[0].fire_bullets_timer) <= 0.0 and float(ctx.players[1].fire_bullets_timer) <= 0.0
|
|
2529
|
+
if should_register:
|
|
2530
|
+
ctx.register_player("fire_bullets_timer")
|
|
2531
|
+
ctx.player.fire_bullets_timer = float(ctx.player.fire_bullets_timer + float(ctx.amount) * ctx.economist_multiplier)
|
|
2532
|
+
ctx.player.weapon_reset_latch = 0
|
|
2533
|
+
ctx.player.shot_cooldown = 0.0
|
|
2534
|
+
ctx.player.reload_active = False
|
|
2535
|
+
ctx.player.reload_timer = 0.0
|
|
2536
|
+
ctx.player.reload_timer_max = 0.0
|
|
2537
|
+
ctx.player.ammo = float(ctx.player.clip_size)
|
|
2538
|
+
|
|
2539
|
+
|
|
2540
|
+
def _bonus_apply_shock_chain(ctx: _BonusApplyCtx) -> None:
|
|
2541
|
+
creatures = ctx.creatures
|
|
2542
|
+
if not creatures:
|
|
2299
2543
|
return
|
|
2300
2544
|
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2545
|
+
origin_pos = ctx.origin_pos()
|
|
2546
|
+
best_idx: int | None = None
|
|
2547
|
+
best_dist = 0.0
|
|
2548
|
+
for idx, creature in enumerate(creatures):
|
|
2549
|
+
if creature.hp <= 0.0:
|
|
2550
|
+
continue
|
|
2551
|
+
d = distance_sq(float(origin_pos.pos_x), float(origin_pos.pos_y), creature.x, creature.y)
|
|
2552
|
+
if best_idx is None or d < best_dist:
|
|
2553
|
+
best_idx = idx
|
|
2554
|
+
best_dist = d
|
|
2555
|
+
if best_idx is None:
|
|
2312
2556
|
return
|
|
2313
2557
|
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2558
|
+
target = creatures[best_idx]
|
|
2559
|
+
dx = target.x - float(origin_pos.pos_x)
|
|
2560
|
+
dy = target.y - float(origin_pos.pos_y)
|
|
2561
|
+
angle = math.atan2(dy, dx) + math.pi / 2.0
|
|
2562
|
+
owner_id = _owner_id_for_player(ctx.player.index) if ctx.state.friendly_fire_enabled else -100
|
|
2563
|
+
|
|
2564
|
+
ctx.state.bonus_spawn_guard = True
|
|
2565
|
+
ctx.state.shock_chain_links_left = 0x20
|
|
2566
|
+
ctx.state.shock_chain_projectile_id = ctx.state.projectiles.spawn(
|
|
2567
|
+
pos_x=float(origin_pos.pos_x),
|
|
2568
|
+
pos_y=float(origin_pos.pos_y),
|
|
2569
|
+
angle=angle,
|
|
2570
|
+
type_id=int(ProjectileTypeId.ION_RIFLE),
|
|
2571
|
+
owner_id=int(owner_id),
|
|
2572
|
+
base_damage=_projectile_meta_for_type_id(int(ProjectileTypeId.ION_RIFLE)),
|
|
2573
|
+
)
|
|
2574
|
+
ctx.state.bonus_spawn_guard = False
|
|
2575
|
+
|
|
2576
|
+
|
|
2577
|
+
def _bonus_apply_weapon(ctx: _BonusApplyCtx) -> None:
|
|
2578
|
+
weapon_id = int(ctx.amount)
|
|
2579
|
+
if perk_active(ctx.player, PerkId.ALTERNATE_WEAPON) and ctx.player.alt_weapon_id is None:
|
|
2580
|
+
ctx.player.alt_weapon_id = int(ctx.player.weapon_id)
|
|
2581
|
+
ctx.player.alt_clip_size = int(ctx.player.clip_size)
|
|
2582
|
+
ctx.player.alt_ammo = float(ctx.player.ammo)
|
|
2583
|
+
ctx.player.alt_reload_active = bool(ctx.player.reload_active)
|
|
2584
|
+
ctx.player.alt_reload_timer = float(ctx.player.reload_timer)
|
|
2585
|
+
ctx.player.alt_shot_cooldown = float(ctx.player.shot_cooldown)
|
|
2586
|
+
ctx.player.alt_reload_timer_max = float(ctx.player.reload_timer_max)
|
|
2587
|
+
weapon_assign_player(ctx.player, weapon_id, state=ctx.state)
|
|
2588
|
+
|
|
2589
|
+
|
|
2590
|
+
def _bonus_apply_fireblast(ctx: _BonusApplyCtx) -> None:
|
|
2591
|
+
origin_pos = ctx.origin_pos()
|
|
2592
|
+
owner_id = _owner_id_for_player(ctx.player.index) if ctx.state.friendly_fire_enabled else -100
|
|
2593
|
+
ctx.state.bonus_spawn_guard = True
|
|
2594
|
+
_spawn_projectile_ring(
|
|
2595
|
+
ctx.state,
|
|
2596
|
+
origin_pos,
|
|
2597
|
+
count=16,
|
|
2598
|
+
angle_offset=0.0,
|
|
2599
|
+
type_id=ProjectileTypeId.PLASMA_RIFLE,
|
|
2600
|
+
owner_id=int(owner_id),
|
|
2601
|
+
)
|
|
2602
|
+
ctx.state.bonus_spawn_guard = False
|
|
2603
|
+
ctx.state.sfx_queue.append("sfx_explosion_medium")
|
|
2604
|
+
|
|
2605
|
+
|
|
2606
|
+
def _bonus_apply_nuke(ctx: _BonusApplyCtx) -> None:
|
|
2607
|
+
# `bonus_apply` (crimsonland.exe @ 0x00409890) starts screen shake via:
|
|
2608
|
+
# camera_shake_pulses = 0x14;
|
|
2609
|
+
# camera_shake_timer = 0.2f;
|
|
2610
|
+
ctx.state.camera_shake_pulses = 0x14
|
|
2611
|
+
ctx.state.camera_shake_timer = 0.2
|
|
2612
|
+
|
|
2613
|
+
origin_pos = ctx.origin_pos()
|
|
2614
|
+
ox = float(origin_pos.pos_x)
|
|
2615
|
+
oy = float(origin_pos.pos_y)
|
|
2616
|
+
rand = ctx.state.rng.rand
|
|
2617
|
+
|
|
2618
|
+
bullet_count = int(rand()) & 3
|
|
2619
|
+
bullet_count += 4
|
|
2620
|
+
assault_meta = _projectile_meta_for_type_id(int(ProjectileTypeId.ASSAULT_RIFLE))
|
|
2621
|
+
for _ in range(bullet_count):
|
|
2622
|
+
angle = float(int(rand()) % 0x274) * 0.01
|
|
2623
|
+
proj_id = ctx.state.projectiles.spawn(
|
|
2624
|
+
pos_x=ox,
|
|
2625
|
+
pos_y=oy,
|
|
2626
|
+
angle=float(angle),
|
|
2627
|
+
type_id=int(ProjectileTypeId.ASSAULT_RIFLE),
|
|
2628
|
+
owner_id=-100,
|
|
2629
|
+
base_damage=assault_meta,
|
|
2325
2630
|
)
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
state.camera_shake_pulses = 0x14
|
|
2335
|
-
state.camera_shake_timer = 0.2
|
|
2336
|
-
|
|
2337
|
-
origin_pos = origin or player
|
|
2338
|
-
ox = float(origin_pos.pos_x)
|
|
2339
|
-
oy = float(origin_pos.pos_y)
|
|
2340
|
-
rand = state.rng.rand
|
|
2341
|
-
|
|
2342
|
-
bullet_count = int(rand()) & 3
|
|
2343
|
-
bullet_count += 4
|
|
2344
|
-
assault_meta = _projectile_meta_for_type_id(int(ProjectileTypeId.ASSAULT_RIFLE))
|
|
2345
|
-
for _ in range(bullet_count):
|
|
2346
|
-
angle = float(int(rand()) % 0x274) * 0.01
|
|
2347
|
-
proj_id = state.projectiles.spawn(
|
|
2348
|
-
pos_x=ox,
|
|
2349
|
-
pos_y=oy,
|
|
2350
|
-
angle=float(angle),
|
|
2351
|
-
type_id=int(ProjectileTypeId.ASSAULT_RIFLE),
|
|
2352
|
-
owner_id=-100,
|
|
2353
|
-
base_damage=assault_meta,
|
|
2354
|
-
)
|
|
2355
|
-
if proj_id != -1:
|
|
2356
|
-
speed_scale = float(int(rand()) % 0x32) * 0.01 + 0.5
|
|
2357
|
-
state.projectiles.entries[proj_id].speed_scale *= float(speed_scale)
|
|
2358
|
-
|
|
2359
|
-
minigun_meta = _projectile_meta_for_type_id(int(ProjectileTypeId.MEAN_MINIGUN))
|
|
2360
|
-
for _ in range(2):
|
|
2361
|
-
angle = float(int(rand()) % 0x274) * 0.01
|
|
2362
|
-
state.projectiles.spawn(
|
|
2363
|
-
pos_x=ox,
|
|
2364
|
-
pos_y=oy,
|
|
2365
|
-
angle=float(angle),
|
|
2366
|
-
type_id=int(ProjectileTypeId.MEAN_MINIGUN),
|
|
2367
|
-
owner_id=-100,
|
|
2368
|
-
base_damage=minigun_meta,
|
|
2369
|
-
)
|
|
2370
|
-
|
|
2371
|
-
state.effects.spawn_explosion_burst(
|
|
2631
|
+
if proj_id != -1:
|
|
2632
|
+
speed_scale = float(int(rand()) % 0x32) * 0.01 + 0.5
|
|
2633
|
+
ctx.state.projectiles.entries[proj_id].speed_scale *= float(speed_scale)
|
|
2634
|
+
|
|
2635
|
+
minigun_meta = _projectile_meta_for_type_id(int(ProjectileTypeId.MEAN_MINIGUN))
|
|
2636
|
+
for _ in range(2):
|
|
2637
|
+
angle = float(int(rand()) % 0x274) * 0.01
|
|
2638
|
+
ctx.state.projectiles.spawn(
|
|
2372
2639
|
pos_x=ox,
|
|
2373
2640
|
pos_y=oy,
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2641
|
+
angle=float(angle),
|
|
2642
|
+
type_id=int(ProjectileTypeId.MEAN_MINIGUN),
|
|
2643
|
+
owner_id=-100,
|
|
2644
|
+
base_damage=minigun_meta,
|
|
2377
2645
|
)
|
|
2378
2646
|
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2647
|
+
ctx.state.effects.spawn_explosion_burst(
|
|
2648
|
+
pos_x=ox,
|
|
2649
|
+
pos_y=oy,
|
|
2650
|
+
scale=1.0,
|
|
2651
|
+
rand=rand,
|
|
2652
|
+
detail_preset=int(ctx.detail_preset),
|
|
2653
|
+
)
|
|
2654
|
+
|
|
2655
|
+
creatures = ctx.creatures
|
|
2656
|
+
if creatures:
|
|
2657
|
+
prev_guard = bool(ctx.state.bonus_spawn_guard)
|
|
2658
|
+
ctx.state.bonus_spawn_guard = True
|
|
2659
|
+
for idx, creature in enumerate(creatures):
|
|
2660
|
+
if creature.hp <= 0.0:
|
|
2661
|
+
continue
|
|
2662
|
+
dx = float(creature.x) - ox
|
|
2663
|
+
dy = float(creature.y) - oy
|
|
2664
|
+
if abs(dx) > 256.0 or abs(dy) > 256.0:
|
|
2665
|
+
continue
|
|
2666
|
+
dist = math.hypot(dx, dy)
|
|
2667
|
+
if dist < 256.0:
|
|
2668
|
+
damage = (256.0 - dist) * 5.0
|
|
2669
|
+
if ctx.apply_creature_damage is not None:
|
|
2670
|
+
ctx.apply_creature_damage(
|
|
2671
|
+
int(idx),
|
|
2672
|
+
float(damage),
|
|
2673
|
+
3,
|
|
2674
|
+
0.0,
|
|
2675
|
+
0.0,
|
|
2676
|
+
_owner_id_for_player(ctx.player.index),
|
|
2677
|
+
)
|
|
2678
|
+
else:
|
|
2679
|
+
creature.hp -= float(damage)
|
|
2680
|
+
ctx.state.bonus_spawn_guard = prev_guard
|
|
2681
|
+
|
|
2682
|
+
ctx.state.sfx_queue.append("sfx_explosion_large")
|
|
2683
|
+
ctx.state.sfx_queue.append("sfx_shockwave")
|
|
2684
|
+
|
|
2685
|
+
|
|
2686
|
+
_BONUS_APPLY_HANDLERS: dict[BonusId, _BonusApplyHandler] = {
|
|
2687
|
+
BonusId.POINTS: _bonus_apply_points,
|
|
2688
|
+
BonusId.ENERGIZER: _bonus_apply_energizer,
|
|
2689
|
+
BonusId.WEAPON_POWER_UP: _bonus_apply_weapon_power_up,
|
|
2690
|
+
BonusId.DOUBLE_EXPERIENCE: _bonus_apply_double_experience,
|
|
2691
|
+
BonusId.REFLEX_BOOST: _bonus_apply_reflex_boost,
|
|
2692
|
+
BonusId.FREEZE: _bonus_apply_freeze,
|
|
2693
|
+
BonusId.SHIELD: _bonus_apply_shield,
|
|
2694
|
+
BonusId.SPEED: _bonus_apply_speed,
|
|
2695
|
+
BonusId.FIRE_BULLETS: _bonus_apply_fire_bullets,
|
|
2696
|
+
BonusId.SHOCK_CHAIN: _bonus_apply_shock_chain,
|
|
2697
|
+
BonusId.WEAPON: _bonus_apply_weapon,
|
|
2698
|
+
BonusId.FIREBLAST: _bonus_apply_fireblast,
|
|
2699
|
+
BonusId.NUKE: _bonus_apply_nuke,
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
|
|
2703
|
+
def bonus_apply(
|
|
2704
|
+
state: GameplayState,
|
|
2705
|
+
player: PlayerState,
|
|
2706
|
+
bonus_id: BonusId,
|
|
2707
|
+
*,
|
|
2708
|
+
amount: int | None = None,
|
|
2709
|
+
origin: _HasPos | None = None,
|
|
2710
|
+
creatures: list[Damageable] | None = None,
|
|
2711
|
+
players: list[PlayerState] | None = None,
|
|
2712
|
+
apply_creature_damage: CreatureDamageApplier | None = None,
|
|
2713
|
+
detail_preset: int = 5,
|
|
2714
|
+
) -> None:
|
|
2715
|
+
"""Apply a bonus to player + global timers (subset of `bonus_apply`)."""
|
|
2716
|
+
|
|
2717
|
+
meta = BONUS_BY_ID.get(int(bonus_id))
|
|
2718
|
+
if meta is None:
|
|
2406
2719
|
return
|
|
2720
|
+
if amount is None:
|
|
2721
|
+
amount = int(meta.default_amount or 0)
|
|
2722
|
+
|
|
2723
|
+
economist_multiplier = 1.0 + 0.5 * float(perk_count_get(player, PerkId.BONUS_ECONOMIST))
|
|
2724
|
+
icon_id = int(meta.icon_id) if meta.icon_id is not None else -1
|
|
2725
|
+
label = meta.name
|
|
2726
|
+
ctx = _BonusApplyCtx(
|
|
2727
|
+
state=state,
|
|
2728
|
+
player=player,
|
|
2729
|
+
bonus_id=bonus_id,
|
|
2730
|
+
amount=int(amount),
|
|
2731
|
+
origin=origin,
|
|
2732
|
+
creatures=creatures,
|
|
2733
|
+
players=players,
|
|
2734
|
+
apply_creature_damage=apply_creature_damage,
|
|
2735
|
+
detail_preset=int(detail_preset),
|
|
2736
|
+
economist_multiplier=float(economist_multiplier),
|
|
2737
|
+
label=str(label),
|
|
2738
|
+
icon_id=int(icon_id),
|
|
2739
|
+
)
|
|
2740
|
+
handler = _BONUS_APPLY_HANDLERS.get(bonus_id)
|
|
2741
|
+
if handler is not None:
|
|
2742
|
+
handler(ctx)
|
|
2407
2743
|
|
|
2408
2744
|
# Bonus types not modeled yet.
|
|
2409
2745
|
return
|