mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Implement basic functionality for recognising achievement updates,
updating associated data and clarifying events
This commit is contained in:
parent
3ff5e105dd
commit
e7c47e1d38
@ -1000,6 +1000,9 @@ impl Client {
|
||||
ServerMsg::AchievementDataError(error) => {
|
||||
// TODO handle somehow
|
||||
},
|
||||
ServerMsg::AchievementCompletion => {
|
||||
tracing::info!("Completed achievement");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +1,95 @@
|
||||
use crate::{
|
||||
comp::{self, inventory::item::Consumable, InventoryUpdateEvent},
|
||||
event::ServerEvent,
|
||||
};
|
||||
use crate::comp::item::{Consumable, Item, ItemKind};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
|
||||
pub enum AchievementCategory {
|
||||
CollectConsumable,
|
||||
ReachLevel,
|
||||
KillHumanoidSpecies,
|
||||
KillBodyType,
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AchievementEvent {
|
||||
None,
|
||||
CollectedItem(Item),
|
||||
LevelUp(u32),
|
||||
}
|
||||
|
||||
// Potential additions
|
||||
// - ReachCoordinate
|
||||
// - CollectCurrency(amount)
|
||||
// - KillPlayers(amount)
|
||||
// - OpenChests
|
||||
/// The types of achievements available in game
|
||||
///
|
||||
/// Some potential additions in the future:
|
||||
/// - ReachCoordinate
|
||||
/// - CollectCurrency
|
||||
/// - KillPlayers
|
||||
/// - OpenChests
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum AchievementType {
|
||||
CollectConsumable(Consumable, i32),
|
||||
ReachLevel(i32),
|
||||
KillHumanoidSpecies(comp::body::humanoid::Species, i32),
|
||||
KillBodyType(comp::Body, i32),
|
||||
pub enum AchievementAction {
|
||||
None,
|
||||
CollectConsumable(Consumable),
|
||||
ReachLevel,
|
||||
}
|
||||
|
||||
impl From<AchievementType> for AchievementCategory {
|
||||
fn from(achievement_type: AchievementType) -> AchievementCategory {
|
||||
match achievement_type {
|
||||
AchievementType::CollectConsumable(_, _) => AchievementCategory::CollectConsumable,
|
||||
AchievementType::ReachLevel(_) => AchievementCategory::ReachLevel,
|
||||
AchievementType::KillHumanoidSpecies(_, _) => AchievementCategory::KillHumanoidSpecies,
|
||||
AchievementType::KillBodyType(_, _) => AchievementCategory::KillBodyType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The representation of an achievement that is declared in .ron config.
|
||||
/// Information about an achievement. This differs from a complete
|
||||
/// [`Achievement`](struct.Achievement.html) in that it describes the
|
||||
/// achievement without any information about progress
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct AchievementItem {
|
||||
pub title: String,
|
||||
pub achievement_type: AchievementType,
|
||||
}
|
||||
|
||||
/// TODO remove this, it's a confusing state
|
||||
impl Default for AchievementItem {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
title: String::new(),
|
||||
achievement_type: AchievementType::ReachLevel(0),
|
||||
}
|
||||
}
|
||||
pub action: AchievementAction,
|
||||
pub target: usize,
|
||||
}
|
||||
|
||||
impl AchievementItem {
|
||||
pub fn matches_event(&self, event: InventoryUpdateEvent) -> bool {
|
||||
pub fn matches_event(&self, event: AchievementEvent) -> bool {
|
||||
match event {
|
||||
InventoryUpdateEvent::Collected(_item) => true,
|
||||
_ => false,
|
||||
AchievementEvent::LevelUp(_) => self.action == AchievementAction::ReachLevel,
|
||||
AchievementEvent::CollectedItem(item) => match self.action {
|
||||
AchievementAction::CollectConsumable(consumable) => {
|
||||
if let ItemKind::Consumable { kind, .. } = item.kind {
|
||||
kind == consumable
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
},
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
?event,
|
||||
"An AchievementEvent was processed but the event was not handled"
|
||||
);
|
||||
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The complete representation of an achievement that has been
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Achievement {
|
||||
pub id: i32,
|
||||
pub item: AchievementItem,
|
||||
pub completed: bool,
|
||||
pub progress: i32,
|
||||
pub progress: usize,
|
||||
}
|
||||
|
||||
// impl Achievement {
|
||||
// pub fn incr(&self, event: InventoryUpdateEvent) -> Option<bool> {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
impl Achievement {
|
||||
/// Increment the progress of this Achievement based on its type
|
||||
///
|
||||
/// By default, when an achievement is incremented, its `progress` value is
|
||||
/// incremented by 1. This covers many cases, but using this method allows
|
||||
/// handling of unique types of achievements which are not simple
|
||||
/// counters for events
|
||||
pub fn increment_progress(&mut self, event: AchievementEvent) -> bool {
|
||||
match event {
|
||||
AchievementEvent::LevelUp(level) => {
|
||||
self.progress = level as usize;
|
||||
},
|
||||
_ => self.progress += 1,
|
||||
};
|
||||
|
||||
self.completed = self.progress >= self.item.target;
|
||||
self.completed
|
||||
}
|
||||
}
|
||||
|
||||
/// The achievement List assigned to all players. This holds a list of
|
||||
/// achievements where the player has made some progress towards completion.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct AchievementList(Vec<Achievement>);
|
||||
|
||||
@ -84,16 +97,215 @@ impl Default for AchievementList {
|
||||
fn default() -> AchievementList { AchievementList(Vec::new()) }
|
||||
}
|
||||
|
||||
impl AchievementList {
|
||||
/// Process a single achievement item, inrementing or doing whataver it does
|
||||
/// to indicate it's one step closer to cmpletion
|
||||
pub fn process(&self, item: &AchievementItem) -> Option<bool> {
|
||||
// if self.0.iter().
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for AchievementList {
|
||||
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
|
||||
}
|
||||
|
||||
impl AchievementList {
|
||||
pub fn item_by_id(&mut self, id: i32) -> Option<&mut Achievement> {
|
||||
self.0.iter_mut().find(|a| a.id == id)
|
||||
}
|
||||
|
||||
/// Process a single achievement item, inrementing the progress of the
|
||||
/// achievement. This is called as part of server/sys/Achievements.
|
||||
pub fn process_achievement(
|
||||
&mut self,
|
||||
achievement: Achievement,
|
||||
event: AchievementEvent,
|
||||
) -> bool {
|
||||
let id = achievement.id;
|
||||
|
||||
if !self.0.contains(&achievement) {
|
||||
self.0.push(achievement);
|
||||
}
|
||||
|
||||
return if let Some(char_achievement) = self.item_by_id(id) {
|
||||
if char_achievement.completed {
|
||||
return false;
|
||||
}
|
||||
|
||||
char_achievement.increment_progress(event)
|
||||
} else {
|
||||
tracing::warn!("Failed to find achievement after inserting");
|
||||
|
||||
false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Used as a container for in-game events that contribute towards player
|
||||
/// achievements.
|
||||
///
|
||||
/// For example, when an `InventoryManip` is detected, we record that event in
|
||||
/// order to process achievements which depend on collecting items.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AchievementUpdate {
|
||||
event: AchievementEvent,
|
||||
}
|
||||
|
||||
impl AchievementUpdate {
|
||||
pub fn new(event: AchievementEvent) -> Self { Self { event } }
|
||||
|
||||
pub fn event(&self) -> AchievementEvent { self.event.clone() }
|
||||
}
|
||||
|
||||
impl Component for AchievementUpdate {
|
||||
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assets, comp::item::Consumable};
|
||||
|
||||
#[test]
|
||||
fn inv_collect_event_matches_consumable_achievement_item() {
|
||||
let item = AchievementItem {
|
||||
title: String::from("Test"),
|
||||
action: AchievementAction::CollectConsumable(Consumable::Apple),
|
||||
target: 10,
|
||||
};
|
||||
|
||||
let event =
|
||||
AchievementEvent::CollectedItem(assets::load_expect_cloned("common.items.apple"));
|
||||
|
||||
assert!(item.matches_event(event));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inv_collect_event_not_matches_consumable_achievement_item() {
|
||||
let item = AchievementItem {
|
||||
title: String::from("Test"),
|
||||
action: AchievementAction::CollectConsumable(Consumable::Cheese),
|
||||
target: 10,
|
||||
};
|
||||
|
||||
let event =
|
||||
AchievementEvent::CollectedItem(assets::load_expect_cloned("common.items.apple"));
|
||||
|
||||
assert_eq!(item.matches_event(event), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levelup_event_matches_reach_level_achievement_item() {
|
||||
let item = AchievementItem {
|
||||
title: String::from("Test"),
|
||||
action: AchievementAction::ReachLevel,
|
||||
target: 100,
|
||||
};
|
||||
|
||||
let event = AchievementEvent::LevelUp(3);
|
||||
|
||||
assert_eq!(item.matches_event(event), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_collect_achievement_increments_progress() {
|
||||
let item = AchievementItem {
|
||||
title: String::from("Collect 3 Mushrooms"),
|
||||
action: AchievementAction::CollectConsumable(Consumable::Mushroom),
|
||||
target: 3,
|
||||
};
|
||||
|
||||
let achievement = Achievement {
|
||||
id: 1,
|
||||
item,
|
||||
completed: false,
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
let event =
|
||||
AchievementEvent::CollectedItem(assets::load_expect_cloned("common.items.mushroom"));
|
||||
|
||||
let mut achievement_list = AchievementList::default();
|
||||
|
||||
// The first two increments should not indicate that it is complete
|
||||
assert_eq!(
|
||||
achievement_list.process_achievement(achievement.clone(), event.clone()),
|
||||
false
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
achievement_list.process_achievement(achievement.clone(), event.clone()),
|
||||
false
|
||||
);
|
||||
|
||||
assert_eq!(achievement_list.0.get(0).unwrap().progress, 2);
|
||||
|
||||
// It should return true when completed
|
||||
assert_eq!(
|
||||
achievement_list.process_achievement(achievement, event),
|
||||
true
|
||||
);
|
||||
|
||||
assert_eq!(achievement_list.0.get(0).unwrap().progress, 3);
|
||||
|
||||
// The achievement `completed` field should be true
|
||||
assert_eq!(achievement_list.0.get(0).unwrap().completed, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_levelup_achievement_increments_progress() {
|
||||
let item = AchievementItem {
|
||||
title: String::from("Reach Level 10"),
|
||||
action: AchievementAction::ReachLevel,
|
||||
target: 10,
|
||||
};
|
||||
|
||||
let achievement = Achievement {
|
||||
id: 1,
|
||||
item,
|
||||
completed: false,
|
||||
progress: 1,
|
||||
};
|
||||
|
||||
let mut achievement_list = AchievementList::default();
|
||||
|
||||
assert_eq!(
|
||||
achievement_list.process_achievement(achievement.clone(), AchievementEvent::LevelUp(6)),
|
||||
false
|
||||
);
|
||||
|
||||
// The achievement progress should be the new level value, and be incomplete
|
||||
assert_eq!(achievement_list.0.get(0).unwrap().progress, 6);
|
||||
assert_eq!(achievement_list.0.get(0).unwrap().completed, false);
|
||||
|
||||
assert_eq!(
|
||||
achievement_list.process_achievement(achievement, AchievementEvent::LevelUp(10)),
|
||||
true
|
||||
);
|
||||
|
||||
// The achievement progress should be the new level value, and be completed
|
||||
assert_eq!(achievement_list.0.get(0).unwrap().progress, 10);
|
||||
assert_eq!(achievement_list.0.get(0).unwrap().completed, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_completed_achievement_doesnt_increment_progress() {
|
||||
let item = AchievementItem {
|
||||
title: String::from("Collect 3 Mushrooms"),
|
||||
action: AchievementAction::CollectConsumable(Consumable::Mushroom),
|
||||
target: 3,
|
||||
};
|
||||
|
||||
let achievement = Achievement {
|
||||
id: 1,
|
||||
item,
|
||||
completed: true,
|
||||
progress: 3,
|
||||
};
|
||||
|
||||
let mut achievement_list = AchievementList(vec![achievement.clone()]);
|
||||
|
||||
let event =
|
||||
AchievementEvent::CollectedItem(assets::load_expect_cloned("common.items.mushroom"));
|
||||
|
||||
assert_eq!(
|
||||
achievement_list.process_achievement(achievement, event),
|
||||
false
|
||||
);
|
||||
|
||||
// The achievement progress should not have incremented
|
||||
assert_eq!(achievement_list.0.get(0).unwrap().progress, 3);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,10 @@ mod visual;
|
||||
|
||||
// Reexports
|
||||
pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout};
|
||||
pub use achievement::{Achievement, AchievementItem, AchievementList, AchievementType};
|
||||
pub use achievement::{
|
||||
Achievement, AchievementAction, AchievementEvent, AchievementItem, AchievementList,
|
||||
AchievementUpdate,
|
||||
};
|
||||
pub use admin::{Admin, AdminList};
|
||||
pub use agent::{Agent, Alignment};
|
||||
pub use body::{
|
||||
|
@ -83,10 +83,6 @@ pub enum ServerEvent {
|
||||
Chat(comp::ChatMsg),
|
||||
}
|
||||
|
||||
pub enum AchievementEvent {
|
||||
CollectedItem { entity: EcsEntity, item: comp::Item },
|
||||
}
|
||||
|
||||
pub struct EventBus<E> {
|
||||
queue: Mutex<VecDeque<E>>,
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ pub enum ServerMsg {
|
||||
AchievementDataUpdate(Vec<comp::Achievement>),
|
||||
/// An error occurred while loading character achievements
|
||||
AchievementDataError(String),
|
||||
/// The client has completed an achievement
|
||||
AchievementCompletion,
|
||||
/// An error occurred while loading character data
|
||||
CharacterDataLoadError(String),
|
||||
/// A list of characters belonging to the a authenticated player was sent
|
||||
|
@ -150,6 +150,7 @@ impl State {
|
||||
ecs.register::<comp::WaypointArea>();
|
||||
ecs.register::<comp::ForceUpdate>();
|
||||
ecs.register::<comp::InventoryUpdate>();
|
||||
ecs.register::<comp::AchievementUpdate>();
|
||||
ecs.register::<comp::Admin>();
|
||||
ecs.register::<comp::Waypoint>();
|
||||
ecs.register::<comp::Projectile>();
|
||||
|
@ -1,10 +1,9 @@
|
||||
[
|
||||
(
|
||||
title: "Collect 10 Apples",
|
||||
achievement_type: CollectConsumable(Apple, 10)
|
||||
),
|
||||
(
|
||||
title: "Collect 50 Apples",
|
||||
achievement_type: CollectConsumable(Apple, 50)
|
||||
),
|
||||
title: "Collect 5 Apples",
|
||||
action: CollectItemKind(Consumable(
|
||||
kind: Apple
|
||||
)),
|
||||
target: 5
|
||||
)
|
||||
]
|
@ -1,7 +1,10 @@
|
||||
use crate::{client::Client, Server, SpawnPoint, StateExt};
|
||||
use common::{
|
||||
assets,
|
||||
comp::{self, item::lottery::Lottery, object, Body, HealthChange, HealthSource, Player, Stats},
|
||||
comp::{
|
||||
self, item::lottery::Lottery, object, AchievementEvent, Body, HealthChange, HealthSource,
|
||||
Player, Stats,
|
||||
},
|
||||
msg::{PlayerListUpdate, ServerMsg},
|
||||
state::BlockChange,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
@ -314,6 +317,12 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) {
|
||||
.get(entity)
|
||||
.expect("Failed to fetch uid component for entity.");
|
||||
|
||||
// Write an achievement update to trigger level achievements
|
||||
let _ = server.state.ecs().write_storage().insert(
|
||||
entity,
|
||||
comp::AchievementUpdate::new(AchievementEvent::LevelUp(new_level)),
|
||||
);
|
||||
|
||||
server
|
||||
.state
|
||||
.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(
|
||||
|
@ -3,9 +3,9 @@ use common::{
|
||||
comp::{
|
||||
self, item,
|
||||
slot::{self, Slot},
|
||||
Pos, MAX_PICKUP_RANGE_SQR,
|
||||
AchievementEvent, Pos, MAX_PICKUP_RANGE_SQR,
|
||||
},
|
||||
event::{AchievementEvent, EventBus},
|
||||
event::AchievementEvent,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
terrain::block::Block,
|
||||
vol::{ReadVol, Vox},
|
||||
@ -86,13 +86,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
|
||||
let item = picked_up_item.unwrap();
|
||||
|
||||
state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<AchievementEvent>>()
|
||||
.emit_now(AchievementEvent::CollectedItem {
|
||||
entity,
|
||||
item: item.clone(),
|
||||
});
|
||||
state.write_component(
|
||||
entity,
|
||||
comp::AchievementUpdate::new(AchievementEvent::CollectedItem(item.clone())),
|
||||
);
|
||||
|
||||
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected(item))
|
||||
} else {
|
||||
@ -122,13 +119,12 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
&& state.try_set_block(pos, Block::empty()).is_some()
|
||||
{
|
||||
comp::Item::try_reclaim_from_block(block).map(|item| {
|
||||
state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<AchievementEvent>>()
|
||||
.emit_now(AchievementEvent::CollectedItem {
|
||||
entity,
|
||||
item: item.clone(),
|
||||
});
|
||||
state.write_component(
|
||||
entity,
|
||||
comp::AchievementUpdate::new(AchievementEvent::CollectedItem(
|
||||
item.clone(),
|
||||
)),
|
||||
);
|
||||
|
||||
state.give_item(entity, item);
|
||||
});
|
||||
|
@ -30,7 +30,7 @@ use crate::{
|
||||
use common::{
|
||||
cmd::ChatCommand,
|
||||
comp::{self, ChatType},
|
||||
event::{AchievementEvent, EventBus, ServerEvent},
|
||||
event::{EventBus, ServerEvent},
|
||||
msg::{ClientState, ServerInfo, ServerMsg},
|
||||
state::{State, TimeOfDay},
|
||||
sync::WorldSyncExt,
|
||||
@ -101,9 +101,6 @@ impl Server {
|
||||
|
||||
// Event Emitters
|
||||
state.ecs_mut().insert(EventBus::<ServerEvent>::default());
|
||||
state
|
||||
.ecs_mut()
|
||||
.insert(EventBus::<AchievementEvent>::default());
|
||||
|
||||
state.ecs_mut().insert(AuthProvider::new(
|
||||
settings.auth_server_address.clone(),
|
||||
@ -262,10 +259,11 @@ impl Server {
|
||||
// Sync and Load Achievement Data
|
||||
debug!("Syncing Achievement data...");
|
||||
|
||||
// TODO I switched this to return comp::Achievement but that's not right...we
|
||||
// want the id really,
|
||||
match persistence::achievement::sync(&settings.persistence_db_dir) {
|
||||
Ok(achievements) => {
|
||||
info!("Achievement data loaded...");
|
||||
info!(?achievements, "data:");
|
||||
state.ecs_mut().insert(AvailableAchievements(achievements));
|
||||
},
|
||||
Err(e) => error!(?e, "Achievement data migration error"),
|
||||
@ -431,20 +429,6 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
let achievement_events = self
|
||||
.state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<AchievementEvent>>()
|
||||
.recv_all();
|
||||
|
||||
for event in achievement_events {
|
||||
match event {
|
||||
AchievementEvent::CollectedItem { entity, item } => {
|
||||
info!(?item, "Achievement event: item");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 7 Persistence updates
|
||||
let before_persistence_updates = Instant::now();
|
||||
|
||||
|
@ -132,7 +132,7 @@ pub fn sync(db_dir: &str) -> Result<Vec<AchievementModel>, Error> {
|
||||
info!(?checksum, "checksum: ");
|
||||
|
||||
migration_entry.checksum != hash(&achievements).to_string()
|
||||
}
|
||||
},
|
||||
Err(diesel::result::Error::NotFound) => {
|
||||
let migration = NewDataMigration {
|
||||
title: "achievements",
|
||||
@ -145,12 +145,12 @@ pub fn sync(db_dir: &str) -> Result<Vec<AchievementModel>, Error> {
|
||||
.execute(&connection)?;
|
||||
|
||||
true
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
error!("Failed to run migrations"); // TODO better error messaging
|
||||
|
||||
false
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (should_run || persisted_achievements.is_empty()) && !achievements.is_empty() {
|
||||
@ -189,7 +189,7 @@ pub fn sync(db_dir: &str) -> Result<Vec<AchievementModel>, Error> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => return Err(Error::DatabaseError(error)),
|
||||
}
|
||||
}
|
||||
@ -207,7 +207,10 @@ pub fn sync(db_dir: &str) -> Result<Vec<AchievementModel>, Error> {
|
||||
info!("No achievement updates required");
|
||||
}
|
||||
|
||||
Ok(schema::achievement::dsl::achievement.load::<AchievementModel>(&connection)?)
|
||||
let data = schema::achievement::dsl::achievement.load::<AchievementModel>(&connection)?;
|
||||
|
||||
Ok(data)
|
||||
// Ok(data.iter().map(comp::Achievement::from).collect::<_>())
|
||||
}
|
||||
|
||||
fn load_data() -> Vec<AchievementItem> {
|
||||
@ -223,7 +226,7 @@ fn load_data() -> Vec<AchievementItem> {
|
||||
Err(error) => {
|
||||
warn!(?error, "Unable to find achievement data file");
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,7 +397,11 @@ where
|
||||
Err(e) => {
|
||||
warn!(?e, "Failed to deserialise achevement data");
|
||||
|
||||
Ok(Self(comp::AchievementItem::default()))
|
||||
Ok(Self(comp::AchievementItem {
|
||||
title: String::new(),
|
||||
action: comp::AchievementAction::None,
|
||||
target: 0,
|
||||
}))
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -456,13 +460,22 @@ pub struct CharacterAchievement {
|
||||
}
|
||||
|
||||
impl From<&CharacterAchievement> for comp::Achievement {
|
||||
fn from(_achievement: &CharacterAchievement) -> comp::Achievement {
|
||||
comp::Achievement::default()
|
||||
fn from(achievement: &CharacterAchievement) -> comp::Achievement {
|
||||
comp::Achievement {
|
||||
id: achievement.achievement_id,
|
||||
item: comp::AchievementItem {
|
||||
title: String::from("TODO"),
|
||||
action: comp::AchievementAction::None,
|
||||
target: 0,
|
||||
},
|
||||
completed: achievement.completed != 0,
|
||||
progress: achievement.progress as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Achievement> for comp::Achievement {
|
||||
fn from(achievement: Achievement) -> comp::Achievement {
|
||||
impl From<&Achievement> for comp::Achievement {
|
||||
fn from(achievement: &Achievement) -> comp::Achievement {
|
||||
comp::Achievement {
|
||||
id: achievement.id,
|
||||
item: comp::AchievementItem::from(&achievement.details),
|
||||
|
@ -1,56 +1,48 @@
|
||||
use crate::persistence::achievement::AvailableAchievements;
|
||||
|
||||
use common::comp::{AchievementItem, AchievementList, InventoryUpdate, Player};
|
||||
use specs::{Entities, Join, ReadExpect, ReadStorage, System};
|
||||
use tracing::info;
|
||||
|
||||
use crate::client::Client;
|
||||
use common::{
|
||||
comp::{Achievement, AchievementItem, AchievementList, AchievementUpdate},
|
||||
msg::ServerMsg,
|
||||
};
|
||||
use specs::{Join, ReadExpect, System, WriteStorage};
|
||||
pub struct Sys;
|
||||
|
||||
impl<'a> System<'a> for Sys {
|
||||
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
ReadStorage<'a, Player>,
|
||||
ReadStorage<'a, AchievementList>,
|
||||
ReadStorage<'a, InventoryUpdate>,
|
||||
WriteStorage<'a, Client>,
|
||||
WriteStorage<'a, AchievementList>,
|
||||
WriteStorage<'a, AchievementUpdate>,
|
||||
ReadExpect<'a, AvailableAchievements>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(entities, players, achievement_lists, inventory_updates, available_achievements): Self::SystemData,
|
||||
(mut clients, mut achievement_lists, mut achievement_updates, available_achievements): Self::SystemData,
|
||||
) {
|
||||
for (_entity, _player, ach_list, inv_event) in
|
||||
(&entities, &players, &achievement_lists, &inventory_updates).join()
|
||||
// TODO filter out achievements which do not care about this event here, then
|
||||
// iterate over them
|
||||
for (client, achievement_list, ach_update) in
|
||||
(&mut clients, &mut achievement_lists, &achievement_updates).join()
|
||||
{
|
||||
(available_achievements.0)
|
||||
.iter()
|
||||
.for_each(|achievement_item| {
|
||||
let ach_item = AchievementItem::from(&achievement_item.details);
|
||||
// pass the event to each achievement
|
||||
// achievement checks if the event matches what it is looking for
|
||||
if ach_item.matches_event(inv_event.event()) {
|
||||
if let Some(event) = ach_list.process(&ach_item) {
|
||||
info!(?event, "Achievement event");
|
||||
}
|
||||
(available_achievements.0).iter().for_each(|achievement| {
|
||||
let achievement_item = AchievementItem::from(&achievement.details);
|
||||
|
||||
// if it's a match, pass it to the characters
|
||||
// achievement list
|
||||
// get a result from the call to the players achievement
|
||||
// list
|
||||
// - It checks for an entry No entry = append and
|
||||
// increment Entry = Increment
|
||||
// inrement(achievement_id, ?amount)
|
||||
// if let Some(results) =
|
||||
// _player_character_list.process(achievement_item) {
|
||||
//
|
||||
// - if its a completion result, dispatch an event which
|
||||
// notifies the client
|
||||
// server_events.dispatch(ServerEvent::
|
||||
// AchievementUpdate(TheAchievementInfo))
|
||||
// }
|
||||
if achievement_item.matches_event(ach_update.event()) {
|
||||
// Calls to `process_achievement` return true to indicate that the
|
||||
// achievement is complete. In this case, we notify the client to notify them of
|
||||
// completing the achievement
|
||||
if achievement_list
|
||||
.process_achievement(Achievement::from(achievement), ach_update.event())
|
||||
== true
|
||||
{
|
||||
client.notify(ServerMsg::AchievementCompletion);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
achievement_updates.clear();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user