mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Initial models, migration and client code for stats persistence.
This commit is contained in:
parent
19a0ffb673
commit
e5853dbdd4
@ -17,4 +17,5 @@ pub struct Character {
|
||||
pub struct CharacterItem {
|
||||
pub character: Character,
|
||||
pub body: comp::Body,
|
||||
pub stats: comp::Stats,
|
||||
}
|
||||
|
1
server/src/migrations/2020-04-20-072214_stats/down.sql
Normal file
1
server/src/migrations/2020-04-20-072214_stats/down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS "stats";
|
10
server/src/migrations/2020-04-20-072214_stats/up.sql
Normal file
10
server/src/migrations/2020-04-20-072214_stats/up.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE "stats" (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
character_id INT NOT NULL,
|
||||
"level" INT NOT NULL DEFAULT 1,
|
||||
"exp" INT NOT NULL DEFAULT 0,
|
||||
endurance INT NOT NULL DEFAULT 0,
|
||||
fitness INT NOT NULL DEFAULT 0,
|
||||
willpower INT NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(character_id) REFERENCES "character"(id) ON DELETE CASCADE
|
||||
);
|
@ -1,9 +1,10 @@
|
||||
extern crate diesel;
|
||||
|
||||
use super::{
|
||||
error::Error,
|
||||
establish_connection,
|
||||
models::{Body, Character, NewCharacter},
|
||||
schema, Error,
|
||||
models::{Body, Character, NewCharacter, Stats, StatsJoinData},
|
||||
schema,
|
||||
};
|
||||
use crate::comp;
|
||||
use common::character::{Character as CharacterData, CharacterItem, MAX_CHARACTERS_PER_PLAYER};
|
||||
@ -14,50 +15,60 @@ type CharacterListResult = Result<Vec<CharacterItem>, Error>;
|
||||
// Loading of characters happens immediately after login, and the data is only
|
||||
// for the purpose of rendering the character and their level in the character
|
||||
// list.
|
||||
pub fn load_characters(uuid: &str) -> CharacterListResult {
|
||||
use schema::{body, character::dsl::*};
|
||||
|
||||
let data: Vec<(Character, Body)> = character
|
||||
.filter(player_uuid.eq(uuid))
|
||||
.order(id.desc())
|
||||
.inner_join(body::table)
|
||||
.load::<(Character, Body)>(&establish_connection())?;
|
||||
pub fn load_characters(player_uuid: &str) -> CharacterListResult {
|
||||
let data: Vec<(Character, Body, Stats)> = schema::character::dsl::character
|
||||
.filter(schema::character::player_uuid.eq(player_uuid))
|
||||
.order(schema::character::id.desc())
|
||||
.inner_join(schema::body::table)
|
||||
.inner_join(schema::stats::table)
|
||||
.load::<(Character, Body, Stats)>(&establish_connection())?;
|
||||
|
||||
Ok(data
|
||||
.iter()
|
||||
.map(|(character_data, body_data)| CharacterItem {
|
||||
character: CharacterData::from(character_data),
|
||||
body: comp::Body::from(body_data),
|
||||
.map(|(character_data, body_data, stats_data)| {
|
||||
let character = CharacterData::from(character_data);
|
||||
let body = comp::Body::from(body_data);
|
||||
let stats = comp::Stats::from(StatsJoinData {
|
||||
character: &character,
|
||||
body: &body,
|
||||
stats: stats_data,
|
||||
});
|
||||
|
||||
CharacterItem {
|
||||
character,
|
||||
body,
|
||||
stats,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Create a new character with provided comp::Character and comp::Body data.
|
||||
/// Note that sqlite does not suppport returning the inserted data after a
|
||||
/// Note that sqlite does not support returning the inserted data after a
|
||||
/// successful insert. To workaround, we wrap this in a transaction which
|
||||
/// inserts, queries for the newly created chaacter id, then uses the character
|
||||
/// id for insertion of the `body` table entry
|
||||
pub fn create_character(
|
||||
uuid: &str,
|
||||
alias: String,
|
||||
tool: Option<String>,
|
||||
character_alias: String,
|
||||
character_tool: Option<String>,
|
||||
body: &comp::Body,
|
||||
) -> CharacterListResult {
|
||||
check_character_limit(uuid)?;
|
||||
|
||||
let new_character = NewCharacter {
|
||||
player_uuid: uuid,
|
||||
alias: &alias,
|
||||
tool: tool.as_deref(),
|
||||
};
|
||||
|
||||
let connection = establish_connection();
|
||||
|
||||
connection.transaction::<_, diesel::result::Error, _>(|| {
|
||||
use schema::{body, character, character::dsl::*};
|
||||
use schema::{body, character, character::dsl::*, stats};
|
||||
|
||||
match body {
|
||||
comp::Body::Humanoid(body_data) => {
|
||||
let new_character = NewCharacter {
|
||||
player_uuid: uuid,
|
||||
alias: &character_alias,
|
||||
tool: character_tool.as_deref(),
|
||||
};
|
||||
|
||||
diesel::insert_into(character::table)
|
||||
.values(&new_character)
|
||||
.execute(&connection)?;
|
||||
@ -83,6 +94,22 @@ pub fn create_character(
|
||||
diesel::insert_into(body::table)
|
||||
.values(&new_body)
|
||||
.execute(&connection)?;
|
||||
|
||||
let default_stats = comp::Stats::new(String::from(new_character.alias), *body);
|
||||
|
||||
// Insert some default stats
|
||||
let new_stats = Stats {
|
||||
character_id: inserted_character.id as i32,
|
||||
level: default_stats.level.level() as i32,
|
||||
exp: default_stats.exp.current() as i32,
|
||||
endurance: default_stats.endurance as i32,
|
||||
fitness: default_stats.fitness as i32,
|
||||
willpower: default_stats.willpower as i32,
|
||||
};
|
||||
|
||||
diesel::insert_into(stats::table)
|
||||
.values(&new_stats)
|
||||
.execute(&connection)?;
|
||||
},
|
||||
_ => log::warn!("Creating non-humanoid characters is not supported."),
|
||||
};
|
||||
@ -105,12 +132,10 @@ fn check_character_limit(uuid: &str) -> Result<(), Error> {
|
||||
use diesel::dsl::count_star;
|
||||
use schema::character::dsl::*;
|
||||
|
||||
let connection = establish_connection();
|
||||
|
||||
let character_count = character
|
||||
.select(count_star())
|
||||
.filter(player_uuid.eq(uuid))
|
||||
.load::<i64>(&connection)?;
|
||||
.load::<i64>(&establish_connection())?;
|
||||
|
||||
match character_count.first() {
|
||||
Some(count) => {
|
||||
|
24
server/src/persistence/error.rs
Normal file
24
server/src/persistence/error.rs
Normal file
@ -0,0 +1,24 @@
|
||||
extern crate diesel;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
// The player has alredy reached the max character limit
|
||||
CharacterLimitReached,
|
||||
// An error occured when performing a database action
|
||||
DatabaseError(diesel::result::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
Self::DatabaseError(diesel_error) => diesel_error.to_string(),
|
||||
Self::CharacterLimitReached => String::from("Character limit exceeded"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<diesel::result::Error> for Error {
|
||||
fn from(error: diesel::result::Error) -> Error { Error::DatabaseError(error) }
|
||||
}
|
@ -1,34 +1,13 @@
|
||||
pub mod character;
|
||||
mod error;
|
||||
mod models;
|
||||
|
||||
mod schema;
|
||||
|
||||
extern crate diesel;
|
||||
|
||||
use diesel::prelude::*;
|
||||
use diesel_migrations::embed_migrations;
|
||||
use std::{env, fmt, fs, path::Path};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
// The player has alredy reached the max character limit
|
||||
CharacterLimitReached,
|
||||
// An error occured when performing a database action
|
||||
DatabaseError(diesel::result::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
Self::DatabaseError(diesel_error) => diesel_error.to_string(),
|
||||
Self::CharacterLimitReached => String::from("Character limit exceeded"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<diesel::result::Error> for Error {
|
||||
fn from(error: diesel::result::Error) -> Error { Error::DatabaseError(error) }
|
||||
}
|
||||
use std::{env, fs, path::Path};
|
||||
|
||||
// See: https://docs.rs/diesel_migrations/1.4.0/diesel_migrations/macro.embed_migrations.html
|
||||
// This macro is called at build-time, and produces the necessary migration info
|
||||
|
@ -1,7 +1,15 @@
|
||||
use super::schema::{body, character};
|
||||
use super::schema::{body, character, stats};
|
||||
use crate::comp;
|
||||
use common::character::Character as CharacterData;
|
||||
|
||||
/// When we want to build player stats from database data, we need data from the
|
||||
/// character, body and stats tables
|
||||
pub struct StatsJoinData<'a> {
|
||||
pub character: &'a CharacterData,
|
||||
pub body: &'a comp::Body,
|
||||
pub stats: &'a Stats,
|
||||
}
|
||||
|
||||
/// `Character` represents a playable character belonging to a player
|
||||
#[derive(Identifiable, Queryable, Debug)]
|
||||
#[table_name = "character"]
|
||||
@ -63,3 +71,45 @@ impl From<&Body> for comp::Body {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// `Stats` represents the stats for a character
|
||||
#[derive(Associations, Identifiable, Queryable, Debug, Insertable)]
|
||||
#[belongs_to(Character)]
|
||||
#[primary_key(character_id)]
|
||||
#[table_name = "stats"]
|
||||
pub struct Stats {
|
||||
pub character_id: i32,
|
||||
pub level: i32,
|
||||
pub exp: i32,
|
||||
pub endurance: i32,
|
||||
pub fitness: i32,
|
||||
pub willpower: i32,
|
||||
}
|
||||
|
||||
impl From<StatsJoinData<'_>> for comp::Stats {
|
||||
fn from(data: StatsJoinData) -> comp::Stats {
|
||||
let mut base_stats = comp::Stats::new(String::from(&data.character.alias), *data.body);
|
||||
|
||||
base_stats.level.set_level(data.stats.level as u32);
|
||||
base_stats.update_max_hp();
|
||||
|
||||
base_stats.exp.set_current(data.stats.exp as u32);
|
||||
|
||||
base_stats.endurance = data.stats.endurance as u32;
|
||||
base_stats.fitness = data.stats.fitness as u32;
|
||||
base_stats.willpower = data.stats.willpower as u32;
|
||||
|
||||
base_stats
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(AsChangeset)]
|
||||
#[primary_key(character_id)]
|
||||
#[table_name = "stats"]
|
||||
pub struct StatsUpdate {
|
||||
pub level: Option<i32>,
|
||||
pub exp: Option<i32>,
|
||||
pub endurance: Option<i32>,
|
||||
pub fitness: Option<i32>,
|
||||
pub willpower: Option<i32>,
|
||||
}
|
||||
|
@ -22,6 +22,18 @@ table! {
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(body -> character (character_id));
|
||||
table! {
|
||||
stats (character_id) {
|
||||
character_id -> Integer,
|
||||
level -> Integer,
|
||||
exp -> Integer,
|
||||
endurance -> Integer,
|
||||
fitness -> Integer,
|
||||
willpower -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
allow_tables_to_appear_in_same_query!(body, character,);
|
||||
joinable!(body -> character (character_id));
|
||||
joinable!(stats -> character (character_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(body, character, stats,);
|
||||
|
33
server/src/persistence/stats.rs
Normal file
33
server/src/persistence/stats.rs
Normal file
@ -0,0 +1,33 @@
|
||||
extern crate diesel;
|
||||
|
||||
use super::{establish_connection, models::StatsUpdate, schema};
|
||||
use diesel::prelude::*;
|
||||
|
||||
pub fn update(
|
||||
character_id: i32,
|
||||
level: Option<i32>,
|
||||
exp: Option<i32>,
|
||||
endurance: Option<i32>,
|
||||
fitness: Option<i32>,
|
||||
willpower: Option<i32>,
|
||||
) {
|
||||
use schema::stats;
|
||||
|
||||
match diesel::update(stats::table)
|
||||
.set(&StatsUpdate {
|
||||
level,
|
||||
exp,
|
||||
endurance,
|
||||
fitness,
|
||||
willpower,
|
||||
})
|
||||
.execute(&establish_connection())
|
||||
{
|
||||
Err(error) => log::warn!(
|
||||
"Failed to update stats for player with character_id: {:?}: {:?}",
|
||||
character_id,
|
||||
error
|
||||
),
|
||||
_ => {},
|
||||
};
|
||||
}
|
@ -341,14 +341,19 @@ impl CharSelectionUi {
|
||||
Mode::Select(data) => data.clone(),
|
||||
Mode::Create {
|
||||
name, body, tool, ..
|
||||
} => Some(vec![CharacterItem {
|
||||
character: Character {
|
||||
id: None,
|
||||
alias: name.clone(),
|
||||
tool: tool.map(|specifier| specifier.to_string()),
|
||||
},
|
||||
body: comp::Body::Humanoid(body.clone()),
|
||||
}]),
|
||||
} => {
|
||||
let body = comp::Body::Humanoid(body.clone());
|
||||
|
||||
Some(vec![CharacterItem {
|
||||
character: Character {
|
||||
id: None,
|
||||
alias: name.clone(),
|
||||
tool: tool.map(|specifier| specifier.to_string()),
|
||||
},
|
||||
body,
|
||||
stats: comp::Stats::new(String::from(name), body),
|
||||
}])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user