summaryrefslogtreecommitdiffstats
path: root/src/game.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-07-11 01:35:02 +0200
committersalmonik <raisersoft@proton.me>2026-07-11 01:35:02 +0200
commitaae1d1a56a935b2aeba7eb953b71106b190da974 (patch)
treed87d11ca843878795f0ccbac12cb0d8d69e6ee68 /src/game.rs
parentPhase 2 (diff)
downloaddungeon-aae1d1a56a935b2aeba7eb953b71106b190da974.tar.gz
dungeon-aae1d1a56a935b2aeba7eb953b71106b190da974.zip
animations, introduction, no terminal
Diffstat (limited to '')
-rw-r--r--src/game.rs84
1 files changed, 71 insertions, 13 deletions
diff --git a/src/game.rs b/src/game.rs
index 80ee0c6..ca30207 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -608,6 +608,7 @@ impl Player {
608const MOVE_DURATION: f64 = 0.18; // seconds to walk one tile 608const MOVE_DURATION: f64 = 0.18; // seconds to walk one tile
609const TURN_DURATION: f64 = 0.12; // seconds for a 90° turn 609const TURN_DURATION: f64 = 0.12; // seconds for a 90° turn
610const ENEMY_ANIM_DURATION: f64 = 0.10; // seconds for enemy movement 610const ENEMY_ANIM_DURATION: f64 = 0.10; // seconds for enemy movement
611const ATTACK_DURATION: f64 = 0.12; // seconds for attack lunge
611 612
612pub const MAX_HP: u32 = 5; 613pub const MAX_HP: u32 = 5;
613pub const MAX_SANITY: u32 = 100; 614pub const MAX_SANITY: u32 = 100;
@@ -659,6 +660,19 @@ pub struct Game {
659 660
660 // ---- animation: enemies ---- 661 // ---- animation: enemies ----
661 enemy_anim_t: f64, 662 enemy_anim_t: f64,
663
664 // ---- animation: attack ----
665 pub attacking: bool,
666 pub attack_t: f64,
667 attack_base_x: f64,
668 attack_base_y: f64,
669
670 // ---- screen flash ----
671 pub flash_color: u32,
672 pub flash_alpha: f64,
673
674 // ---- controls overlay ----
675 pub show_controls: bool,
662} 676}
663 677
664fn smoothstep(t: f64) -> f64 { 678fn smoothstep(t: f64) -> f64 {
@@ -703,6 +717,13 @@ impl Game {
703 turn_delta: 0.0, 717 turn_delta: 0.0,
704 turn_t: 0.0, 718 turn_t: 0.0,
705 enemy_anim_t: 0.0, 719 enemy_anim_t: 0.0,
720 attacking: false,
721 attack_t: 0.0,
722 attack_base_x: 0.0,
723 attack_base_y: 0.0,
724 flash_color: 0,
725 flash_alpha: 0.0,
726 show_controls: true,
706 } 727 }
707 } 728 }
708 729
@@ -717,6 +738,11 @@ impl Game {
717 /// Returns true when the current animation phase finishes and we should 738 /// Returns true when the current animation phase finishes and we should
718 /// advance the state machine. 739 /// advance the state machine.
719 pub fn update_animation(&mut self, dt: f64) -> bool { 740 pub fn update_animation(&mut self, dt: f64) -> bool {
741 // Flash always fades, regardless of state.
742 if self.flash_alpha > 0.0 {
743 self.flash_alpha = (self.flash_alpha - dt * 4.0).max(0.0);
744 }
745
720 // Turn animation runs independently — turning never consumes a turn. 746 // Turn animation runs independently — turning never consumes a turn.
721 if self.turning { 747 if self.turning {
722 self.turn_t += dt / TURN_DURATION; 748 self.turn_t += dt / TURN_DURATION;
@@ -751,6 +777,28 @@ impl Game {
751 } 777 }
752 } 778 }
753 779
780 // Animate attack lunge.
781 if self.attacking {
782 self.attack_t += dt / ATTACK_DURATION;
783 if self.attack_t >= 1.0 {
784 self.player.x = self.attack_base_x;
785 self.player.y = self.attack_base_y;
786 self.attacking = false;
787 } else {
788 // Lunge forward then back.
789 let lunge = if self.attack_t < 0.5 {
790 smoothstep(self.attack_t * 2.0) * 0.25
791 } else {
792 (1.0 - smoothstep((self.attack_t - 0.5) * 2.0)) * 0.25
793 };
794 let fx = self.player.angle.cos();
795 let fy = self.player.angle.sin();
796 self.player.x = self.attack_base_x + fx * lunge;
797 self.player.y = self.attack_base_y + fy * lunge;
798 done = false;
799 }
800 }
801
754 if done { 802 if done {
755 self.state = GameState::ResolvingTurn; 803 self.state = GameState::ResolvingTurn;
756 self.resolve_turn(); 804 self.resolve_turn();
@@ -921,12 +969,10 @@ impl Game {
921 let new_hp = hp.saturating_sub(total_dmg); 969 let new_hp = hp.saturating_sub(total_dmg);
922 970
923 if new_hp == 0 { 971 if new_hp == 0 {
924 // Enemy dies.
925 self.maze.take_entity(fx, fy); 972 self.maze.take_entity(fx, fy);
926 self.kills += 1; 973 self.kills += 1;
927 self.set_message(&format!("You slay the enemy! ({} dmg)", total_dmg)); 974 self.set_message(&format!("You slay the enemy! ({} dmg)", total_dmg));
928 } else { 975 } else {
929 // Enemy survives and retaliates.
930 self.maze.entity_at_mut(fx, fy).map(|e| { 976 self.maze.entity_at_mut(fx, fy).map(|e| {
931 if let Entity::Enemy { hp: ref mut h, .. } = e { 977 if let Entity::Enemy { hp: ref mut h, .. } = e {
932 *h = new_hp; 978 *h = new_hp;
@@ -941,7 +987,9 @@ impl Game {
941 self.state = GameState::GameOver; 987 self.state = GameState::GameOver;
942 } 988 }
943 } 989 }
944 self.advance_turn(); 990 self.flash_color = 0xFF_CC3333;
991 self.flash_alpha = 0.4;
992 self.begin_attack_animation();
945 true 993 true
946 } 994 }
947 995
@@ -950,11 +998,9 @@ impl Game {
950 let new_hp = hp.saturating_sub(1); 998 let new_hp = hp.saturating_sub(1);
951 999
952 if !persistent || new_hp == 0 { 1000 if !persistent || new_hp == 0 {
953 // Vanishes.
954 self.maze.take_entity(fx, fy); 1001 self.maze.take_entity(fx, fy);
955 self.set_message("The hallucination dissipates."); 1002 self.set_message("The hallucination dissipates.");
956 } else { 1003 } else {
957 // Persistent — doesn't vanish yet.
958 self.maze.entity_at_mut(fx, fy).map(|e| { 1004 self.maze.entity_at_mut(fx, fy).map(|e| {
959 if let Entity::Hallucination { hp: ref mut h, .. } = e { 1005 if let Entity::Hallucination { hp: ref mut h, .. } = e {
960 *h = new_hp; 1006 *h = new_hp;
@@ -962,18 +1008,30 @@ impl Game {
962 }); 1008 });
963 self.set_message("You strike the apparition - it remains."); 1009 self.set_message("You strike the apparition - it remains.");
964 } 1010 }
965 self.advance_turn(); 1011 self.flash_color = 0xFF_8833CC;
1012 self.flash_alpha = 0.35;
1013 self.begin_attack_animation();
966 true 1014 true
967 } 1015 }
968 1016
969 _ => { 1017 _ => {
970 self.set_message("You swing at the air."); 1018 self.set_message("You swing at the air.");
971 self.advance_turn(); 1019 self.flash_color = 0xFF_FFFFFF;
1020 self.flash_alpha = 0.05;
1021 self.begin_attack_animation();
972 true 1022 true
973 } 1023 }
974 } 1024 }
975 } 1025 }
976 1026
1027 fn begin_attack_animation(&mut self) {
1028 self.attack_base_x = self.player.x;
1029 self.attack_base_y = self.player.y;
1030 self.attacking = true;
1031 self.attack_t = 0.0;
1032 self.state = GameState::AnimatingPlayer;
1033 }
1034
977 // ---- inventory -------------------------------------------------------- 1035 // ---- inventory --------------------------------------------------------
978 1036
979 /// Use the currently selected inventory item (Z key). 1037 /// Use the currently selected inventory item (Z key).
@@ -1107,7 +1165,10 @@ impl Game {
1107 1165
1108 /// Apply all per-turn effects (weakness, heartbeat, HP drain, message decay). 1166 /// Apply all per-turn effects (weakness, heartbeat, HP drain, message decay).
1109 fn resolve_turn(&mut self) { 1167 fn resolve_turn(&mut self) {
1110 self.weakness = (self.weakness + 1).min(MAX_WEAKNESS); 1168 // Weakness grows every 3 turns, not every turn.
1169 if self.heartbeat_count % 3 == 0 {
1170 self.weakness = (self.weakness + 1).min(MAX_WEAKNESS);
1171 }
1111 self.heartbeat_count += 1; 1172 self.heartbeat_count += 1;
1112 1173
1113 // Message decay. 1174 // Message decay.
@@ -1118,14 +1179,11 @@ impl Game {
1118 } 1179 }
1119 } 1180 }
1120 1181
1121 // HP drain at max weakness. 1182 // HP drain at max weakness — stops at 1, never kills.
1122 if self.weakness >= MAX_WEAKNESS { 1183 if self.weakness >= MAX_WEAKNESS {
1123 if self.hp > 0 { 1184 if self.hp > 1 {
1124 self.hp -= 1; 1185 self.hp -= 1;
1125 } 1186 }
1126 if self.hp == 0 {
1127 self.state = GameState::GameOver;
1128 }
1129 } 1187 }
1130 } 1188 }
1131 1189