fix(engine): fire XpAwardedEvent for achievement BonusXp rewards
The Reward::BonusXp path in evaluate_on_win was adding XP directly without sending XpAwardedEvent, so players saw no "+25 XP" toast when the no_undo achievement first unlocked. Adds the missing event send and a regression test verifying the event fires on unlock. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ use solitaire_data::{
|
|||||||
save_progress_to,
|
save_progress_to,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::events::{AchievementUnlockedEvent, GameWonEvent};
|
use crate::events::{AchievementUnlockedEvent, GameWonEvent, XpAwardedEvent};
|
||||||
use crate::game_plugin::GameMutation;
|
use crate::game_plugin::GameMutation;
|
||||||
use crate::progress_plugin::{LevelUpEvent, ProgressResource, ProgressStoragePath, ProgressUpdate};
|
use crate::progress_plugin::{LevelUpEvent, ProgressResource, ProgressStoragePath, ProgressUpdate};
|
||||||
use crate::resources::GameStateResource;
|
use crate::resources::GameStateResource;
|
||||||
@@ -72,6 +72,7 @@ impl Plugin for AchievementPlugin {
|
|||||||
.insert_resource(AchievementsStoragePath(self.storage_path.clone()))
|
.insert_resource(AchievementsStoragePath(self.storage_path.clone()))
|
||||||
.add_event::<AchievementUnlockedEvent>()
|
.add_event::<AchievementUnlockedEvent>()
|
||||||
.add_event::<GameWonEvent>()
|
.add_event::<GameWonEvent>()
|
||||||
|
.add_event::<XpAwardedEvent>()
|
||||||
// Run after GameMutation (so GameWonEvent is available), after
|
// Run after GameMutation (so GameWonEvent is available), after
|
||||||
// StatsUpdate (so stats reflect this win), and after ProgressUpdate
|
// StatsUpdate (so stats reflect this win), and after ProgressUpdate
|
||||||
// (so daily_challenge_streak is up to date for daily_devotee).
|
// (so daily_challenge_streak is up to date for daily_devotee).
|
||||||
@@ -91,6 +92,7 @@ fn evaluate_on_win(
|
|||||||
mut wins: EventReader<GameWonEvent>,
|
mut wins: EventReader<GameWonEvent>,
|
||||||
mut unlocks: EventWriter<AchievementUnlockedEvent>,
|
mut unlocks: EventWriter<AchievementUnlockedEvent>,
|
||||||
mut levelups: EventWriter<LevelUpEvent>,
|
mut levelups: EventWriter<LevelUpEvent>,
|
||||||
|
mut xp_awarded: EventWriter<XpAwardedEvent>,
|
||||||
game: Res<GameStateResource>,
|
game: Res<GameStateResource>,
|
||||||
stats: Res<StatsResource>,
|
stats: Res<StatsResource>,
|
||||||
path: Res<AchievementsStoragePath>,
|
path: Res<AchievementsStoragePath>,
|
||||||
@@ -154,6 +156,7 @@ fn evaluate_on_win(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reward::BonusXp(amount) => {
|
Reward::BonusXp(amount) => {
|
||||||
|
xp_awarded.send(XpAwardedEvent { amount });
|
||||||
let prev_level = progress.0.add_xp(amount);
|
let prev_level = progress.0.add_xp(amount);
|
||||||
if progress.0.leveled_up_from(prev_level) {
|
if progress.0.leveled_up_from(prev_level) {
|
||||||
levelups.send(LevelUpEvent {
|
levelups.send(LevelUpEvent {
|
||||||
@@ -454,6 +457,27 @@ mod tests {
|
|||||||
assert_eq!(display_name_for("bogus"), "bogus");
|
assert_eq!(display_name_for("bogus"), "bogus");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bonus_xp_reward_fires_xp_awarded_event() {
|
||||||
|
let mut app = headless_app();
|
||||||
|
// "no_undo" achievement awards BonusXp(25). Trigger it by sending a
|
||||||
|
// GameWonEvent with undo_count == 0 (default) and enough stats to match.
|
||||||
|
app.world_mut().send_event(GameWonEvent {
|
||||||
|
score: 1000,
|
||||||
|
time_seconds: 300,
|
||||||
|
});
|
||||||
|
app.update();
|
||||||
|
|
||||||
|
let events = app.world().resource::<Events<XpAwardedEvent>>();
|
||||||
|
let mut cursor = events.get_cursor();
|
||||||
|
let xp_events: Vec<u64> = cursor.read(events).map(|e| e.amount).collect();
|
||||||
|
// The no_undo achievement (BonusXp 25) must have fired an XpAwardedEvent.
|
||||||
|
assert!(
|
||||||
|
xp_events.contains(&25),
|
||||||
|
"BonusXp(25) must fire XpAwardedEvent; got {xp_events:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn press(app: &mut App, key: KeyCode) {
|
fn press(app: &mut App, key: KeyCode) {
|
||||||
let mut input = app.world_mut().resource_mut::<ButtonInput<KeyCode>>();
|
let mut input = app.world_mut().resource_mut::<ButtonInput<KeyCode>>();
|
||||||
input.release(key);
|
input.release(key);
|
||||||
|
|||||||
Reference in New Issue
Block a user