Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions crates/game-logic/src/engine/config.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
// ─── Core game settings ───────────────────────────────────────────────────────
//Core game settings
pub const DEFAULT_LIVES: u8 = 3;
pub const ULTIME_CHARGE_RATIO: u32 = 100;
pub const BALL_SAVER_SCORE: u32 = 500;

// ─── Bumper scoring ───────────────────────────────────────────────────────────
// Bumper scoring
pub const BUMPER_SCORE: u32 = 100;
pub const BUMPER_TRIANGLE_SCORE: u32 = 200;
pub const PORTAL_SCORE: u32 = 300;

// ─── Multiball ────────────────────────────────────────────────────────────────
// Multiball
pub const MULTIBALL_RING_THRESHOLD: u32 = 10;
pub const MULTIBALL_SCORE: u32 = 5_000;

// ─── Timer bonus (BonusGameTimerMultiplier) ───────────────────────────────────
// Timer bonus (BonusGameTimerMultiplier)
pub const TIMER_BONUS_SECONDS: u64 = 60;
pub const TIMER_BONUS_SCORE: u32 = 500;
pub const TIMER_BONUS_MULTIPLIER: f32 = 1.5;

// ─── Tilt penalties ───────────────────────────────────────────────────────────
// Tilt penalties
pub const TILT_PENALTY_1: i64 = -2_000;
pub const TILT_PENALTY_2: i64 = -6_000;

// ─── Boss HP ──────────────────────────────────────────────────────────────────
// Boss HP
pub const BOSS_0_HP: u32 = 500;
pub const BOSS_1_HP: u32 = 800;
pub const BOSS_2_HP: u32 = 1_200;

// ─── Boss difficulty scaling ──────────────────────────────────────────────────
// Boss difficulty scaling
pub const BOSS_0_DIFFICULTY_SCALE: f32 = 1.0;
pub const BOSS_1_DIFFICULTY_SCALE: f32 = 1.6;
pub const BOSS_2_DIFFICULTY_SCALE: f32 = 2.4;
pub const ENDLESS_BASE_DIFFICULTY_SCALE: f32 = 2.4;
pub const ENDLESS_LEVEL_SCALE_EXPONENT: f32 = 1.3;

// ─── Combo system ─────────────────────────────────────────────────────────────
// Combo system
pub const COMBO_BUFFER_MAX: usize = 10;
pub const COMBO_DETECTION_WINDOW_MS: u64 = 3_000;
pub const COMBO_PENALTY_REPEAT: usize = 7;
pub const COMBO_PENALTY_PTS: i64 = -2_000;
pub const COMBO_PENALTY_PTS: i64 = 2_000;

// Combo stats: (bonus_pts, multiplier, duration_ms)
pub const COMBO_1_BONUS: u32 = 0;
Expand Down Expand Up @@ -78,7 +80,7 @@ pub const COMBO_13_BONUS: u32 = 0;
pub const COMBO_13_MULTIPLIER: f32 = 1.5;
pub const COMBO_13_DURATION_MS: u64 = 500;

// ─── Character stats ──────────────────────────────────────────────────────────
// Character stats
pub const ROBOCP_ULTIMATE_MAX: u32 = 500;
pub const ROBOCP_BONUS_COOLDOWN_MS: u64 = 30_000;
pub const ROBOCP_MALUS_COOLDOWN_MS: u64 = 45_000;
Expand All @@ -95,7 +97,7 @@ pub const CYBORG_ULTIMATE_MAX: u32 = 450;
pub const CYBORG_BONUS_COOLDOWN_MS: u64 = 28_000;
pub const CYBORG_MALUS_COOLDOWN_MS: u64 = 40_000;

// ─── Skill effects ────────────────────────────────────────────────────────────
// Skill effects
pub const SKILL_SHIELD_DURATION_MS: u64 = 8_000;

pub const SKILL_DAMAGE_BOOST_MULTIPLIER: f32 = 2.0;
Expand Down
62 changes: 60 additions & 2 deletions crates/game-logic/src/engine/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ impl GameEngine {
"BumperTriangle" => GameEvent::BumperTriangleHit {
pts: crate::engine::config::BUMPER_TRIANGLE_SCORE,
},
"PortalUsed" => GameEvent::PortalUsed,
"FlipperLeft" => GameEvent::ButtonPressed {
side: ButtonSide::Left,
},
"FlipperRight" => GameEvent::ButtonPressed {
side: ButtonSide::Right,
},
"BallSaverReady" => GameEvent::BallSaverReady,
unknown => {
tracing::debug!(event_type = unknown, "unhandled screen event type");
return vec![];
Expand All @@ -94,8 +102,9 @@ impl GameEngine {
let mut envelopes = Vec::new();

match event {
GameEvent::StartGame { .. } => {
GameEvent::StartGame { ref player_id } => {
self.state = GameState::new(DEFAULT_LIVES);
self.state.player_id = player_id.clone();
self.state.phase = GamePhase::InGame;
self.state.session_start = Some(now);
self.timer_bonus_given = false;
Expand Down Expand Up @@ -126,6 +135,7 @@ impl GameEngine {
return envelopes;
}
self.state.balls_lost_since_start += 1;
self.state.bumper_hit_count = 0;
self.state.lives = self.state.lives.saturating_sub(1);

let (pve_env, extra) = self.pve_engine.on_event(&event, &mut self.state);
Expand Down Expand Up @@ -159,7 +169,11 @@ impl GameEngine {
envelopes.extend(self.process(GameEvent::ComboActivated(effect)));
}
ComboResult::Penalty { pts } => {
self.state.score = apply_tilt_penalty(self.state.score, pts);
if pts < 0 {
self.state.score = apply_tilt_penalty(self.state.score, pts);
} else {
self.state.score = self.state.score.saturating_add(pts as u64);
}
envelopes.push(self.emit_score_update());
}
ComboResult::BadgeUnlocked { badge_id } => {
Expand Down Expand Up @@ -193,8 +207,14 @@ impl GameEngine {
envelopes.extend(self.process(e));
}

self.state.bumper_hit_count += 1;
let bumper_count = self.state.bumper_hit_count;
envelopes.extend(self.check_timer_bonus(now));
envelopes.push(self.emit_score_delta(scored, "bumper"));
envelopes.push(self.emit_score_update());
envelopes.extend(self.process(GameEvent::BumperCombo {
count: bumper_count,
}));
}

GameEvent::BumperCombo { count } => {
Expand All @@ -206,6 +226,21 @@ impl GameEngine {
GameEvent::PortalUsed => {
let pts = crate::engine::scoring::score_portal_bonus();
self.state.add_score(pts);
envelopes.push(self.emit_score_delta(pts, "portal"));
envelopes.push(self.emit_score_update());
}

GameEvent::BallSaverReady => {
if self.state.phase != GamePhase::InGame {
return envelopes;
}
let pts = crate::engine::config::BALL_SAVER_SCORE as u64;
self.state.add_score(pts);
envelopes.push(self.emit_score_delta(pts, "ball_saver"));
envelopes.push(make_event_envelope(
"BallSaverReady",
serde_json::Value::Null,
));
envelopes.push(self.emit_score_update());
}

Expand Down Expand Up @@ -237,7 +272,12 @@ impl GameEngine {
}

GameEvent::MultiballWin => {
self.state.bumper_hit_count = 0;
let pts = crate::engine::config::MULTIBALL_SCORE as u64;
self.state.add_score(pts);
envelopes.push(self.emit_score_delta(pts, "multiball"));
envelopes.push(make_event_envelope("MultiballWin", serde_json::Value::Null));
envelopes.push(self.emit_score_update());
}

GameEvent::ScoreMultiplierActivated => {
Expand All @@ -261,6 +301,7 @@ impl GameEngine {
self.multiplier.apply(&effect, now);
self.state.add_score(effect.bonus_pts as u64);
envelopes.push(self.emit_combo_activated(&effect));
envelopes.push(self.emit_score_delta(effect.bonus_pts as u64, "combo"));
envelopes.push(self.emit_score_update());
}

Expand Down Expand Up @@ -298,12 +339,15 @@ impl GameEngine {
&& self.state.balls_lost_since_start == 0
{
self.timer_bonus_given = true;
let old_score = self.state.score;
self.state.score = timer_bonus(self.state.score, 0);
let delta = self.state.score.saturating_sub(old_score);
return vec![
make_event_envelope(
"TimerBonus",
serde_json::json!({ "new_score": self.state.score }),
),
self.emit_score_delta(delta, "timer_bonus"),
self.emit_score_update(),
];
}
Expand Down Expand Up @@ -353,11 +397,25 @@ impl GameEngine {

fn emit_score_update(&self) -> ScreenEnvelope {
let current_multiplier = self.multiplier.current(Instant::now());
let ball = self.state.balls_lost_since_start + 1;
make_event_envelope(
"ScoreUpdate",
serde_json::json!({
"score": self.state.score,
"multiplier": current_multiplier,
"player": self.state.player_id,
"ball": ball,
}),
)
}

fn emit_score_delta(&self, delta: u64, reason: &str) -> ScreenEnvelope {
make_event_envelope(
"ScoreDelta",
serde_json::json!({
"delta": delta,
"reason": reason,
"total": self.state.score,
}),
)
}
Expand Down
1 change: 1 addition & 0 deletions crates/game-logic/src/engine/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub enum GameEvent {
BumperTriangleHit { pts: u32 },
BumperCombo { count: u32 },
PortalUsed,
BallSaverReady,
TiltDetected,
LifeUp,
MultiballWin,
Expand Down
4 changes: 4 additions & 0 deletions crates/game-logic/src/engine/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ impl TiltState {
pub struct GameState {
pub phase: GamePhase,
pub score: u64,
pub player_id: String,
pub lives: u8,
pub active_multiplier: f32,
pub multiplier_expires_at: Option<Instant>,
pub tilt_state: TiltState,
pub balls_lost_since_start: u32,
pub bumper_hit_count: u32,
pub session_start: Option<Instant>,
pub cheating_detected: bool,
pub extra_balls: u8,
Expand All @@ -58,11 +60,13 @@ impl GameState {
Self {
phase: GamePhase::Idle,
score: 0,
player_id: String::new(),
lives,
active_multiplier: 1.0,
multiplier_expires_at: None,
tilt_state: TiltState::default(),
balls_lost_since_start: 0,
bumper_hit_count: 0,
session_start: None,
cheating_detected: false,
extra_balls: 0,
Expand Down
Loading